Android ListView封装

Android ListView封装(代码优化):抽取方法共性,封装 BaseAdapter 和 ViewHolder

发表于2016/9/7 20:09:18  802人阅读

分类: Android UI学习 Android 学习笔记

这里写图片描述


大多App都会使用到的基本控件 ——- Listiew,特别像新闻浏览类的比如说“今日关注”,或者“应用宝”这种汇集手机软件集合的。而且大家都知道 需要给每个单独的 ListView 搭配相应的适配器 Adapter 。如果你的项目中使用ListView 的频率很少甚至没有,那我不建议你对 ListView 进行抽取封装,但是!如果它的使用渗透到App中大多页面时,你必须考虑 对Adapter的公共方法进行抽取封装到单独的类中,来避免整个项目代码的冗杂,使代码结构规范化


一. 未封装版 ListView

HomeFragment.java

@Override
public View onCreateSuccessView() {
    ListView view = new ListView(UIUtils.getContext());
    initData();
    view.setAdapter(new HomeAdapter());
    return view;
}

private void initData() {
    for (int i = 0; i < 50; i++) {
        mList.add("测试数据" + i);
    }
}

class HomeAdapter extends BaseAdapter {

    @Override
    public int getCount() {
        return mList.size();
    }

    @Override
    public String getItem(int position) {
        return mList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = View.inflate(UIUtils.getContext(),
                    R.layout.list_item_home, null);
            holder = new ViewHolder();
            holder.tvContent = (TextView) convertView
                    .findViewById(R.id.tv_content);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        holder.tvContent.setText(getItem(position));

        return convertView;
    }
}

static class ViewHolder {
    public TextView tvContent;
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

这里我们主要 把重点放在 Adapter 和 ViewHolder 的抽取封装,首先简单分析其逻辑组成

1.首先看到我自定义的 HomeAdapter 继承的是 BaseAdapter 。继承的四个方法中,前三个:getCount 、getItem 、getItemId 看的出来方法及其简单,只涉及到一个不确定量——显示的数据类,易封装,使用泛型定义数据类型即可。

但是getView方法中步骤略复杂,首先梳理清楚方法里的逻辑,才好进一步的封装: 
(1)加载布局文件,布局转换(xml —> view) 
(2)初始化控件(finViewById) 
(3)给ViewHolder设置标记(setTag),便于下次复用 
(4)给控件设置数据


static class ViewHolder {
    public TextView tvContent;
}
   
   
  • 1
  • 2
  • 3

2. ViewHolder 类中定义的是一个单独条目中的所有控件声明,也许你会发现 不同的ListView中适配器 Adapter中有共性的方法,但是 不同页面中展示条目不尽相同,根本无法提取。事实的确是这样,但是Adapter中肯定会用到 ViewHolder类 ,而且getView方法中的部分逻辑还要依靠这个类去完成,所以我们还是 要继续提取一个基类 BaseHolder,至于取决于不同页面逻辑也不同的地方,封装一个抽象方法丢给子类去实现即可!






二. BaseAdapter封装

1.抽取BaseAdapter的共性方法,封装到MyBaseAdapter

/**
 * 数据适配器的基类
 * 展示的数据类型不确定,故使用泛型!
 */
public class MyBaseAdapter<T> extends BaseAdapter {

    private ArrayList<T> list;

    public MyBaseAdapter(ArrayList<T> list) {
        this.list = list;
    }

    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public T getItem(int position) {
        return list.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        return null;
    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

首先我们对 继承Basedapter的三个方法:getCount 、getItem 、getItemId 进行封装,由于方法中涉及到 展示的条目数据,这里要增加一个 构造方法 —— 用来接收 数据集合。至于展示的数据集合类型无法确定,所以才用泛型来定义数据集。定义的类也要加上泛型。MyBaseAdapter<T>


2. getView 封装

这里只剩下getView方法还未封装,我们再次复习其中逻辑: 
(1)加载布局文件,布局转换(xml —> view) 
(2)初始化控件(finViewById) 
(3)给ViewHolder设置标记(setTag),便于下次复用 
(4)给控件设置数据

MyBaseAdapter.java

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    BaseHolder<T> holder = null;
    if (convertView == null) {
        // 在初始化holder的同时,已经对布局进行了加载,也给view设置了tag
        holder = getHolder(position);
    } else {
        holder = (BaseHolder<T>) convertView.getTag();
    }
    // 刷新界面,更新数据
    holder.setData(getItem(position));

    return holder.getRootView();
}

// 返回BaseHolder的子类,必须实现
public abstract BaseHolder<T> getHolder(int position);
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

首先初始化我们从ViewHolder中提取的基类泛型 BaseHolder<T> 。 
(1)前3点逻辑 主要依赖于具体的页面,不同的页面应当对应不同的ViewHolder,所以在这个封装一个抽象方法getHolder ,交由 子类继承 MyBaseAdapter去实现! 
(2)第四点逻辑 ,更新数据,直接调用 BaseHolder中的方法。





三. BaseHolder封装

1.抽取ViewHolder的功能,封装到BaseHolder

由以上可知,BaseHolder类中应做的事 
1. 初始化item布局,2. findViewById方法一个抽象方法,由子类在初始化布局时实现! 
3. 给view设置tag:BaseHolder的构造方法中实现! 
4. 刷新界面:抽象方法,由子类去实现!

所以,BaseHolder相当于是对getView方法的封装

public abstract class BaseHolder<T> {

    private View mRootView;// item的布局对象
    private T data;// item对应的数据

    public BaseHolder() {
        mRootView = initView();// 初始化布局
        mRootView.setTag(this);// 给view设置tag
    }

    // 初始化布局的方法必须由子类实现
    public abstract View initView();

    // 返回布局对象
    public View getRootView() {
        return mRootView;
    };

    // 设置数据
    public void setData(T data) {
        this.data = data;
        refreshView(data);
    }

    // 获取数据
    public T getData() {
        return data;
    }

    // 刷新界面,更新数据,子类必须实现
    public abstract void refreshView(T data);
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32



——–以上已经封装完毕!———–



四. BaseHolder耦合于getView

解析BaseHolder: 
若按照以上解释,那BaseHolder只需要两个抽象方法和构造函数即可,可并非如此!既然BaseHolder是对getView方法的一个封装,那它需要完成getView中的所有功能,可它跟getView方法是耦合的,所以需要相互配合,再查看封装好的 getView方法:

MyBaseAdapter.java

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    BaseHolder<T> holder = null;
    if (convertView == null) {
        // 在初始化holder的同时,已经对布局进行了加载,也给view设置了tag
        holder = getHolder(position);
    } else {
        holder = (BaseHolder<T>) convertView.getTag();
    }
    // 刷新界面,更新数据
    holder.setData(getItem(position));

    return holder.getRootView();
}

// 返回BaseHolder的子类,必须实现
public abstract BaseHolder<T> getHolder(int position);
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19


1. 这里的holder调用的是 由子类实现的抽象方法

// 在初始化holder的同时,已经对布局进行了加载,也给view设置了tag
        holder = getHolder(position);
   
   
  • 1
  • 2

具体就是new 一个 继承BaseHolder 的子类,所以我们new完这个子类后,需要完成getView的1.2.3点逻辑所以在 Baseholder中的构造方法中完成此3点逻辑(抽象方法)。


// 刷新界面,更新数据
    holder.setData(getItem(position));
   
   
  • 1
  • 2

2. 而这里的设置数据的方法不适合直接设为抽象,因为涉及到数据的传递(数据类型不确定,依旧为泛型)。方法中第一步是接收数据,第二步才是刷新数据。我们的做法是把第二步的逻辑(刷新数据)单独拿出来,封装成一个抽象方法,留给子类BaseHolder去实现!


return holder.getRootView();
   
   
  • 1

3.getView 的最后一步 —— 将当前item的View返回!可是我们具体View的初始化都交由子类实现了,BaseHodler 并无View。这时我们就要在BaseHodler 单独定义一个方法getRootView()来返回当前item的布局。



以上,才是BaseAdapter 和ViewHolder的完美搭配,即MyBaseAdapter 和 BaseHodler 完美搭配





举一个栗子:

MyBaseAdapter 的继承:

HomeFragment.java
public class HomeFragment extends BaseFragment {
    private ArrayList<String> data;

    @Override
    public View onCreateSuccessView() {
        initData();
        ListView view = new ListView(UIUtils.getContext());
        view.setAdapter(new HomeAdapter(data));
        return view;
    }

   private void initData(){
        for (int i = 0; i < 50; i++) {
            data.add("测试数据" + i);
      }

class HomeAdapter extends MyBaseAdapter<AppInfo> {
        private ArrayList<String> data;

        public HomeAdapter(ArrayList<AppInfo> data) {
            super(data);
        }

        @Override
        public BaseHolder<AppInfo> getHolder() {
            return new HomeHolder();
        }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

BaseHodler 的继承:

public class HomeHolder extends BaseHolder<AppInfo> {

    private TextView tvContent;

    @Override
    public View initView() {
        // 1. 加载布局
        View view = UIUtils.inflate(R.layout.list_item_home);
        // 2. 初始化控件
        tvContent = (TextView) view.findViewById(R.id.tv_content);
        return view;
    }

    @Override
    public void refreshView(Stringdata) {
        tvContent.setText(data);
    }

}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

这里写图片描述




喔~~~~大家感觉到了吗?!代码量骤减啊!!!好处自是不必多说,用心体会~ 
希望对你有帮助 :)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值