• 代码优化>>>Android ListView适配器三级优化详解


    转载本专栏每一篇博客请注明转载出处地址,尊重原创。此博客转载链接地址:点击打开链接  http://blog.csdn.net/qq_32059827/article/details/52718489

    对ListView的优化,也就是对其封装:抽取方法共性,封装 BaseAdapter 和 ViewHolder

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

    首先,我们在写ListView的时候一般会这么写:

    一. 未封装版 ListView
    
    @Override
    public View onCreateSuccessView() {
        ListView view = new ListView(UIUtils.getContext());
        initData();
        view.setAdapter(new MyListViewAdapter());
        return view;
    }
    
    private void initData() {
        for (int i = 0; i < 50; i++) {
            mList.add("测试数据" + i);
        }
    }
    
    class MyListViewAdapterextends 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.mListTextView= (TextView) convertView
                        .findViewById(R.id.tv_content);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
    
            holder.mListTextView.setText(getItem(position));
    
            return convertView;
        }
    }
    
    static class ViewHolder {
        public TextView mListTextView;
    }

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

    1.首先看到我自定义的 HomeAdapter 继承的是 BaseAdapter 。继承的四个方法中,前三个:getCountgetItemgetItemId 看的出来方法及其简单,

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

    那么就开始对市面上60%的项目中的封装方法,进行普通封装。

    封装一》》》普通封装:方式,保留getView(),getCountgetItemgetItemId 先封装。

    方法:抽取类名:MyBaseAdapter。基类是需要一个数据源的,因此通过构造方法得到这个数据源;注意:数据源是什么类型,通过泛型指定。

    public class MyBaseAdapter<T> extends BaseAdapter {//在类旁边声明一下泛型
    	
    	private ArrayList<T> mList;
    	
    	/**
    	 * 我需要一个集合,那么就由孩子传递过来。但是孩子这么多,集合可以有好多类型,那么集合到底写什么类型?泛型更好用,孩子什么类型,我就什么类型
    	 */
    	public MyBaseAdapter(ArrayList<T> list){
    		this.mList = list;
    	}
    
    	@Override
    	public int getCount() {
    		// TODO Auto-generated method stub
    		return mList.size();
    	}
    
    	@Override
    	public T getItem(int position) {
    		// TODO Auto-generated method stub
    		return mList.get(position);
    	}
    
    	@Override
    	public long getItemId(int position) {
    		// TODO Auto-generated method stub
    		return position;
    	}
    
    	@Override
    	public View getView(int position, View convertView, ViewGroup parent) {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    }

    因为抽取了一些方法,我们原来的Adapter类继承自MyBaseAdapter的代码就简单了一些,如下:

    private class MyListViewAdapter extends MyBaseAdapter<String>{//在这里告知父亲,我继承你我要给你的集合传递什么类型
    
    		//通过构造函数,把孩子的集合传给父亲
    		public MyListViewAdapter(ArrayList<String> list) {
    			super(list);
    		}
    
    		@Override
    		public View getView(int position, View convertView, ViewGroup parent) {
    			// 自定义listview界面
    			ViewHolder holder = null;
    			if(convertView == null){
    				holder = new ViewHolder();
    				//1、加载item布局
    				convertView = UIUtils.inflate(R.layout.homefragment_listview_item);
    				//2、获取item布局实例
    				holder.mListTextView = (TextView) convertView.findViewById(R.id.tv_listview_item_text);
    				//3、viewholder设置到tag
    				convertView.setTag(holder);
    			}else{
    				//若不为空,在缓存对象里取出
    				holder = (ViewHolder) convertView.getTag();
    			}
    			
    			//4、设置listview布局上的控件数据
    			holder.mListTextView.setText(getItem(position));
    			return convertView;
    		}
    		
    	}

    同时,原来实例化适配器代码也要稍作修改,传递数据源到基类里面:

    //获取适配器对象
    MyListViewAdapter listAdapter = new MyListViewAdapter(mListDatas);

    现在运行程序,跟以前的效果是一样的。如下:

    到目前为止,叫做是普通抽取,也就是60%应用可能是这么做的,对于每个子类的getView()都是自己实现自己的。

    但是要想再高大上一些,要想根那些牛逼点的app一样,就要抽取getView(),对于这一点,比较复杂。接下来就一点点的演变整个过程。

    在演变之前,要对ListView的加载流程几个问题要清楚,只有理解了ListView的加载流程,才能更好的而理解抽取getView()的思想。

    HomeHolder抽取前思考问

    1. 为什么想到去抽取?
    2. convertView的作用 ?
    3. ViewHolder是什么 ?
    4. ViewHolder的作用 ?
    5. ViewHolder里面需要持有什么对象?
    6. 做ViewHolder需要什么条件?
    7. 一个ListView会创建几个convertView以及几个ViewHolder的思考?
    8. getView其实主要是做啥?

    HomeHolder抽取前思考答

    1. 为什么想到去抽取?

      1. 总是要创建ViewHolder
      2. 有重复代码
    2. convertView的作用 ?

      1. 减少view对象的创建
    3. ViewHolder是什么 ?
      1. 就是一个普通的类,成员变量有根布局里面的孩子对象.
    4. ViewHolder的作用 ?
      1. 减少孩子对象的创建,减少findViewById的调用
    5. ViewHolder里面需要持有什么对象?
      1. 持有根布局里面的孩子对象
    6. 做ViewHolder需要什么条件?
      1. 一个类持有根布局里面的孩子对象即可
      2. 其实只要能有根布局就可以了,有根布局就有了对应的孩子.孩子无非就是调用根布局的findViewById初始化即可
    7. 一个ListView会创建几个convertView以及几个ViewHolder的思考
      1. 如果一个屏幕正好显示6个itemView那么会创建6+1个convertView和6+1个ViewHolder
    8. getView其实主要是做啥?
      1. 决定根布局
      2. 得到数据
      3. 填充数据

    相信您已经理解了LitView的加载流程,就看一下第一次演变:

    对ViewHolder抽取,定义为HomeHolder。

    getView()有两层:视图层V、数据源模型层M。其实属于典型的MVC。抽取的过程,也按照MVC模式

    整体的步骤,都详细的注释在代码中了,一目了然:

    public class HomeHolder {
    	//根据getView的设置过程来写这里的代码
    	
    	//getView()过程:
    	//0、初始化ViewHolder
    	//1、初始化布局
    	//2、viewholder设置到根布局的tag。这里根布局给了mHolderView【viewHolder可以绑定tag的条件:该viewHolder要有孩子的布局,或者所有根布局的控件实例】
    	//3、初始化孩子对象,即item布局上的控件实例
    	//4、给孩子(item的布局控件实例)设置数据
    	
    
    	/*******************初始化视图************************/
    	public View mHolderView;
    	
    	//条件:做Holder需要有孩子对象。该viewHolder要有孩子所有根布局的控件实例,该布局只有一个控件,就是展示文本
    	TextView mListTextView;
    
    	private String mData;
    	
    	//0、初始化ViewHolder
    	public HomeHolder(){/**可见,构造方法一调用,初始化了viewholder、初始化了布局、布局控件实例化、此viewHolder绑定到了当前布局的tag中**/
    		mHolderView = initView();
    		//2、viewholder设置到根布局的tag。这里根布局给了mHolderView
    		mHolderView.setTag(this);//this指当前的viewHolder
    	}
    	
    	//1、初始化布局
    	private View initView() {
    		//return UIUtils.inflate(R.layout.homefragment_listview_item);
    		View view = UIUtils.inflate(R.layout.homefragment_listview_item);
    		
    		//3、初始化孩子对象,即item布局上的控件实例
    		mListTextView = (TextView) view.findViewById(R.id.tv_listview_item_text);
    		
    		return view;
    	}
    
    
            /***********************初始化数据***********************/
    	//4、给孩子(item的布局控件实例)设置数据
    	public void setDataAndRefreshHolderView(String data) {
    		//保存数据
    		this.mData = data;
    		//刷新显示,设置数据
    		refreshHolderView(data);
    	}
    
    	private void refreshHolderView(String data) {
    		// 刷新数据显示
    		mListTextView.setText(data);
    	}
    }

    经过抽取ViewHolder后,再看一看getView()代码怎么写:

                    @Override
    		public View getView(int position, View convertView, ViewGroup parent) {
    			/**********************初始化视图 决定根布局***********************/
    			HomeHolder holder = null;
    			if(convertView == null){
    				holder = new HomeHolder();
    			}else{
    				holder = (HomeHolder) convertView.getTag();
    			}
    			
    			/**********************数据刷新显示***********************/
    			
    			holder.setDataAndRefreshHolderView(getItem(position));
    			return holder.mHolderView;
    		}
    		
    	}

    可见,getView()明显的少了好多代码,一下子变得轻松了许多。运行程序,结果一模一样。

    这个时候,明显轻松了很多,但是上边的抽取仅仅是针对HomeHolder的,如果页面很多的话,显然要写很多的Holder类。为了节省Holder的代码,在网上抽取。

    》》》》HomeHolder抽取成BaseHolder。该类的抽取,是基于上边HomeHolder做修改的。把觉得基类取不到的具体东西定义为抽象方法。例如:加载具体的布局、设置刷新具体的数据。在基类里面都无法得知,交给子类实现。并且,由于成了基类,所以具体的设置的数据类型也不清楚,所以设置传递数据的时候,使用泛型

    public abstract class BaseHolder<T> {
    	//根据getView的设置过程来写这里的代码
    	
    		//getView()过程:
    		//0、初始化ViewHolder
    		//1、初始化布局
    		//2、viewholder设置到根布局的tag。这里根布局给了mHolderView【viewHolder可以绑定tag的条件:该viewHolder要有孩子的布局,或者所有根布局的控件实例】
    		//3、初始化孩子对象,即item布局上的控件实例
    		//4、给孩子(item的布局控件实例)设置数据
    		
    		
    		/*******************初始化视图************************/
    		public View mHolderView;
    		
    		//条件:做Holder需要有孩子对象。该viewHolder要有孩子所有根布局的控件实例,该布局只有一个控件,就是展示文本
    		//TextView mListTextView;基类不知道孩子对象
    
    		private T mData;
    		
    		//0、初始化ViewHolder
    		public BaseHolder(){/**可见,构造方法一调用,初始化了viewholder、初始化了布局、布局控件实例化、此viewHolder绑定到了当前布局的tag中**/
    			mHolderView = initHolderView();
    			//2、viewholder设置到根布局的tag。因为能获取item的根布局也可以设置tag
    			mHolderView.setTag(this);//this指当前的viewHolder
    		}
    		
    		/**
    		 * 初始化holderView/根视图
    		 * @call BaseHolder初始化的时候调用
    		 * @return
    		 */
    		//1、初始化布局
    		public abstract View initHolderView();
    
    		
    		
    		/***********************初始化数据***********************/
    		/**
    		 * 设置数据和刷新视图
    		 * @call 需要设置数据和刷新数据的时候调用
    		 * @param data
    		 */
    		//4、给孩子(item的布局控件实例)设置数据
    		public void setDataAndRefreshHolderView(T data) {//只有HomeHolder子类是String类型,其他的孩子不一定也是String,有可能还是bean等。因此使用泛型
    			//保存数据
    			this.mData = data;
    			//刷新显示,设置数据
    			refreshHolderView(data);
    		}
    
    		/**
    		 * 刷新Holder视图
    		 * @call setDataAndRefreshHolderView(T data) 调用的时候就被调用了吧
    		 * 具体的布局我不知道,布局实例更不可能知道。定义为抽象
    		 * @param data
    		 */
    		public abstract void refreshHolderView(T data);
    	}
    此时,HomeHolder继承自BaseHolder。看一下HomeHolder的代码简单了多少吧!

    public class HomeHolder extends BaseHolder<String> {
    
    	private TextView mListTextView;
    
    	@Override
    	public View initHolderView() {
    		View view = UIUtils.inflate(R.layout.homefragment_listview_item);
    		mListTextView = (TextView) view
    				.findViewById(R.id.tv_listview_item_text);
    		return view;
    	}
    
    	@Override
    	public void refreshHolderView(String data) {
    		// 刷新数据显示
    		mListTextView.setText(data);
    	}
    }
    只需要实现两个方法,就搞定了。运行程序,还是跟原来一样的效果。

    那么最后,再回到getView()所在的MyListViewAdapter适配器类。当然记得,它也是有父类的MyBaseAdapter。

    父类里面还有一个getView()没去写代码,最后就去搞一搞父亲的这个方法,让两个基类MyBaseAdapter和BaseHolder打招呼整合一下。把子类的getView()放到基类里面去

    在基类里面做如下修改(与BaseHolder建立了连接)

    public abstract class MyBaseAdapter<T> extends BaseAdapter {
    	
    	private ArrayList<T> mList;
    	
    	public MyBaseAdapter(ArrayList<T> list){
    		this.mList = list;
    	}
    
    	@Override
    	public int getCount() {
    		// TODO Auto-generated method stub
    		return mList.size();
    	}
    
    	@Override
    	public T getItem(int position) {
    		// TODO Auto-generated method stub
    		return mList.get(position);
    	}
    
    	@Override
    	public long getItemId(int position) {
    		// TODO Auto-generated method stub
    		return position;
    	}
    
    	@Override
    	public View getView(int position, View convertView, ViewGroup parent) {
    		/**********************初始化视图 决定根布局***********************/
    		//基类根基类交谈,不能使用具体的实现类
    		BaseHolder<T> holder = null;
    		if(convertView == null){
    			//该holder应该是具体哪个Holder不清楚。如:HomeHolder、AppHolder等。因此定义抽象方法获取
    			holder = getSpecialHolder();
    		}else{
    			holder = (BaseHolder) convertView.getTag();//注意:从缓存里面取tag
    		}
    		
    		/**********************数据刷新显示***********************/
    		
    		holder.setDataAndRefreshHolderView(getItem(position));
    		return holder.mHolderView;
    	}
    
    	/**
    	 * 返回具体的BaseHolder的子类
    	 * @call getView()方法中,如果没有converView的时候被创建
    	 * @return
    	 */
    	public abstract BaseHolder<T> getSpecialHolder();
    
    }

    Adapter的基类抽象方法,且在获取Holder对象时,调用子类具体哪个Holder,只需要在子类中实现这个方法,并且返回具体的哪个Holder就好了。代码如下:

    private class MyListViewAdapter extends MyBaseAdapter<String>{//在这里告知父亲,我继承你我要给你的集合传递什么类型
    
    		//通过构造函数,把孩子的集合传给父亲
    		public MyListViewAdapter(ArrayList<String> list) {
    			super(list);
    		}
    
    		@Override
    		public BaseHolder<String> getSpecialHolder() {
    			// TODO Auto-generated method stub
    			return new HomeHolder();
    		}
    	}
    这个时候,您发现getView方法的代码以及适配器里面的代码少了太多太多了。而且运行结果还是一样的。

    可能仅仅一个Adapter看不出有多强大,如果说有100个类需要适配器的话,那么只需要一个基类,其他的只要继承自基类,每个适配器类里面的方法就上边那几行,很显然,简化了大量的冗余的代码。

    最后再总结一下此时的调用加载流程。

    当ListView想要关联适配器的时候,创建自己的adapter适配器类对象,同时把集合数据源数据传递过去。

    例如此例中的MyListViewAdapter listAdapter = new MyListViewAdapter(mListDatas);这样把mListDatas传给了adapter的基类,是通过带参构造函数的形式传给adapter父类的,他把以前孩子自己要写的getCountgetItemgetItemId方法帮孩子完成,孩子简写三大方法。同时,在ListVIew的item加载布局和数据的时候,getView方法被调用,此时的getView()方法在adapter基类里面呢,对于item的布局的显示和布局实例的数据刷新,都封装在了对应的Holder类里面;先执行holder = getSpecialHolder();方法来看该adapter配套的Holder是谁。马上调用该adapter的实现方法

            @Override
            public BaseHolder<String> getSpecialHolder() {
                // TODO Auto-generated method stub
                return new HomeHolder();
            }

    看到返回的是HomeHolder,在new HomeHolder();的同时BaseHolder的构造也会被加载(子类初始化先初识化父类构造),此时父类的构造函数

               public BaseHolder(){/**可见,构造方法一调用,初始化了viewholder、初始化了布局、布局控件实例化、此viewHolder绑定到了当前布局的tag中**/
                mHolderView = initHolderView();
                //2、viewholder设置到根布局的tag。因为能获取item的根布局也可以设置tag
                mHolderView.setTag(this);//this指当前的viewHolder
            }

    加载了布局、设置了tag。注意此时public abstract View initHolderView();是调用对应子类实现类HomeHolder的具体方法(调用父类抽象实际调用子类实现方法)。

    此时HomeHolder中的
    @Override
        public View initHolderView() {
            View view = UIUtils.inflate(R.layout.homefragment_listview_item);
            mListTextView = (TextView) view
                    .findViewById(R.id.tv_listview_item_text);
            return view;
        }

    被调用,终于看到在哪里初始化布局了!

    这一系列方法走完之后,再回到adapter基类的getView()方法,继续往下执行到holder.setDataAndRefreshHolderView(getItem(position));。同上,先调用holder的
           //4、给孩子(item的布局控件实例)设置数据
            public void setDataAndRefreshHolderView(T data) {//只有HomeHolder子类是String类型,其他的孩子不一定也是String,有可能还是bean等。因此使用泛型
                //保存数据
                this.mData = data;
                //刷新显示,设置数据
                refreshHolderView(data);
            }

     refreshHolderView(data);抽象,调用实现类HomeHolder的实现类方法,完成了数据的设置和刷新。

    到此,此次ListView的优化,以及详细的解析终于结束了。

    9点半开始写博客,现在0:36,搞了3个多小时。由衷佩服自己,看完您记得关注本博客,或者点赞留下包括意见哦。


  • 相关阅读:
    http 协议相关问题
    网卡中断及多队列
    Visual Studio Code 配置C/C++环境
    C++通用框架和库
    命令行的艺术
    NetScaler Logs Collection Guide
    C++性能榨汁机之无锁编程
    Codeforces 839E Mother of Dragons【__builtin_popcount()的使用】
    C/C++中__builtin_popcount()的使用及原理
    Codeforces 839D Winter is here【数学:容斥原理】
  • 原文地址:https://www.cnblogs.com/wanghang/p/6299577.html
Copyright © 2020-2023  润新知