背景
ConcatAdapter
ConcatAdapter我打算分两部分来讲:一、如何入手并且使用;二、内部原因如何实现。为了减轻文章篇幅长度,我会拆成两篇文章来说。
注意
:要想使用ConcatAdapter,必须引入依赖:implementation "androidx.recyclerview:recyclerview:1.2.0-alpha06",比该版本高都是支持的。
如何插入头部视图
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而已嘛?是不是想骗我们啊??不要着急,重头戏通常都是摆最后的嘛。跟着我继续往下看。
如何创建正常列表视图
为了照顾下不同阶层的大老爷们,虽然都是简单的代码,我这里都是会贴出来的。下面创建正常列表视图,我会造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。
如何在列表中插入头部视图
第一步:把头部视图的Adapter和列表内容视图的Adapter实例化。
第二步:给recyclerView设置一个layoutManager
第三部:创建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的尴尬场景。
最符合的实际应用
上面给大家讲完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)
} else {
false
MotionEvent.ACTION_UP -> {
if (contact.adapters.contains(headAdapter)) {
contact.removeAdapter(headAdapter)
} else {
false
else -> {
false
注意:在书写头部炫酷的刷新动画时候,也要注意onViewDetachedFromWindow时候要把动画给停掉。因为再怎么说都还是在recyclerView内进行操作的,所以视图不可见后不应该再做动画的。
最终实现效果图(用系统自带动画都能足以和ios的系统下拉刷后回弹回去的效果平起平坐了)
新增的ConcatAdapter我相信往后肯定会成为主流,因为他可以把负责的列表拆分多个模块,这样无论对后期维护还是开发都是更为方便的。
参考文献:在 RecyclerView 中使用 header 快人一步