Android应用架构系列——ListView的模板化_android listview通用莫办_安地Andy的博客-程序员秘密

技术标签: RecyclerView  

 Android开发基本都会有ListView,用来加载一个列表数据,如果列表的每一项视图不确定时,如何为每一项加载不同的数据呢?

先说普通的情况,大家都用过ListView的headview吧,可能会和ListView分开处理,还使逻辑更复杂;再有,碰到ListView增加一种类型的数据,如果差别不太大的,大家可能就在itemView的layout里面支持所有的,然后getView里面根据类别控制可见来达到不同的效果了,这也是使逻辑复杂,再碰到改动的需求就要疯了。

其实可以用模板化的方法让ListView支持多种类型的数据,甚至headView也可以作为ListView的一项(在listView里面本质就是如此)。

这里我介绍两种方法,一种是使用ListView,一种是使用RecycleView。

第一种是以前公司使用的,逻辑比较复杂。这里就简单介绍下思路。

ListView的每一个Item都对应一个模板,有一个基本模板类BaseView,每一种模板需要继承BaseView。然后有个大容器类管理模板Id和模板View的关系,大容器去加载数据时,根据模板Id加载出不同模板View,add进ListView中。 除了这些基本的事情,大容器管理类还需要做缓存,上拉下拉等处理。

整个结构最大的优点是模板与使用者完全分离,但逻辑复杂,加载速度偏慢,还有滑动卡顿问题,优化后还是较差。

第二种是现在很多公司使用的。用RecycleView多种ViewHolder。

先贴下使用实例,主要介绍思路,所以并没有单独做出demo,直接源码中找出来的。

 class GroupAdapter extends BasicRecyclerWithTaleAdapter<DiscoverResponse> {
        protected static final int TYPE_GALLERY_BANNER = -2147483645;
        ArrayList<DiscoverBanner> mBanners;
        ArrayList<DiscoverSection> mSections;
        GetGroupDiscoverRequest request;

        public GroupAdapter(OnRecyclerItemClickListener l) {
            super(true, true, l);
            this.mBanners = new ArrayList();
            this.mSections = new ArrayList();
        }

        public int getItemCount() {
            return (Math.max(1, this.mContents.size()) + getHeadPlaceHolderSize()) + getTalePlaceHolderSize();
        }

        public boolean isBanner(int position) {
            int pos = position - getHeadPlaceHolderSize();
            return pos >= 0 && pos < this.mContents.size() && this.mContents.get(pos) != null && (((BaseRecyclerItem) this.mContents.get(pos)).getData() instanceof ArrayList);
        }

        public boolean isCellHeader(int position) {
            int pos = position - getHeadPlaceHolderSize();
            return pos >= 0 && pos < this.mContents.size() && this.mContents.get(pos) != null && (((BaseRecyclerItem) this.mContents.get(pos)).getData() instanceof DiscoverSection);
        }

        public void appendResult(DiscoverResponse response) {
            this.mBanners = ((Discover) response.getContent()).banners;
            this.mSections = ((Discover) response.getContent()).sections;
            if (!(this.mBanners == null || this.mBanners.size() == 0)) {
                addItem(new GroupDiscoveryBannerItem(this.mBanners, true));
            }
            if (this.mSections != null) {
                Iterator it = this.mSections.iterator();
                while (it.hasNext()) {
                    DiscoverSection section = (DiscoverSection) it.next();
                    addItem(new GroupDiscoverySectionItem(section));
                    if (section.groups != null) {
                        int size = section.groups.size();
                        for (int i = 0; i < size; i++) {
                            Group group = (Group) section.groups.get(i);
                            if (i % 3 == 0) {
                                addItem(new GroupDiscoveryGridItem(group, 1));
                            } else if (i % 3 == 2) {
                                addItem(new GroupDiscoveryGridItem(group, 2));
                            } else {
                                addItem(new GroupDiscoveryGridItem(group));
                            }
                        }
                    }
                }
            }
            this.isEnd = true;
            notifyDataSetChanged();
            GroupDiscoverFragment.this.validateAfterLoad();
        }

        public void reload() {
            if (this.mBanners != null) {
                this.mBanners.clear();
            }
            if (this.mSections != null) {
                this.mSections.clear();
            }
            super.reload();
        }

        public void loadMore() {
            if (this.request == null) {
                GroupDiscoverFragment.this.showRefreshView();
                this.request = new GetGroupDiscoverRequest(GroupDiscoverFragment.this.getZhihuSpiceClient());
                GroupDiscoverFragment.this.execute(this.request, new RequestListener<DiscoverResponse>() {
                    public void onRequestFailure(SpiceException pSpiceException) {
                        super.onRequestFailure(pSpiceException);
                        GroupAdapter.this.errorMsg = pSpiceException.getMessage();
                        GroupAdapter.this.isEnd = true;
                        GroupAdapter.this.notifyDataSetChanged();
                        GroupDiscoverFragment.this.validateAfterLoad(1);
                        GroupAdapter.this.request = null;
                    }

                    public void onRequestSuccess(DiscoverResponse pResult) {
                        super.onRequestSuccess(pResult);
                        if (((Discover) pResult.getContent()).isSuccess()) {
                            GroupAdapter.this.isEnd = true;
                            GroupAdapter.this.appendResult(pResult);
                            GroupAdapter.this.notifyDataSetChanged();
                        } else {
                            GroupAdapter.this.errorMsg = ((Discover) pResult.getContent()).getErrorMessage();
                            GroupAdapter.this.isEnd = true;
                            GroupAdapter.this.notifyDataSetChanged();
                            GroupDiscoverFragment.this.validateAfterLoad(User.BADGE_COOL);
                        }
                        GroupAdapter.this.request = null;
                    }
                });
            }
            GroupDiscoverFragment.this.saveCurrentLoadTime();
        }

        public void resetContents() {
            super.resetContents();
        }
    }

这是Adapter的写法,大部分都是加载数据的,跟界面有关的就是addItem()了。再看BasicRecyclerWithTaleAdapter中addItem做了什么。

public abstract class BasicRecyclerWithTaleAdapter<T> extends Adapter<ViewHolder> implements DownScrollable {
    public static final int TYPE_EMPTY_VIEW = -2147483646;
    public static final int TYPE_PLACEHOLDER = Integer.MIN_VALUE;
    public static final int TYPE_TALE_PLACEHOLDER = -2147483647;
    private EmptyInfo emptyInfo;
    public String errorMsg;
    public boolean isEnd;
    protected long lastItemtId;
    protected ArrayList<BaseRecyclerItem> mContents;
    protected boolean mHasHeaderPlaceholder;
    protected boolean mHasTalePlaceHolder;
    private OnRecyclerItemClickListener mOnRecyclerItemClickListener;
    public int mPaging;
    protected int mTalePlaceHolderHeight;

    public static class ViewHolder<T extends BaseRecyclerItem> extends android.support.v7.widget.RecyclerView.ViewHolder {
        private View view;

        public ViewHolder(View v) {
            super(v);
            this.view = findRecyclerItemViewById(v, null);
        }

        public ViewHolder(View v, OnRecyclerItemClickListener l) {
            super(v);
            this.view = findRecyclerItemViewById(v, l);
        }

        public void build(T item) {
            if (this.view instanceof BaseRecyclerItemView) {
                ((BaseRecyclerItemView) this.view).build(item);
            }
        }

        private View findRecyclerItemViewById(View v, OnRecyclerItemClickListener l) {
            this.view = v.findViewById(R.id.recycler_item_view);
            if (this.view == null || !(this.view instanceof BaseRecyclerItemView)) {
                return v;
            }
            ((BaseRecyclerItemView) this.view).setOnItemClickListener(l);
            return this.view;
        }
    }

    class AnonymousClass_1 extends ViewHolder {
        AnonymousClass_1(View v) {
            super(v);
        }
    }

    class AnonymousClass_2 extends ViewHolder {
        AnonymousClass_2(View v) {
            super(v);
        }
    }

    class AnonymousClass_3 extends ViewHolder {
        AnonymousClass_3(View v) {
            super(v);
        }
    }

    public abstract void appendResult(T t);

    public abstract void loadMore();

    public BasicRecyclerWithTaleAdapter() {
        this(false, false, null);
    }

    public BasicRecyclerWithTaleAdapter(OnRecyclerItemClickListener l) {
        this(false, false, l);
    }

    public BasicRecyclerWithTaleAdapter(boolean hasHeader, boolean hasTale) {
        this(hasHeader, hasTale, null);
    }

    public BasicRecyclerWithTaleAdapter(boolean hasHeader, boolean hasTale, OnRecyclerItemClickListener l) {
        this.mHasHeaderPlaceholder = false;
        this.mHasTalePlaceHolder = false;
        this.mTalePlaceHolderHeight = 0;
        this.mContents = new ArrayList();
        this.mPaging = 1;
        this.lastItemtId = 0;
        this.mHasHeaderPlaceholder = hasHeader;
        this.mHasTalePlaceHolder = hasTale;
        this.mOnRecyclerItemClickListener = l;
    }

    public ArrayList<BaseRecyclerItem> getItems() {
        return this.mContents;
    }

    public int getItemCount() {
        int i;
        int i2 = 1;
        int max = Math.max(1, this.mContents.size());
        if (this.mHasTalePlaceHolder) {
            i = 1;
        } else {
            i = 0;
        }
        i += max;
        if (!this.mHasTalePlaceHolder) {
            i2 = 0;
        }
        return i + i2;
    }

    public int getDataCount() {
        return this.mContents.size();
    }

    public BaseRecyclerItem getItem(int pPosition) {
        return pPosition < this.mContents.size() ? (BaseRecyclerItem) this.mContents.get(pPosition) : null;
    }

    public Object getData(int pos) {
        return ((BaseRecyclerItem) this.mContents.get(pos)).getData();
    }

    public int getItemPositionByData(Object o) {
        for (int i = 0; i < this.mContents.size(); i++) {
            if (o.equals(((BaseRecyclerItem) this.mContents.get(i)).getData())) {
                return i;
            }
        }
        return -1;
    }

    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType) {
            case TYPE_PLACEHOLDER /*-2147483648*/:
                return new AnonymousClass_1(LayoutInflater.from(parent.getContext()).inflate(R.layout.material_view_pager_placeholder, parent, false));
            case TYPE_TALE_PLACEHOLDER /*-2147483647*/:
                return new AnonymousClass_2(LayoutInflater.from(parent.getContext()).inflate(R.layout.material_view_pager_tail_placeholder, parent, false));
            case TYPE_EMPTY_VIEW /*-2147483646*/:
                return new AnonymousClass_3(LayoutInflater.from(parent.getContext()).inflate(R.layout.empty_item_view, parent, false));
            default:
                return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false), this.mOnRecyclerItemClickListener);
        }
    }

    public void onBindViewHolder(ViewHolder viewHolder, int position) {
        switch (getItemViewType(position)) {
            case TYPE_PLACEHOLDER /*-2147483648*/:
            case TYPE_TALE_PLACEHOLDER /*-2147483647*/:
                if (position == findLastTaleEmptyViewPosition()) {
                    ((MaterialViewPagerTailView) viewHolder.itemView).setMaterialHeight(this.mTalePlaceHolderHeight + getAdditionalTalePlaceHolderSize());
                }
            case TYPE_EMPTY_VIEW /*-2147483646*/:
                if (this.emptyInfo != null) {
                    View view = viewHolder.itemView;
                    if (view instanceof EmptyItemView) {
                        ((EmptyItemView) view).build(new EmptyItem(this.emptyInfo));
                        view.setVisibility(0);
                        return;
                    }
                    return;
                }
                viewHolder.itemView.setVisibility(ConnectionResult.INTERNAL_ERROR);
            default:
                viewHolder.build((BaseRecyclerItem) getItems().get(position - getHeadPlaceHolderSize()));
        }
    }

    protected int findLastTaleEmptyViewPosition() {
        return getItemCount() - 1;
    }

    public boolean isDataItemType(int position) {
        int type = getItemViewType(position);
        return (type == Integer.MIN_VALUE || type == -2147483647) ? false : true;
    }

    public int getItemViewType(int position) {
        if (position < getHeadPlaceHolderSize()) {
            return TYPE_PLACEHOLDER;
        }
        if (position >= Math.max(1, this.mContents.size()) + getHeadPlaceHolderSize()) {
            return TYPE_TALE_PLACEHOLDER;
        }
        return this.mContents.size() == 0 ? TYPE_EMPTY_VIEW : ((BaseRecyclerItem) getItems().get(position - getHeadPlaceHolderSize())).getLayout();
    }

    public void reload() {
        this.mPaging = 1;
        this.isEnd = false;
        this.lastItemtId = 0;
        resetContents();
        loadMore();
    }

    public long getItemId(int position) {
        return (position < 0 || position >= getItemCount()) ? -1 : (long) position;
    }

    public boolean isEnd() {
        return this.isEnd;
    }

    public void resetContents() {
        if (this.mContents != null) {
            this.mContents.clear();
        }
    }

    public void addItem(BaseRecyclerItem item) {
        this.mContents.add(item);
        notifyItemInserted(getHeadPlaceHolderSize() + this.mContents.size());
    }

    public void addItem(int index, BaseRecyclerItem item, boolean isNotifyAll) {
        this.mContents.add(index, item);
        if (isNotifyAll) {
            notifyDataSetChanged();
        } else {
            notifyItemInserted(getHeadPlaceHolderSize() + index);
        }
    }

    public void addAllItems(int index, ArrayList<BaseRecyclerItem> items, boolean isNotifyAll) {
        this.mContents.addAll(index, items);
        ArrayList<Object> datas = new ArrayList();
        Iterator it = items.iterator();
        while (it.hasNext()) {
            datas.add(((BaseRecyclerItem) it.next()).getData());
        }
        if (isNotifyAll) {
            notifyDataSetChanged();
        } else {
            notifyItemRangeInserted(getHeadPlaceHolderSize() + index, items.size());
        }
    }

    public void addAllItems(int index, ArrayList<BaseRecyclerItem> items) {
        addAllItems(index, items, true);
    }

    public void addAllItems(ArrayList<BaseRecyclerItem> items, boolean isNotifyAll) {
        if (isNotifyAll) {
            this.mContents.addAll(items);
            notifyDataSetChanged();
            return;
        }
        int index = this.mContents.size() + getHeadPlaceHolderSize();
        this.mContents.addAll(items);
        notifyItemRangeInserted(index, items.size());
    }

    public void addAllItems(ArrayList<BaseRecyclerItem> items) {
        addAllItems((ArrayList) items, true);
    }

    public void removeItem(int index) {
        this.mContents.remove(index);
        notifyItemRemoved(getHeadPlaceHolderSize() + index);
    }

    public void removeItem(BaseRecyclerItem item) {
        int index = this.mContents.indexOf(item);
        if (index >= 0) {
            this.mContents.remove(index);
            notifyItemRemoved(getHeadPlaceHolderSize() + index);
        }
    }

    public void removeItemByData(Object o, boolean isNotifyAll) {
        int index = getItemPositionByData(o);
        if (index >= 0) {
            this.mContents.remove(index);
            if (isNotifyAll) {
                notifyDataSetChanged();
            } else {
                notifyItemRemoved(getHeadPlaceHolderSize() + index);
            }
        }
    }

    public void clearAll() {
        this.mContents.clear();
        notifyDataSetChanged();
    }

    public void setTalePlaceHolderHeight(int height) {
        this.mTalePlaceHolderHeight = height;
        notifyItemChanged(findLastTaleEmptyViewPosition());
    }

    public void setEmptyInfo(EmptyInfo info) {
        this.emptyInfo = info;
        notifyDataSetChanged();
    }

    public int getAdditionalTalePlaceHolderSize() {
        return 0;
    }

    public int getTalePlaceHolderSize() {
        return this.mHasTalePlaceHolder ? 1 : 0;
    }

    public int getHeadPlaceHolderSize() {
        return this.mHasHeaderPlaceholder ? 1 : 0;
    }
}
看到里面addItem的实现就是为ArrayList<BaseRecyclerItem> mContents 添加数据,再刷新界面。再看BaseRecyclerItem。

public abstract class BaseRecyclerItem<T> {
    private T data;

    public abstract int getLayout();

    public BaseRecyclerItem(T o) {
        this.data = o;
    }

    public T getData() {
        return this.data;
    }
}
看到里面其实是数据和layout。再看BasicRecyclerWithTaleAdapter中对mContent的使用。在onCreateViewHolder中 return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false), this.mOnRecyclerItemClickListener),这里用到viewType,

    public int getItemViewType(int position) {
        if (position < getHeadPlaceHolderSize()) {
            return TYPE_PLACEHOLDER;
        }
        if (position >= Math.max(1, this.mContents.size()) + getHeadPlaceHolderSize()) {
            return TYPE_TALE_PLACEHOLDER;
        }
        return this.mContents.size() == 0 ? TYPE_EMPTY_VIEW : ((BaseRecyclerItem) getItems().get(position - getHeadPlaceHolderSize())).getLayout();
    }
发现viewType就是BaseRecyclerItem的layout。在看Adapter的onBindViewHolder,关键在这句 viewHolder.build((BaseRecyclerItem) getItems().get(position - getHeadPlaceHolderSize()));通过viewHoder更新view的视图,getItem()就是mContent,ViewHoder我们再看下代码:

 public static class ViewHolder<T extends BaseRecyclerItem> extends android.support.v7.widget.RecyclerView.ViewHolder {
        private View view;

        public ViewHolder(View v) {
            super(v);
            this.view = findRecyclerItemViewById(v, null);
        }

        public ViewHolder(View v, OnRecyclerItemClickListener l) {
            super(v);
            this.view = findRecyclerItemViewById(v, l);
        }

        public void build(T item) {
            if (this.view instanceof BaseRecyclerItemView) {
                ((BaseRecyclerItemView) this.view).build(item);
            }
        }

        private View findRecyclerItemViewById(View v, OnRecyclerItemClickListener l) {
            this.view = v.findViewById(R.id.recycler_item_view);
            if (this.view == null || !(this.view instanceof BaseRecyclerItemView)) {
                return v;
            }
            ((BaseRecyclerItemView) this.view).setOnItemClickListener(l);
            return this.view;
        }
    }
发现bulid最后是通过BaseRecyclerItemView的build方法构造的,BaseRecyclerItemView就只那个layout,所以layout必须继承这个,在xml里看到就是继承了这个的自定义View。看下BaseRecyclerItemView

public interface BaseRecyclerItemView<T> extends OnClickListener, OnLongClickListener {
    void build(T t);

    T getItem();

    void setOnItemClickListener(OnRecyclerItemClickListener onRecyclerItemClickListener);
}
就是一个接口,这里的T就对应BaseRecyclerItem,有build方法,getItem还有点击的监听,因为RecycleView没有Item点击事件,只能自己写。


用RecycleView实现多种样式在一起还是较为容易的,不过写出一个良好的通用的框架就不那么容易了。这里通过BaseRecyclerItem,BaseRecyclerItemView,ViewHoder,整个结构就很清晰了,再通过getItemViewType区分列表,再创建ViewHoder和绑定ViewHoder时处理,整个就很简单了。使用时只需要去对item数据操作就行了,主要是addItem,注意item数据要继承BaseRecyclerItem,BaseRecyclerItem的layout要继承BaseRecyclerItemView。

熟悉结构后就可以根据自己项目需要自己打造一个通用的可包含不同ItemView的adpater了。



版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/mzm489321926/article/details/48490657

智能推荐

linux点亮硬盘locat,Linux中常用的查询指令(which、whereis、find、locatae)_南城北忆的博客-程序员秘密

我们经常在linux要查找某个文件,但不知道放在哪里了,可以使用下面的一些命令来搜索:Which 查看可执行文件的位置。whereis查看文件的位置。locate 配合数据库查看文件位置。Find 实际搜寻硬盘查询文件名称whichwhich命令的作用是,在PATH变量指定的路径中,搜索某个系统命令的位置,并且返回第一个搜索结果。也就是说,使用which 命令,就可以看到某个系统命令...

for of 和 for in 的区别_forin和forof区别_搞前端的半夏的博客-程序员秘密

一般用于遍历对象的可枚举属性。以及对象从构造函数原型中继承的属性。对于每个不同的属性,语句都会被执行。 不建议使用for in 遍历数组,因为输出的顺序是不固定的。 如果迭代的对象的变量值是null或者undefined, for in不执行循环体,建议在使用for in循环之前,先检查该对象的值是不是null或者undefined for of...

C#LeetCode刷题之#819-最常见的单词(Most Common Word)_无痕的过往的博客-程序员秘密

问题给定一个段落 (paragraph) 和一个禁用单词列表 (banned)。返回出现次数最多,同时不在禁用列表中的单词。题目保证至少有一个词不在禁用列表中,而且答案唯一。禁用列表中的单词用小写字母表示,不含标点符号。段落中的单词不区分大小写。答案都是小写字母。输入:paragraph = &quot;Bob hit a ball, the hit BALL flew far after ...

XMind ZEN 2020 v10.1.3安装和下载说明_COCO56(徐可可)的博客-程序员秘密

本文已迁移至:https://www.cnblogs.com/coco56/p/13802334.html

中山大学电子地图收藏_weixin_30263073的博客-程序员秘密

中山大学电子地图收藏-高清版转载于:https://www.cnblogs.com/absolute8511/archive/2008/10/11/1649623.html

基于深度学习的短视频内容分析简介_leng_yan的博客-程序员秘密

基于图像的目标检测和语义分割已进入后半程,基于视频的内容分析正在逐渐成为主流,理由很简单视频可以拿到更多有价值可分析的信息。一个视频在它的生命周期内可能涉及到许多处理技术。从摄像头捕获开始,然后是编解码,这个阶段还涉及到传输、存储,然后是编辑与处理,比如剪辑、背景分割。随后是信息提取,包括物体识别、场景检测、人物分析、行为识别、主题提取、事件检测。以上步骤完成后,我们拿到了海量视频,还可做视频的...

随便推点

Android(01)-进程间的通信机制_其子昱舟的博客-程序员秘密

Linux系统将自身划分为两部分,一部分为核心软件,即是kernel,也称作内核空间,另一部分为普通应用程序,这部分称为用户空间。用户空间中的代码运行在较低的特权级别上,只能看到允许它们使用的部分系统资源,并且不能使用某些特定的系统功能,也不能直接访问内核空间和硬件设备,以及其他一些具体的使用限制。

JVM调优总结(精)_ajian005的博客-程序员秘密

转自:http://pengjiaheng.javaeye.com/blog/518622搜索JVM调优发现的好文,转载共赏 JVM调优总结-序    几年前写过一篇关于JVM调优的文章,前段时间拿出来看了看,又添加了一些东西。突然发现,基础真的很重要。学习的过程是一个由表及里,再由里及表的过程。呵呵,所谓的“温故而知新”。而真正能走完这个轮回的人,也就能称

【华人学者风采】刘小平 中山大学_AMiner学术搜索和科技情报挖掘的博客-程序员秘密

【华人学者风采】刘小平,中山大学地理科学与规划学院教授。主要从事地理模拟、空间智能及优化决策方面的研究。是中国地理信息系统协会理论与方法专业委员会委员,中国海外地理信息科学协会(CPGIS)委员。任国际著名GIS刊物International Journal of Geographical Information Science、Landscape and Urban Planning审稿人,国内核心刊物“地理学报”、“测绘学报”等审稿人。2020年发表论文:23发表论文:242论文引用数:4046

unity中EasyTouch插件的基本使用_黑火石科技的博客-程序员秘密

1关于EasyTouch的两种写法在4.x版本中,需要在Hierarchy中创建EasyTouch,在5版本中不需要,建议都创建。①4.x版本:using System.Collections;using System.Collections.Generic;using HedgehogTeam.EasyTouch;using UnityEngine;public class EST...

mysql 主键自增_会飞的程序员zjm的博客-程序员秘密

ERROR 1049 (42000): Unknown database 'user'mysql> alter table user modify Id integer auto_increment;Query OK, 0 rows affected (0.06 sec)Records: 0  Duplicates: 0  Warnings: 0mysql> alter t

无语!_我真的很无语 王自在_9期王莎莎的博客-程序员秘密

真的不知道写什么了!就不吐口水了!祭奠自己这七天虚无的生命吧!开创未来!

推荐文章

热门文章

相关标签