ConcatAdapter-RecyclerView最佳伴侣

背景

  1. 日常逛技术帖时候,无意中发现Android又新推出了一个类ConcatAdapter,该类是用来辅助RecyclerView去添加ViewHolder的。那有人有疑惑了,普通的Adapter也可以添加ViewHolder,这玩意有啥用呢?不要急,慢慢听我下面和你讲。可否还记得当初ListView对比RecyclerView唯一的优势是什么??ListView唯一的优势就是有系统api可以设置HeadView,而ReclcerView在ConcatAdapter问世前都是没有系统api支持的。说到这里相信大家都能猜到它的作用了吧!对,ConcatAdapter就是可以合并多个Adapter,可以做到在RecyclerView的头、尾插入不同的Adapter,实现一列表多样式视图, 而且还可以指定Index来插入指定Adapter。(工程师🐂🍺)

ConcatAdapter

ConcatAdapter我打算分两部分来讲:一、如何入手并且使用;二、内部原因如何实现。为了减轻文章篇幅长度,我会拆成两篇文章来说。
注意:要想使用ConcatAdapter,必须引入依赖:implementation "androidx.recyclerview:recyclerview:1.2.0-alpha06",比该版本高都是支持的。

如何插入头部视图
  1. 声明需要插入到头部的视图样式,即正常声明RecyclerView.Adapter和RecyclerView.ViewHolder,我们暂时命名为HeadAdapter和HeadViewHolder(只包含你想要插入到头部的视图)
class HeadAdapter: RecyclerView.Adapter<HeaderViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_head_view,parent,false)
        return HeaderViewHolder(view)
    }

    override fun getItemCount(): Int {
        return 1
    }

    override fun onBindViewHolder(holder: HeaderViewHolder, position: Int) {
        holder.bind(position.toString() )
    }
}

//ViewHolder也没什么特殊的,你按照正常的ViewHolder去继承就行,不要在乎我的Base,我就是封装下少写点代码
class HeaderViewHolder(val view: View) : BaseViewHolder<String>(view) {
    private val flowerNumberTextView: TextView = itemView.findViewById(R.id.item_number)

    override fun bind(number: String) {
        flowerNumberTextView.text = number
    }
}

看完上面的代码,你已经成功创建了一个即将被插入到列表头部的视图了。观众就懵逼了,这不是日常的创建ViewHolder和adapter而已嘛?是不是想骗我们啊??不要着急,重头戏通常都是摆最后的嘛。跟着我继续往下看。

如何创建正常列表视图
  1. 为了照顾下不同阶层的大老爷们,虽然都是简单的代码,我这里都是会贴出来的。下面创建正常列表视图,我会造100个假数据,主要展示形式就是文字列表。
class ContentAdapter<T : UniteBean>(val content: List<T>) :
    RecyclerView.Adapter<BaseViewHolder<T>>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<T> {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.iteam_content_view, parent, false)
        return TextContentViewHolder(view)
    }

    override fun getItemCount(): Int {
        return content.size
    }

    override fun getItemViewType(position: Int): Int {
        return content[position].getType()
    }

    override fun onBindViewHolder(holder: BaseViewHolder<T>, position: Int) {
        holder.bind(content[position])
    }
}

看到这里观众老爷们估计要喊:”rnm退钱”。一直都是正常创建adapter和viewHolder而已,有什么不同。别急别急嘛,记得你心里这句话,就是正常创建、使用而已。是的,工程师也想要尽量减少对用户习惯的侵入性,所以他们开发了ConcatAdapter。

如何在列表中插入头部视图
  1. 第一步:把头部视图的Adapter和列表内容视图的Adapter实例化。
  2. 第二步:给recyclerView设置一个layoutManager
  3. 第三部:创建ConcatAdapter,把所以的adapter依照视图展示样式进行添加。
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_new_recycler)
       ……
        val adapter = initAdapter()
        // adapter.addAdapter(0,mHeadAdapter) (可以动态插入列表所在位置去展示)
        new_recycler.adapter = adapter
        new_recycler.layoutManager = LinearLayoutManager(this)
    }

    private fun initAdapter(): ConcatAdapter {
        mHeadAdapter = HeadAdapter()
        mContentAdapter = ContentAdapter(mSource)
        //按照添加顺序,在列表中也是按照此顺序展示
        return ConcatAdapter(mHeadAdapter, mContentAdapter)
    }

优势:比起以往自己做头部和尾部视图方法,position = 0 或者 position == size - 1 或者根据type 去做渲染头、尾视图,用ConcatAdapter能实现单一职责,每个adapter负责自身任务,头、中、尾视图都有自己的adapter去负责管理对应的ViewHolder,扩展的同时也进行了解耦,解决以前一个RecyclerView只能对应一个Adapter的尴尬场景。


最符合的实际应用

  1. 上面给大家讲完ConcatAdapter如何使用,要结合实际场景进行开发才行,要不然我这篇文章就太垃圾了,什么都没讲。我们日常遇到的肯定是各种各样的设计师,要求自定义我们列表的头部刷新控件,就是实现那种列表头部下拉出现一个炫酷的加载动画,在没有ConcatAdapter时候,我们往RecyclerView插入一个刷新动画视图是比较困难的,但是用了ConcatAdapter就变得简单了。因为ConcatAdapter可以动态添加、删除Adapter。
//在recyclerView到达底部的时候,判断是否拦截点击事件的分发,我这里手指放开就把头部的视图给移除了,实际上可结合请求业务接口结束后再remove。简单实现下,给大家看效果,实际效果很不错的。
fun dynamicCalculate(
        recyclerView: RecyclerView,
        headAdapter: RecyclerView.Adapter<*>,
        contact: ConcatAdapter
    ) {
        var lastY = 0f
        recyclerView.setOnTouchListener { v: View, event: MotionEvent ->
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    lastY = event.y
                    recyclerView.scrollState == RecyclerView.SCROLL_STATE_IDLE &&
                            !recyclerView.canScrollVertically(-1)
                }
                MotionEvent.ACTION_MOVE -> {
                    if (recyclerView.scrollState != RecyclerView.SCROLL_STATE_SETTLING &&
                        !recyclerView.canScrollVertically(-1)
                    ) {
                        if (event.y < lastY) {
                            //手指往上滑动,给recyclerView处理滑动事件
                            lastY = event.y
                            false
                        } else {
                            lastY = event.y
                            //往下拉,把头部View展示出来
                            if (!contact.adapters.contains(headAdapter)) {
                                contact.addAdapter(0, headAdapter)
                            }
                            true
                        }
                    } else {
                        false
                    }
                }
                MotionEvent.ACTION_UP -> {
                    if (contact.adapters.contains(headAdapter)) {
                        contact.removeAdapter(headAdapter)
                        true
                    } else {
                        false
                    }
                }
                else -> {
                    false
                }
            }
        }

注意:在书写头部炫酷的刷新动画时候,也要注意onViewDetachedFromWindow时候要把动画给停掉。因为再怎么说都还是在recyclerView内进行操作的,所以视图不可见后不应该再做动画的。

最终实现效果图(用系统自带动画都能足以和ios的系统下拉刷后回弹回去的效果平起平坐了)


c1chm-px5ym (1).gif

总结

  1. 新增的ConcatAdapter我相信往后肯定会成为主流,因为他可以把负责的列表拆分多个模块,这样无论对后期维护还是开发都是更为方便的。
  2. 参考文献:在 RecyclerView 中使用 header 快人一步
  3. demo链接:https://github.com/Linsixu/Magic-demo/tree/feature_1.0.2_head_recyclerview
  4. ConcatAdapter相关源码解析:https://www.jianshu.com/p/61b2f3b44267
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容

  • 抽屉菜单 MaterialDrawer★7337 - 安卓抽屉效果实现方案 Side-Menu.Android★3...
    KennGM阅读 739评论 0 0
  • Android是一种基于Linux的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由Goo...
    Androidgiao阅读 1,031评论 0 3
  • GitHub上受欢迎的Android UI Library https://mvnrepository.com/ ...
    门心叼龙阅读 1,861评论 0 2
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 124,898评论 2 7
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,046评论 0 4