美文网首页从0到1开发一款APP直播APP计算机杂谈
【从 0 开始开发一款直播 APP】3.1 高层封装之 Adap

【从 0 开始开发一款直播 APP】3.1 高层封装之 Adap

作者: 菜鸟窝 | 来源:发表于2017-03-31 09:50 被阅读227次

    本文为菜鸟窝作者蒋志碧的连载。“从 0 开始开发一款直播 APP ”系列来聊聊时下最火的直播 APP,如何完整的实现一个类"腾讯直播"的商业化项目
    视频地址:http://www.cniao5.com/course/10121


    【从 0 开始开发一款直播 APP】3.1 高层封装之 Adapter — ListView & GridView
    【从 0 开始开发一款直播 APP】3.2 高层封装之 Adapter — RecyclerView 实现单布局展示
    【从 0 开始开发一款直播 APP】3.3 高层封装之 Adapter -- RecyclerView 实现多条目展示
    【从 0 开始开发一款直播 APP】3.4 高层封装之 Adapter -- RecyclerView 优雅的添加 Header、Footer


    一、前言

    我们在开发中写得最多的就是对 ListView、GridView 的适配器,我们熟悉得不能再熟悉,Adapter 一般都是继承 BaseAdapter 并复写其中的方法,getView 里面使用 ViewHolder 绑定控件。

    二、常见示例

    public class TraditionAdapter extends BaseAdapter {
    
        private Context mContext;
        private List<Item> mItems;
    
        public TraditionAdapter(Context context, List<Item> items) {
            mContext = context;
            mItems = items;
        }
    
        @Override
        public int getCount() {
            return mItems.size();
        }
    
        @Override
        public Object getItem(int position) {
            return mItems.get(position);
        }
    
        @Override
        public long getItemId(int position) {
            return position;
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
          
            if (holder == null){
                convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item,parent,false);
                holder = new ViewHolder();
                
                holder.titleText = (TextView) convertView.findViewById(R.id.tv1);
                holder.descText = (TextView) convertView.findViewById(R.id.tv2);
                holder.img = (ImageView) convertView.findViewById(R.id.img);
              
                convertView.setTag(holder);
            }else {
                holder = (ViewHolder) convertView.getTag();
            }
          
            Item item = mItems.get(position);
            holder.titleText.setText(item.getTv1());
            holder.descText.setText(item.getTv2());
            holder.img.setImageResource(item.getRes());
            return convertView;
        }
    
        class ViewHolder{
            TextView titleText;
            TextView descText;
            ImageView img;
        }
    }
    

    这种重复的代码大家应该都写了很多遍了,TraditionAdapter 继承 BaseAdapter,getView 使用 ViewHolder 绑定控件。而通常每个 ListView 布局都会有一个 Adapter,Adapter 中也会有一个对应的 ViewHolder。由此看来,要减少代码量就要将 Adapter、ViewHolder 封装成通用类。

    想要 AdapterViewHolder 通用,目前需要以下几步:

    2.1、数据是活的,TraditionAdapter 中的 Item 类应作为范型传入 Adapter

    2.2、getCount()、getItem()、getItemId() 这三个方法一直都不变,将其封装

    2.3、抽取 ViewHolder 类,解决控件绑定问题

    2.4、Adapter 类封装,实现与 ViewHolder 神匹配

    知道了大概步骤,先来封装 ViewHolder 吧。

    三、ViewHodler 类封装

    ViewHolder 通过 convertView.setTag() 与 convertView 进行绑定,然后当 convertView复用时,直接利用 convertView.getTag() 获取的ViewHolder 的 convertView 布局中的控件,省去了findViewById() 的时间

    实际上每个 convertView 会绑定一个 ViewHolder 对象,这个 ViewHolder 主要用于帮 convertView 存储布局中的控件。

    那么我们只要写出一个通用的 ViewHolder,对于任意的 convertView,提供一个对象让其 setTag() 即可

    ViewHodler 类封装,需要做以下几步:

    1、返回 ViewHolder

    2、获取控件

    3、设置控件

    4、convertView 的复用

    每个布局有不同的控件,每个控件有自己的 id 和数据。存储这些控件需要使用 SparseArray。

    SparseArray 简介

    SparseArray 是 android 提供的新的存储键值对的 API,相比 HashMap,SparseArray 性能更好。原因如下:

    1、SparseArray 更加优化内存

    2、key 的类型是 int 型,免去装箱操作,时间性能优于 HashMap

    3、SparseArray 结构简单,使用一位数组存储 key 和 value

    开始 ViewHolder 的封装。

    3.1、创建 BaseViewHolder 类,需要的变量大概有 View mConvertView 复用机制,SparseArray<View> mViews 存储所有控件,int mPosition 记录 View 位置信息

    public class BaseViewHolder{
        //复用的View
        private final View mConvertView;
        //所有控件集合
        private SparseArray<View> mViews;
        //记录位置信息
        private int mPosition;
    
        /**
         * BaseViewHolder 构造函数
         * @param context 上下文对象
         * @param parent 父类容器
         * @param layoutId 布局 Id
         * @param position item位置信息
         */
        public BaseViewHolder(Context context, ViewGroup parent, int layoutId, int position) {
            this.mPosition = position;
            this.mViews = new SparseArray<View>();
            mConvertView = LayoutInflater.from(context).inflate(layoutId, parent, false);
            //设置 tag
            mConvertView.setTag(this);
        }
      
       /**
         * 通过 viewId 获取控件
         * @param viewId 控件id
         * @param <T> View 子类
         * @return 返回 View
         */
        public <T extends View> T getView(int viewId) {
            View view = mViews.get(viewId);
            if (view == null) {
                view = mConvertView.findViewById(viewId);
                mViews.put(viewId, view);
            }
            return (T) view;
        }
    
        //返回 ViewHolder
        public static BaseViewHolder getViewHolder(Context context, View convertView, ViewGroup parent,int layoutId, int position) {
            
            //BaseViewHolder 为空,创建新的,否则返回已存在的
            if (convertView == null) {
                return new BaseViewHolder(context, parent, layoutId, position);
            } else {
                BaseViewHolder holder = (BaseViewHolder) convertView.getTag();
                //更新 item 位置信息
                holder.mPosition = position;
                return holder;
            }
        }
        
       //获取 convertView
        public View getConvertView() {
            return mConvertView;
        }
    }
    

    3.2、设置控件以及监听(采用链式编程方法)

    /**
     * 设置 TextView 的值
     * @param viewId
     * @param text
     * @return
     */
    public BaseViewHolder setText(int viewId, String text)
    {
        TextView tv = getView(viewId);
        tv.setText(text);
        return this;
    }
    
    /**
     * 设置TImageView的值
     * @param viewId
     * @param resId
     * @return
     */
    public BaseViewHolder setImageResource(int viewId, int resId)
    {
        ImageView view = getView(viewId);
        view.setImageResource(resId);
        return this;
    }
    
    /**
     * 设置是否可见
     * @param viewId
     * @param visible
     * @return
     */
    public BaseViewHolder setVisible(int viewId, boolean visible)
    {
        View view = getView(viewId);
        view.setVisibility(visible ? View.VISIBLE : View.GONE);
        return this;
    }
    
    /**
     * 设置tag
     * @param viewId
     * @param tag
     * @return
     */
    public BaseViewHolder setTag(int viewId, Object tag)
    {
        View view = getView(viewId);
        view.setTag(tag);
        return this;
    }
    
    public BaseViewHolder setTag(int viewId, int key, Object tag)
    {
        View view = getView(viewId);
        view.setTag(key, tag);
        return this;
    }
    /**
     * 设置 Checkable
     * @param viewId
     * @param checked
     * @return
     */
    public BaseViewHolder setChecked(int viewId, boolean checked)
    {
        Checkable view = (Checkable) getView(viewId);
        view.setChecked(checked);
        return this;
    }
    
    /**
     * 点击事件
     */
    public BaseViewHolder setOnClickListener(int viewId,View.OnClickListener listener)
    {
        View view = getView(viewId);
        view.setOnClickListener(listener);
        return this;
    }
    
    /**
     * 触摸事件
     */
    public BaseViewHolder setOnTouchListener(int viewId,View.OnTouchListener listener)
    {
        View view = getView(viewId);
        view.setOnTouchListener(listener);
        return this;
    }
    
    /**
     * 长按事件
     */
    public BaseViewHolder setOnLongClickListener(int viewId,View.OnLongClickListener listener)
    {
        View view = getView(viewId);
        view.setOnLongClickListener(listener);
        return this;
    }z
    

    四、Adapter 类封装

    Adapter 封装需要以下几步:

    1、上例一直存在的 Item 类将作为范型传入Adapter

    2、封装 getCount()、getItem()、getItemId() 三个方法

    3、封装 getView()

    4、绑定 ViewHolder

    创建 BaseAdapter 继承 android.widget.BaseAdapter 类,重写方法及构造函数,根据需求,需要的参数有 List<T> mDatas 数据源,Context mContext 上下文对象,int mLayoutId 布局。

    public abstract class BaseAdapter<T> extends android.widget.BaseAdapter {
        protected List<T> mDatas;
        protected Context mContext;
        protected int mLayoutId;
    
        public BaseAdapter(List<T> datas, Context context, int layoutId) {
            mDatas = datas;
            mContext = context;
            this.mLayoutId = layoutId;
        }
    
        @Override
        public int getCount() {
            return mDatas == null ? 0 : mDatas.size();
        }
    
        @Override
        public T getItem(int position) {
            return mDatas == null ? null : mDatas.get(position);
        }
    
        @Override
        public long getItemId(int position) {
            return position;
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            BaseViewHolder holder = BaseViewHolder.getViewHolder(mContext, convertView, parent, mLayoutId, position);
    
            T t = mDatas.get(position);
            
            //抽象出 ViewHolder 让用户去实现填充数据
            bindData(holder, t);
    
            return holder.getConvertView();
        }
    
        public abstract void bindData(BaseViewHolder holder, T t);
    }
    

    五、Adapter 和 ViewHolder 的使用

    **5.1、 创建 SimpleAdapter.java 继承 BaseAdapter 类,传入 Item 数据源,根据布局找到需要的控件并填充数据,添加监听等。 **

    public class SimpleAdapter extends BaseAdapter<Item> {
    
        public SimpleAdapter(List datas, Context context) {
            super(datas, context, R.layout.list_item);
        }
    
        @Override
        public void bindData(BaseViewHolder holder, final Item item) {
    
            holder.setText(R.id.tv1,item.getTv1())
                    .setText(R.id.tv2,item.getTv2())
                    .setImageResource(R.id.img,item.getRes())
                    .setOnClickListener(R.id.tv2, new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                           Toast.makeText(mContext,item.getTv2(),Toast.LENGTH_SHORT).show();
                        }
                    });
        }
    }
    

    5.2、由于加了点击事件,运行起来点击事件无效果,并不是因为代码有问题,而是焦点抢占原因,因此需要在布局文件 activity_adapter.xml 中设置是否可点击

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:orientation="horizontal"
                  android:clickable="false"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content">
    
        <ImageView
            android:padding="5dp"
            android:layout_marginRight="40dp"
            android:id="@+id/img"
            android:layout_width="100dp"
            android:layout_height="100dp"/>
    
        <LinearLayout
            android:clickable="false"
            android:padding="10dp"
            android:orientation="vertical"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
            <TextView
                android:paddingTop="10dp"
                android:id="@+id/tv1"
                android:layout_width="wrap_content"
                android:layout_height="40dp"/>
    
            <TextView
                android:id="@+id/tv2"
                android:clickable="true"
                android:layout_width="wrap_content"
                android:layout_height="40dp"/>
        </LinearLayout>
    </LinearLayout>
    

    5.3、 AdapterActivity.java

    public class AdapterActivity extends BaseActivity {
    
        private SimpleAdapter mAdapter;
        private ListView mListView;
        private ArrayList<Item> Datas;
    
        @Override
        protected void setToolbar() {
        }
    
        @Override
        protected void setListener() {
        }
    
        //添加数据
        @Override
        protected void initData() {
            Datas = new ArrayList<>();
            for (int i = 1; i <= 30; i++) {
                Item item = new Item(R.drawable.tab_publish_normal, " get 新技能" + i, "拣到漂亮妹子 "+i+" 枚,在大街上");
                Datas.add(item);
            }
    
            mAdapter = new SimpleAdapter(Datas,this);
            mListView.setAdapter(mAdapter);
        }
    
        @Override
        protected void initView() {
              mListView = obtainView(R.id.list);
        }
    
        @Override
        protected int getLayoutId() {
            return R.layout.activity_adapter;
        }
    }
    

    六、运行效果展示

    6.1、ListView

    6.2、GridView

    七、总结

    Adapter 传统写法熟练,对 ConverView 复用了解

    ViewHolder 的使用熟悉,尤其是控件绑定

    了解 SparseArray 的优缺点,对其基本使用熟悉

    笔者只是传达了封装的思想,能力有限,封装不到位的地方还望大家留言指正

    更多内容,请关注菜鸟窝(微信公众号ID: cniao5),程序猿的在线学习平台。转载请注明出处,本文出自菜鸟窝,原文链接http://www.cniao5.com/forum/thread/2ac69d820f0611e790dc00163e0230fa

    相关文章

      网友评论

      本文标题:【从 0 开始开发一款直播 APP】3.1 高层封装之 Adap

      本文链接:https://www.haomeiwen.com/subject/knvmottx.html