Android 学习笔记之ListView二层封装


ListView作为Android 开发中使用最多的控件之一,相信很多人对他的用法应该都不陌生,最主要就是,定义好每个Itme的布局,给其设置一个数据适配器(Adapter)就完事,下面来回顾一下ListVew的一般用法。

/**
 * Created by 毛麒添 on 2017/1/18 0018.
 */

public class MainActivity extends AppCompatActivity {
    private ArrayListdata;

   protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        data=new ArrayList();
        for (int i=0;i<30;i++){
            data.add("listView数据"+i);
        }

        ListView listView=new ListView(getContext());
        listView.setAdapter(new MyAdapter());
    }


    class MyAdapter extends BaseAdapter{

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

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

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

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder=null;
            if(convertView==null){
                //1.加载布局文件
                convertView= MyUIUtils.inflate(R.layout.list_home_item);
                viewHolder=new ViewHolder();
                //2.初始化控件
                viewHolder.textView= (TextView) convertView.findViewById(R.id.tv_test);
                //3.打一个标记tag
                convertView.setTag(viewHolder);
            }else {
                viewHolder= (ViewHolder) convertView.getTag();
            }
            //4.根据数据来刷新界面
            viewHolder.textView.setText(getItem(position));
            return convertView;
        }
    }

    static class ViewHolder{
        TextView textView;
    }
}
  • 如果我们只是给一个ListView设置数据,则写一个上面的数据适配器无可厚非,但是如果在一个项目中有许多的ListView,则给每个ListView都写一个数据适配器,则上面的适配器中必须实现 getCount() getItem(),getItemId(),getView() 方法对每个都要写一次,这样多几次应该会感觉很烦,那有没有可能对这几个方法抽取出来,进行封装,简便要写多个 ListView 数据适配器的烦恼呢?答案是肯定的,下面就开始对ListView的数据适配器进行两层封装,以达到我们的目的。

ListView的第一层封装

  • 首先可以对最简单的 getCount(),getItem(),getItemId() 方法进行封装,创建我们自己的父类Adapter,让其继承BaseAdpter.其中getCount(),getItem(),getItemId() 最后设置的返回值一般都是依赖一个List数组,但这个父类是我们定义的,则可以用构造方法来传入List数组,而这时候问题又来了,数组的泛型我们也不知道,这时候可以把泛型定义为Object,但是当我们传入数据的时候则就要将 Object 强转为我们传入数据对应的泛型,其实这里可以直接传入一个大写字母(ArrayList 源码中也是如此定义),代表传入的数据泛型,下面上代码:
/**
 * Created by 毛麒添 on 2017/1/21 0021.
 * 对Adapter的封装 简化数据适配器的设置
 */

public  class MyBaseAdapter extends BaseAdapter {


    private ArrayListdata; //T代表泛型,传入的是什么类型就是什么类型

    public MyBaseAdapter(ArrayListdata){
         this.data=data;
    }

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

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

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
       return null;
    }

}
  • 这样当我们使用 MyBaseAdapter 来作为数据适配器的父类,就不用再写 getCount(),getItem(),getItemId()这三个方法了,OK,完成对 ListView 的第一层封装,但是这个我们自定义的父类方法还没完成,接下来就是对getView()的封装。

ListView的第二层封装

  • 前面只是对 getCount(), getItem() ,getItemId() 等方法进行了封装,接下来便是对 getView() 方法来进行封装,以前我们写ListView数据适配器的getView()方法一般都是以下四个步骤:
  • 1.加载布局文件
  • 2.初始化控件
  • 3.打一个标记tag
  • 4.根据数据来刷新界面
  • 无论如何,这四个步骤都是要实现的。而这四个步骤中,都在其中有ViewHolder帮助类来帮助我们实现,所以我们可以考虑在将这四个步骤抽取成为一个工具类,也就是自己定义一个父类holder,在其中实现这几个步骤,进一步进行封装。
import android.view.View;

/**
 * Created by 毛麒添 on 2017/1/21 0021.
 * getView(); ViewHolder封装
 */

public abstract class BaseHolder {


    private final View view;//ListView的item根布局View对象

    private T data;

    /**获取这个父类holder,就是执行
     * 1.加载布局文件
     * 2.初始化控件
     * 3.打一个标记tag
     * 这三个步骤
    */
    public BaseHolder(){

        view = initView();
        //3.打一个标记tag,也就是当前的BaseHolder
        view.setTag(this);
    }

    //设置当前Item的数据
    public void setData(T data){
       this.data=data;
        //获取数据直接将其传递给子类刷新数据
        RefreshView(data);
    }

    //获取当前Item的数据
    public T getData(){
        return data;
    }

    /**
     * 本身并不知道需要加载什么样的布局和控件,让子类去实现
     * 1.加载布局文件
     * 2.初始化控件
     * @return 返回相应的item根布局View对象
     */
    public abstract View initView();

    /**
     * 获取Item布局对象
     * @return 返回item布局对象
     */
    public View getItemView(){
        return view;
    }

    //4.根据数据来刷新界面,本身并不知道是什么数据,让子类去实现
    public abstract void RefreshView(T data);

}
  • 代码中,首选我们实现第一、二个步骤,但是这只是所有布局Item的父类,并不知道每个ListView的Item需要展现什么样的布局,所以定义一个抽象方法 initView()让子类去实现布局的加载与控件的初始化,而且在初始化父类的时候,也就是在构造方法中就调用该方法获取对应的Item的View对象,既然获取了对应的View对象,并且该类就是四个步骤ed封装,也就可以给其设置一个Tag,到此,前三个步骤已经搞定,最后一个步骤是给Item布局设置刷新数据,同理也是定义成抽象方法RefreshView(T data)让子类去实现,再加上两个数据set和get方法。
    至此,四个步骤封装完成。
  • 接下来搞个子类继承BaseHolder实现未实现的方法,指定特定的ListView的Item需要展示的数据。
/**
 * Created by 毛麒添 on 2017/1/21 0021.
 * BaseHolder子类
 */

public class MyHolder extends BaseHolder {

    private TextView textView;

    @Override
    public View initView() {
        //步骤1.加载布局文件
        View view= MyUIUtils.inflate(R.layout.list_home_item);
        //步骤2.初始化控件
        textView = (TextView) view.findViewById(R.id.tv_test);
        return view;
    }

    //步骤4刷新页面数据
    @Override
    public void RefreshView(String data) {
        textView.setText(data);
    }


}

现在,就可以把上面未完成的MyBaseAdapter父类完整实现:

public abstract class MyBaseAdapter extends BaseAdapter {


    private ArrayListdata; //T代表泛型,传入的是什么类型就是什么类型

    public MyBaseAdapter(ArrayListdata){
         this.data=data;
    }

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

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

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        BaseHolder baseHolder;
        if(convertView==null){
            /**
             * 1.加载布局文件
             * 2.初始化控件
             * 3.打一个标记tag
             */
            baseHolder=getHolder();//由子类返回具体对象
        }else {
            baseHolder= (BaseHolder) convertView.getTag();
        }
        //4.根据数据来刷新界面
        baseHolder.setData(getItem(position));

        //返回item对象
        return baseHolder.getItemView();
    }

    //返回当前页面的holder对象,必须由子类去实现
    public abstract BaseHolder getHolder();
}
  • 在上面的getView()方法,只要初始化BaseHolder的对象,就已经完成了四个步骤中的前三个,第四个步骤只要将数据设置给BaseHolder的对象也就完成了,对象的初始化也是需要继承该方法的子类去实现,因为每个LsitView的所展示的Item是不同的。

  • 终于,对ListView二层封装已经完成,下面我们来使用一发:
public class MainActivity extends AppCompatActivity {
    private ArrayListdata;

   protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        data=new ArrayList();
        for (int i=0;i<30;i++){
            data.add("listView数据"+i);
        }

        ListView listView=new ListView(getContext());
        listView.setAdapter(new MyAdapter());
    }


    class MyAdapter extends MyBaseAdapter{

     public MyAdapter(ArrayList data) {
            super(data);
        }

        @Override
        public BaseHolder getHolder() {
            return new MyHolder();
        }
}
  • 好了,以后我们在使用ListView,只需要写一个类继承BaseHolder,然后按照我们的数据获取初始化布局和控件,然后设置数据,只需要在数据适配器中初始化该类就完事。

最后

抽取相同的部分,封装,应该相当于搭建了一个小框架吧,也让我对适配器有了更进一步的认识。如果有哪些地方写得不对,欢迎大家给我提出来纠正,让我们一同进步。

你可能感兴趣的:(Android 学习笔记之ListView二层封装)