RecyclerView多功能集合适配器:SuperAdapter

引言

从上学到工作,一晃搞Android也已经有几年了,用的最多控件不外乎就那么几个,其中列表控件用起来相对来说比较繁琐,尤其是出了RecyclerView之后。前段时间突发奇想做一个通用的适配器这样就不用每次写重复的东西了那多爽啊!这里还要好好感谢一下网上那些技术博客大神们,很多东西都是借鉴他们的思想。

为什么只封装适配器呢?
虽说把RecyclerView一起封装就能做出更多好用的功能,用户体验也会更好,但可能是历史遗留阴影,以前用别人的开源控件时总容易出点问题。现在这样就是一个单纯的适配器,不用考虑列表的布局是线性的还是网格的等类似问题。与界面无关,只干干净净的定义了逻辑规则,这种感觉太爽了~

最后,GitHub 上项目地址传送门:SuperAdapter

SuperAdapter是什么

SuperAdapter 是对RecyclerView.Adapter进行封装并将许多常用功能集成之中的一个Android库。你不需要为每个列表单独去写ViewHolder后再声明控件,当构建不需要重用的列表时你甚至不需要单独创建类去继承RecyclerView.Adapter定制适配器,只要在ActivityFragment中简单写下几行代码即可实现,有效的减化了复写代码的数量。

SuperAdapter目前有哪些功能

  • 快速绑定单一布局列表
  • 简化多类型布局列表
  • 列表点击事件
  • 分页显示数据
  • 自定义列表唯一顶部Header
  • 自定义列表底部Footer
  • 上拉自动加载更多数据
  • 添加空数据提示视图

下载SuperAdapter

你需要在项目的根 build.gradle 加入如下JitPack仓库链接:

allprojects {
    repositories {
        ...     
        maven { url 'https://jitpack.io' }      
    }
}

着在你的需要依赖的Module的build.gradle加入依赖:

compile 'com.github.JesseWuuu:SuperAdapter:x.y.z'

注意:上面依赖中的 x.y.z 为版本号,目前最新的版本号为 -> 0.2.0

关于开源

如果你只是想了解如何使用它可以跳过本节。

在大学期间懵懵懂懂的听老师说了一大堆开源的意义,虽然现在也还是不太能领会其中的精神,但是这几年一路走来看过不少大神们的开源代码,也算是在其中获得不少的好处。

虽说这个工具只能算是个小玩具级别,但我还是想将它分享给志同道合的朋友们,源码中核心部分我都认真的写全了注释,希望能有同样对这个小工具感兴趣的朋友和我一起来继续完善它,要是能 Star 一下项目就更好啦哈哈哈~

接下来我将继续为这个小工具添加新的实用功能,当然如果使用过程中有问题可以添加到 Issues 中,我会经常看的。

GitHub地址传送门:SuperAdapter

定义与声明

如果你的列表只出现在了一个布局中并不会重复用使用,那么使用SuperAdapter时就不需要您单独创建类继承父类来定制化适配器,但是还是需要遵守一项规则:在ActivityFragment中声明SuperAdapter属性的同时需要声明您的列表数据源类型:

List<DataEntity> mData;
SuperAdapter<DataEntity> mAdapter; 

如果你的列表会在多处重复使用,这时你需要将 SuperAdapter 封装成自定义的 Adapter,下面会有专门一节讲封装 SuperAdapter 需要注意的事项,此处先不提。

绑定单一布局的列表

绑定单一布局列表的方法非常简单,只需要在SuperAdapter的构造函数中添加布局文件的layoutId与数据源即可。

SuperAdapter<DataEntity> adapter = new SuperAdapter<DataEntity>(R.layout.view_list_item_1){

    @Override
    public void bindView(ViewHolder itemView, DataEntity data,int position) {
        // 此处写绑定itemview中的控件逻辑
        itemView.<TextView>getView(R.id.text_1).setText(data.getTitle());
        Button button = holder.<Button>getView(R.id.button);
        button.setEnabled(entity.IsEnabled());   
    }               
};

我们需要在方法 void bindView(ViewHolder itemView, DataEntity data,int position) 中绑定列表每个条目中的控件并进行逻辑处理。其中方法参数ViewHolder itemView 为自定义 ViewHolder

通用ViewHolder

为方便管理,SuperAdapter 中持有的 ViewHolder 均为一个通用的自定义 ViewHolder ,其中它对外提供了一个 <T extends View>T getView(int viewId) 方法来代替 View findViewById(int ViewId)获取布局中的控件,目的是简化绑定控件方法以及对每个条目中的子 view 做了简单的缓存,具体使用方法上述代码段中有体现。

绑定多种布局的列表

绑定多种类型布局文件需要构造器MultiItemViewBuilder<T>来辅助适配器确定每个位置调用对应的布局文件。同理,声明属性同时需要声明数据源类型:

MultiItemViewBuilder<DataEntity> multiItemViewBuilder;

MultiItemViewBuilder需要您实现两个方法:

  • int getItemType(int position,T data),通过参数中的位置和数据源来判断返回ItemView类型,ItemView类型的值需自定义。
  • int getLayoutId(int type),通过判断itemType返回对应的布局文件id。
MultiItemViewBuilder<DataEntity> multiItemViewBuilder = MultiItemViewBuilder<TestEntity>() {
        @Override
        public int getLayoutId(int type) {
            if (type == 0){
                return R.layout.view_item_normal;
            }
            return R.layout.view_item_other;
        }

        @Override
        public int getItemType(int position, DataEntity data) {
                
            if(entity.isTest()){
                return 0;
            }else{
                return 1;
            }

        }
 };

最后,将MultiItemViewBuilder直接添加到SuperAdapter构造函数中即可:

mAdapter = new SuperAdapter<DataEntity>(multiItemViewBuilder,mData) {

        @Override
        public void convert(ViewHolder holder, DataEntity entity) {
              // 此处写绑定itemview中的控件逻辑
        }

 };

最后在方法convert()中绑定控件逻辑时需要针对不同的控件类型分别处理。

绑定列表点击事件

绑定点击事件:

mAdapter.setOnItemClickListener(new SuperAdapter.OnItemClickListener<DataEntity>() {
        @Override
        public void onItemClick(int position, DataEntity entity) {
            // 此处写点击事件的逻辑
        }
});

绑定长按事件:

mAdapter.setOnItemLongClickListener(new SuperAdapter.OnItemLongClickListener<DataEntity>() {
        @Override
        public void onItemLongClick(int position, DataEntity entity) {
            // 此处写长按事件的逻辑
        }
});

设置空数据视图

空数据视图是指列表的数据源数据为空时显示的提示视图。设置的使用方法非常简单:

mAdapter.setEmptyDataView(R.layout.empty_view);

在为列表添加了 Header 与 Footer 后即使数据源为空也不认为需要显示空视图

添加列表唯一顶部Header

为什么要说是列表“唯一顶部”的 Header 呢?因为除该 Header 外,还有一种用于显示分类信息的 Header ,类似与通讯录中通过首字母 A~Z 顺序显示联系人。

自定义头部控件的本质其实就是列表中的一个特殊ItemView,它永远存在列表最上方的位置且跟随列表滑动。

添加自定义头部需要实现一个头部构造器来管理自定义头部布局:

HeaderBuilder builder = new HeaderBuilder() {
        @Override
        public int getHeaderLayoutId() {
            return R.layout.view_header;
        }

        @Override
        public void bindHeaderView(ViewHolder holder) {
            ImageView background = holder.getView(R.id.header_img);
            holder.<TextView>getView(R.id.header_name).setText("Test UserName);
        }

};

构造器接口中有两个回调方法:

  • int getLayoutId() 设置自定义头部的布局id;
  • void convert(ViewHolder view) 绑定布局中的控件及添加逻辑;

最后,将实现好的头部构造器添加到适配器中即可:

mAdapter.addHeader(builder);

添加列表分类Header

开发中。。。

这个功能有点费劲啊。。。

添加列表底部Footer

Footer 是为满足特殊需求一直存在于列表底部的ItemView,通常情况下是配合列表分页加载数据使用。添加 Footer 的方法与之前添加 Header 的方法类似,需要一个构造器管理:

FooterBuilder builder = new FooterBuilder() {
        
       /**
        * 获取Footer的布局文件Id
        */        
        @Override
        public int getFooterLayoutId() {
            return R.layout.view_footer;
        }

       /**
        * 非用于分页加载更多数据状态下Footer的界面逻辑处理
        */
        @Override
        public void onNormal(ViewHolder holder) {
            holder.<ProgressBar>getView(R.id.footer_progress).setVisibility(View.GONE);
            holder.<TextView>getView(R.id.footer_msg).setText("这是个底部");
        }

       /**
        * 分页加载更多数据 - “正在加载数据中” 状态的界面逻辑处理
        */
        @Override
        public void onLoading(ViewHolder holder) {
            holder.<ProgressBar>getView(R.id.footer_progress).setVisibility(View.VISIBLE);
            holder.<TextView>getView(R.id.footer_msg).setText("正在加载数据中");
        }

       /**
        * 分页加载更多数据 - “加载数据失败” 状态的界面逻辑处理
        *
        * @param msg 数据加载失败的原因
        */
        @Override
        public void onLoadingFailure(ViewHolder holder, String msg) {
            holder.<ProgressBar>getView(R.id.footer_progress).setVisibility(View.GONE);
            holder.<TextView>getView(R.id.footer_msg).setText(msg);
        }

       /**
        * 分页加载更多数据 - “没有更多数据” 状态的界面逻辑处理
        */
        @Override
        public void onNoMoreData(ViewHolder holder) {
            holder.<ProgressBar>getView(R.id.footer_progress).setVisibility(View.GONE);
            holder.<TextView>getView(R.id.footer_msg).setText("已经到底啦");
        }
};

因为 Footer 需要经常配合列表分页加载数据使用,所以在构造器中除了正常使用情况下的方法onNormal(ViewHolder holder)外还提供了三个用于管理分页加载数据状态的方法:

  • onLoading(ViewHolder holder) 正在加载数据中
  • onLoadingFailure(ViewHolder holder, String msg) 数据加载失败,msg为失败的原因
  • onNoMoreData(ViewHolder holder) 已经加载完所有数据

最后,将构造器添加到 SuperAdapter 中:

mAdapter.addFooter(builder);

简易Footer构造器SimpleFooterBuilder

考虑到构造 Footer 的过程有些繁琐, SuperAdapter 库中提供了一个简易的内部定义好的 Footer 构造器 SimpleFooterBuilder,使用方法很简单:

mAdapter.addFooter(new SimpleFooterBuilder("这是个底部","正在加载数据中","加载数据失败","已经到底啦"));

SimpleFooterBuilder构造方法中的4个参数依次对应着 Footer 的正常模式、正在加载、加载失败、加载完毕这几种状态的提示信息。

分页自动加载更多数据

注意!在设置分页加载数据前一定要先添加Footer

在使用该功能时,用户可以自己设置分页请求数据的起始页,每当列表滑动到底部的时候就会按之前设置的页数依次累加请求新的数据。

设置分页加载数据调用 SuperAdapter 的setPaginationData()方法就可以,但是在这之前需要一个加载数据监听器LoadDataListener来管理数据的加载状态,当列表需要加载新的数据时就会调用监听器的onLoadingData()方法,该方法有两个参数:

  • int loadPage 需要加载新数据的页数
  • LoadDataStatus loadDataStatus 分页加载数据的状态控制器

分页加载数据的状态控制器LoadDataStatus 提供了三种状态:

  • onSuccess(List<T> datas) 数据请求成功调用该方法传入新数据
  • onFailure(String msg) 数据请求失败调用该方法传入失败信息
  • onNoMoreData() 数据全部加载完毕调用该方法

注意!加载数据一定要调用状态控制器LoadDataStatus中的方法给列表加载状态做反馈

具体的实现代码:

// 分页加载数据的起始页
private static final int START_PAGE = 0;

...
...

// 实现加载数据监听器
LoadDataListener listener = new LoadDataListener() {
    
        @Override
        public void onLoadingData(final int loadPage, final LoadDataStatus loadDataStatus) {
            
                DataManager.getListPaginationData(loadPage,new Callback(){
                        
                        @Override
                        public void onSuccess(List<String> datas){
                            if(datas.size() == 0){
                                loadDataStatus.onNoMoreData();
                            }else{
                                loadDataStatus.onSuccess(datas);
                            }
                        }

                        @Override
                        public void onFailure(String msg){
                            loadDataStatus.onFailure(msg);
                        }

                        @Override
                        public void onError(){
                            loadDataStatus.onFailure("网络请求出错");
                        }                                
                });
        }
}

// 设置分页加载数据
mAdapter.setPaginationData(START_PAGE, listener);

封装 SuperAdapter 的注意事项

SuperAdaper类中提供了两个有参构造方法:

  • SuperAdapter(int layoutId )
  • SuperAdapter(MultiItemViewBuilder multiItemViewBuilder)

他们实现了不同类型的子布局,所以继承SuperAdapter时要Super()其中一个构造方法,但是你肯定不希望每次实例化适配器都要定义它们,所以建议在你的自定义类中将布局类型以static方法定义好:

private static int layoutId = R.layout.view_list_item;

或者

private static MultiItemViewBuilder<TestEntity> multiItemViewBuilder = new MultiItemViewBuilder<TestEntity>() {

        @Override
        public int getLayoutId(int type) {
            if (type == 0){
                return R.layout.view_list_item_1;
            }else {
                return R.layout.view_list_item_2;
            }
        }

        @Override
        public int getItemType(int position, TestEntity data) {
            return data.getType();
        }
    };

这样自定义类的构造方法中就无需填写参数:

public MineAdapter() {
    super(layoutId);
    // do something
}

或者

public MineAdapter() {
    super(multiItemViewBuilder);
    // do something
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,799评论 25 707
  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,737评论 1 92
  • 近期一直在一个自怜的悲观思绪中践踏自己的自信,想想目前的生活过得不如意都是由于自己的决策失误导致。一个完全交学费...
    墨道寻常阅读 292评论 0 0
  • 火山 经历雨水冲激三十二年 某个深夜 一匹流浪的狼 选择跳进岩浆 怒目圆睁, 咬着冷冷的牙 一瞬间 岩浆被撞破了缺...
    关馨仁阅读 328评论 0 2
  • 我,一个简单普通的人。女性、职员、妈妈。 大学毕业后就职在湖南长沙某知名互联网公司(上市企业,2000人及以上),...
    槑的陈阅读 888评论 0 3