阅读 90

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

作者:Magic旭

原文链接:https://www.jianshu.com/p/21a2b004fc96

文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐