美文网首页AndroidAndroid研究院编程之美
关于android中ListView的Adapter如何设计能通

关于android中ListView的Adapter如何设计能通

作者: a帆仔 | 来源:发表于2016-05-19 10:05 被阅读2375次

    我们都知道,在安卓中使用ListView显示多条数据的时候,必须要用一个适配器作为Data和View的桥梁,这种设计非常好, 能很简单就把ui和data分离开来,为ui的复用和维护代码提供方便。
    但是每次写一个适配器,都要实现一大堆的重复逻辑,下面是一个常规的实现:
    foo_item.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"    
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="@dimen/activity_horizontal_margin"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical">   
     <TextView
    android:id="@+id/txtTitle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:text="今天好天气" />
    <TextView
    android:id="@+id/txtContent"
    android:layout_marginTop="10dp"
    tools:text="我是一名android开发者"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />
    </LinearLayout>
    

    适配器:

    public class FooNormalAdapter extends BaseAdapter {
    
        private List<FooBean> datas = new ArrayList<>();
        private Context _context;
    
        public FooNormalAdapter(Context context) {
            this._context = context;
        }
    
        public Context getContext() {
            return _context;
        }
    
        public void setDataSource(List<FooBean> fooBeens) {
            setDataSource(fooBeens,true);
        }
    
        public void setDataSource(List<FooBean> fooBeens,boolean isClear) {
            if(isClear) this.datas.clear();
            this.datas.addAll(fooBeens);
            notifyDataSetChanged();
        }
    
        @Override
        public int getCount() {
            return datas.size();
        }
    
        @Override
        public Object getItem(int position) {
            return datas.get(position);
        }
    
        @Override
        public long getItemId(int position) {
            return position;
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            FooViewHolder viewHolder = null;
            if (convertView == null) {
                convertView = View.inflate(getContext(), R.layout.foo_item, null);
                convertView.setTag(new FooViewHolder(convertView));
            } else {
                viewHolder = (FooViewHolder) convertView.getTag();
            }
            FooBean fb = (FooBean) getItem(position);
            viewHolder.txtTitle.setText(fb.getTitle());
            viewHolder.txtContent.setText(fb.getContent());
            return convertView;
        }
    
        public static class FooViewHolder {
            private TextView txtTitle;
            private TextView txtContent;
    
            public FooViewHolder(View convertView) {
                this.txtTitle = (TextView) convertView.findViewById(R.id.txtTitle);
                this.txtContent = (TextView) convertView.findViewById(R.id.txtContent);
            }
        }
    }
    

    嗯,看起来没有什么问题,但是如果有十个adapter,就要写十次这种无意义的代码,我们不能干体力活啊~怎么办?
    先分析一下adapter需要哪些元素:
    1.首先要inflateView就必须用到Context.
    2.需要一个数组来存储用于显示的数据源
    3.需要一个viewholder来优化程序性能
    4.可能有不同的viewType
    分析完这个,代码随之而来:

    public abstract class LBaseAdapter<E, V extends LBaseAdapter.BaseViewHolder> extends BaseAdapter {
    
        private Context context;
        private List<E> dataSource = new ArrayList<>(); //初始化一个防止getCount()空指针
    
        public LBaseAdapter(Context context) {
            this.context = context;
        }
    
        public Context getContext() {
            return context;
        }
    
        //替换原有数据源
        public void setDataSource(List<E> dataSource) {
            setDataSource(dataSource,true);
        }
    
        //如果isClear==true,则替换原有数据源,否则加到数据源后面
        public void setDataSource(List<E> dataSource, boolean isClear) {
            if (isClear) this.dataSource.clear();
            this.dataSource = dataSource;
            notifyDataSetChanged();
        }
    
        //只加一个数据
        public void addData(E data) {
            this.dataSource.add(data);
            notifyDataSetChanged();
        }
    
        //通过下标移除一条数据
        public void removeData(int position) {
            this.dataSource.remove(position);
            notifyDataSetChanged();
        }
    
        //通过对象移除一条数据
        public void removeData(E data) {
            this.dataSource.remove(data);
            notifyDataSetChanged();
        }
    
    
        @Override
        public int getCount() {
            return this.dataSource.size();
        }
    
    
        @Override
        public E getItem(int position) {
            return this.dataSource.get(position);
        }
    
    
        @Override
        public long getItemId(int position) {
            return position;
        }
    
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            V viewHolder = null;
            if (convertView == null) {
                viewHolder = createViewHolder(position, parent);
                if (viewHolder == null || viewHolder.getRootView() == null) {
                    throw new NullPointerException("createViewHolder不能返回null或view为null的实例");
                }
                convertView = viewHolder.getRootView();
                convertView.setTag(viewHolder);
            }else{
                viewHolder = (V) convertView.getTag();
            }
            //给当前复用的holder一个正确的position
            viewHolder.setPosition(position);
            bindViewHolder(viewHolder,position,getItem(position));
            return viewHolder.getRootView();
        }
    
        protected abstract V createViewHolder(int position, ViewGroup parent);
    
        protected abstract void bindViewHolder(V holder,int position, E data);
    
        public static class BaseViewHolder {
            private View rootView;
            private SparseArray<View> viewCache = new SparseArray<>();
            private int position = -1;
    
            public View getRootView() {
                return rootView;
            }
    
            void setPosition(int position) {
                this.position = position;
            }
    
            public int getPosition() {
                return position;
            }
    
            public BaseViewHolder(View rootView) {
                this.rootView = rootView;
            }
    
            public <R> R getView(@IdRes int viewID) {
                View cachedView = viewCache.get(viewID);
                if(null == cachedView) {
                    cachedView = rootView.findViewById(viewID);
                    viewCache.put(viewID, cachedView);
                }
                return (R) cachedView;
            }
        }
    }
    

    1.我加一个构造函数,强制传context,并提供getContext()方法
    2.加一个泛型E,允许子类提供随意实体类型
    3.加一个BaseViewHolder,并提供一些常用方法
    4.重写getView(),在父类里面把复用逻辑搞定,并提供两个抽象方法用于让子类提供viewholder,和绑定具体数据,嗯,我是仿着RecyclerView来的。:)
    ok,这个通用的父类怎么使用呢?
    代码说话:

    public class FooSuperAdapter extends LBaseAdapter<FooBean, LBaseAdapter.BaseViewHolder> {
    
        public FooSuperAdapter(Context context) {
            super(context);
        }
    
        @Override
        protected BaseViewHolder createViewHolder(int position, ViewGroup parent) {
            return new BaseViewHolder(View.inflate(getContext(), R.layout.foo_item,null));
        }
    
        @Override
        protected void bindViewHolder(BaseViewHolder holder, int position, FooBean data) {
            TextView txtTitle = holder.getView(R.id.txtTitle);
            TextView txtContent = holder.getView(R.id.txtContent);
    
            txtTitle.setText(data.getTitle());
            txtContent.setText(data.getContent());
        }
    }
    

    之前的一大堆东西,现在都不用关心了, 只管设置itemview,和绑定数据就好了,是不是好看多了呢?
    通用adapter用于了泛型,如果不了解可以留言,我将出一个泛型的专题来讨论希望我的博客能帮到你 :)

    相关文章

      网友评论

      • ee6c7c025709:底层分装使用泛型,具体写法参考了 RecyclerView.Adapter 写法,挺不错。不过,可以不用在构造器中传 Context:inflate 用 ViewGroup 的,获取 Resources 用 ApplicationContext;
        a帆仔: @torchmu 嗯嗯,是的,感谢提出改进意见
      • 红颜疯子:有个问题啊,在bindviewholder里面,如果item的控件比较多,那样getview也是很麻烦的,怎么才能和butterknife这样的控件注入插件配合使用呢?
        a帆仔:@红颜疯子 比如你自定义了一个
        public class ButterknifeBasedViewHolder extends LBaseAdapter.BaseViewHolder {
        public ButterknifeBasedViewHolder(View view) {
        ButterKnife.inject(this,view);
        }
        }

        public class FooSuperAdapter extends LBaseAdapter<FooBean, FooSuperAdapter.YouViewHolder> {

        //把你自定义的viewholder写到LBaseAdapter的第二个泛型参数上,这样生成的createViewholder,bindviewholder等方法就是你自定义的viewholder了

        public static class YouViewHolder extends ButterknifeBasedViewHolder{

        @InjectView(R.id.txt) private TextView txt;

        public YouViewHolder(View view){
        super(view);
        }
        }
        }
        红颜疯子:@a帆仔 有点不明白啊,那怎么在bindviewholder里面用呢?
        a帆仔: @红颜疯子 可以的,在写一个viewholder继承自这个baseviewholder,在构造函数里面Butterknife.inject(this,view),就行了
      • xandroid:哥写的比这好用多了
        寒涵:@xandroid 同求代码! :grin:
        a帆仔:@xandroid 贴出来学习下😊
      • 宝塔山上的猫:学习到了~~~不过想了想,似乎和前几天看到的设计模式中有提及这些问题
      • LulPerer:好东西,学习了!
        a帆仔:@d52159876ee1 感谢😊
      • 妙法莲花1234:思路清晰,代码风骚
        a帆仔:@追风917 😌感谢支持,

      本文标题:关于android中ListView的Adapter如何设计能通

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