欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

RecycleView中使用总结以及在项目中的实际运用场景总结

程序员文章站 2022-05-15 19:29:55
...

前言

RecycleView的问世,替代了ListViewt和GridView,性能得到提升。同时也出现了许多优秀的第三方开源库。本文总结了在实现项目中是如何运用RecycleView的场景,以及总结了项目中使用时的一些心得,希望对你有所帮助。

1.RecycleView

官方对RecyclerView的描述:

A flexible view for providing a limited window into a large data set.

1.1 使用RecycleView的优缺点

优点:

  • RecycleView强制封装ViewHolder
  • 相当轻松的设置布局管理器以控制Item的布局方式,横向、竖向以及瀑布流方式
  • 可设置Item操作的动画,删除或者添加等
  • 通过ItemDecoration,控制Item间的间隔,可自己绘制

缺点:

  • 需要自己实现OnItemClickListener点击事件。

    是缺点,也是优点,为何?

    ListView 中对于点击事件的处理,其实是有很大弊端的,它的 setOnItemClickListener() 方法是为子项注册点击事件,这就导致只能识别到这一整个子项,对于子项中的组件比如按钮就束手无策了。

    因此,RecyclerView 直接放弃了这个为子项注册点击事件的监听方法,所有点击事件都有具体 View 去注册,我们可以按需为组件注册点击事件,不存在点击不到的组件。

1.2 基本用法

在xml布局中:(activity_launch.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:itemCount="5"
        tools:listitem="@layout/data_item" />

</LinearLayout>

item布局:(data_item.xml)

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/tv"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="10dp"
    android:gravity="center"
    android:padding="5dp"
    android:textColor="@color/colorAccent"
    tools:text="10" />

在Activity界面中:(LaunchActivity.kt)

class LaunchActivity : AppCompatActivity() {
    private var listData = ArrayList<Int>()
    private val adapter = MyAdapter(listData)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_launch)
        initListener()
        initRecycleView()
        initData()
    }

    private fun initListener() {
        adapter.setOnItemOnClickListener(object : MyAdapter.OnItemClickListener {
            override fun onItemOnclick(view: View, position: Int) {

                Toast.makeText(
                    this@LaunchActivity,
                    "你点了第" + position + "个,文本内容是:" + (view as TextView).text,
                    Toast.LENGTH_SHORT
                ).show()
            }
        })
    }

    private fun initRecycleView() {
        //线型布局、显示垂直或水平滚动的列表项
        rvList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)

        //显示网格中的item(项)
//        rvList.layoutManager = GridLayoutManager(this, 5)

        //显示交错的网格item(项目)
//        rvList.layoutManager = StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL)

        rvList.adapter = adapter
    }

    private fun initData() {
        for (i in 0..100) {
            listData.add(i)
        }
    }
}

自定义Adapter

class MyAdapter(private var mList: ArrayList<Int>) : RecyclerView.Adapter<MyAdapter.MyHolder>() {

    private lateinit var mOnItemClickListener: OnItemClickListener

    fun setOnItemOnClickListener(onItemClickListener: OnItemClickListener) {
        this.mOnItemClickListener = onItemClickListener
    }

    ///创建ViewHolder
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder {

        return MyHolder(
            LayoutInflater.from(parent.context).inflate(
                R.layout.data_item,
                parent,
                false
            )
        )
    }

    //返回个数
    override fun getItemCount(): Int = mList.size

    //填充视图
    override fun onBindViewHolder(holder: MyHolder, position: Int) {
        holder.itemView.tv.text = mList[position].toString()

        //点击事件
        mOnItemClickListener.let {
            holder.itemView.setOnClickListener {
                val position = holder.layoutPosition
                mOnItemClickListener.onItemOnclick(holder.itemView, position)
            }
        }
    }

    class MyHolder(itemView: View) : RecyclerView.ViewHolder(itemView)


    //实现自定义点击事件
    interface OnItemClickListener {
        fun onItemOnclick(view: View, position: Int)
    }
}

实现效果:

RecycleView中使用总结以及在项目中的实际运用场景总结

上面就是recycleView的基本用法 ,然后在实际使用中,需要不断完善。比较需要考虑下拉刷新、添加头文件或是尾部布局、或是多种类型的布局,还要考虑局部刷新、网络异常、数据异常时不同的布局显示等等。

下面总结一下项目中是如何使用的。

2.RecycleView中考虑多布局

2.1 版本1.0

在xml布局文件中:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

在Activity中:

class MutiTypeActivity : AppCompatActivity() {
    private val lists = ArrayList<Category>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_mutitype)
        initData()
        initRecycleView()
    }


    private fun initRecycleView() {
        //显示网格中的item(项)
        val layoutManager = GridLayoutManager(this, 3)

        val adapter = CategoryAdapter(this, lists)

        //根据不同类型返回不同的列数
        layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {

            override fun getSpanSize(position: Int): Int {
                return when (adapter.getItemViewType(position)) {
                    Category.ONE_TYPE -> 3
                    else -> 1
                }
            }
        }

        rvList.layoutManager = layoutManager

        rvList.adapter = adapter



        adapter.setOnItemOnClickListener(object : CategoryAdapter.OnItemClickListener {
            override fun onItemOnclick(view: View, position: Int, viewType: Int) {

                if (viewType == Category.THIRD_TYPE) {
                    Toast.makeText(
                        this@MutiTypeActivity,
                        "你点了第" + position + "个,文本内容是:" + (view as TextView).text,
                        Toast.LENGTH_SHORT
                    ).show()
                } else {
                    Toast.makeText(
                        this@MutiTypeActivity,
                        "跳到更多界面,当前pos==$position",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
        })
    }

    private fun initData() {
        lists.add(Category("商机", 0))
        lists.add(Category("车险", 1))
        lists.add(Category("违章", 1))
        lists.add(Category("年检", 1))

        lists.add(Category("客户列表", 0))
        lists.add(Category("有消费能力", 1))
        lists.add(Category("朋友", 1))
        lists.add(Category("同事", 1))
        lists.add(Category("其他", 1))

        lists.add(Category("品牌列表", 0))
        lists.add(Category("别克", 1))
        lists.add(Category("宝马", 1))
        lists.add(Category("奔驰", 1))
        lists.add(Category("标致", 1))
        lists.add(Category("奔腾", 1))
        lists.add(Category("奥迪", 1))
    }
}

在自定义Adapter 中:


class CategoryAdapter(context: Context, lists: List<Category>?) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private var lists = ArrayList<Category>()
    private val layoutInflater: LayoutInflater

    private lateinit var mOnItemClickListener: OnItemClickListener

    fun setOnItemOnClickListener(listener: OnItemClickListener) {
        this.mOnItemClickListener = listener
    }

    init {
        this.lists = lists as ArrayList<Category>
        this.layoutInflater = LayoutInflater.from(context)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return if (viewType == Category.ONE_TYPE) {
            OneViewHolder(layoutInflater.inflate(R.layout.second_item, null, false))
        } else {
            ThirdViewHolder(layoutInflater.inflate(R.layout.thrid_item, null, false))
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

        val viewType = getItemViewType(position)
        when (viewType) {
            Category.ONE_TYPE -> {
                val oneViewHolder = holder as OneViewHolder
                oneViewHolder.secondCategory.text = lists[position].categoryName
            }
            Category.THIRD_TYPE -> {
                val thirdViewHolder = holder as ThirdViewHolder
                thirdViewHolder.thirdCategory.text = lists[position].categoryName
            }
        }
        //点击事件
        mOnItemClickListener.let {
            holder.itemView.setOnClickListener {
                val pos = holder.layoutPosition
                mOnItemClickListener.onItemOnclick(holder.itemView, pos, viewType)
            }
        }

    }

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

    override fun getItemViewType(position: Int): Int {
        return lists[position].type
    }


    inner class OneViewHolder internal constructor(itemView: View) :
        RecyclerView.ViewHolder(itemView) {
        val secondCategory: TextView = itemView.findViewById(R.id.tvSecond)
    }

    inner class ThirdViewHolder internal constructor(itemView: View) :
        RecyclerView.ViewHolder(itemView) {
        val thirdCategory: TextView = itemView.findViewById(R.id.tvThird)
    }

    //实现自定义点击事件
    interface OnItemClickListener {
        fun onItemOnclick(view: View, position: Int, viewType: Int)
    }
}

相关实体类:


class Category(val categoryName: String, val type: Int) {

    companion object {
        const val ONE_TYPE = 0
        const val THIRD_TYPE = 1
    }
}

实现效果如图所示:

RecycleView中使用总结以及在项目中的实际运用场景总结

注意:为了实现如上图的线性和网格的混合视图效果,只需要一个 GridLayoutManager(其继承自 LinearLayoutManager)而关键的代码就是下图中的为 GridLayoutManager 设置 GridLayoutManager.SpanSizeLookup 监听器。用getSpanSize()方法来根据type返回不同的值,最后载显示不同的列数。 这样做体现了RecyclewView的灵活性,这个界面只需要用一个RecycleView中的多布局就可以实现。

2.2 版本2.0时代

比如我们写一个类似微博列表页面,这样的列表是十分复杂的:有纯文本的、带转发原文的、带图片的、带视频的、带文章的等等,甚至穿插一条可以横向滑动的好友推荐条目。不同的 item 类型众多,而且随着业务发展,还会更多。如果我们使用传统的开发方式,经常要做一些繁琐的工作,代码可能都堆积在一个 Adapter 中:我们需要覆写 RecyclerView.AdaptergetItemViewType 方法,罗列一些 type 整型常量,并且 ViewHolder 转型、绑定数据也比较麻烦。一旦产品需求有变,或者产品设计说需要增加一种新的 item 类型,我们需要去代码堆里找到原来的逻辑去修改,或找到正确的位置去增加代码。这些过程都比较繁琐,侵入较强,需要小心翼翼,以免改错影响到其他地方。

于是 出现了第三方的开源库:MultiType

支持功能如下:

  • 使用 MultiTypeTemplates 插件自动生成代码
  • 一个类型对应多个 ItemViewBinder
  • 与 ItemViewBinder 通讯
  • 使用断言,比传统 Adapter 更加易于调试
  • 支持 Google AutoValue
  • MultiType 与下拉刷新、加载更多、HeaderView、FooterView、Diff
  • 实现 RecyclerView 嵌套横向 RecyclerView
  • 实现线性布局和网格布局混排列表

实现步骤如下:

第一步:(开源库中的示例)新建一个类:Foo.kt 这个类的内容没有任何限制。

data class Foo(
  val value: String
)

第二步:基于ItemViewBinder<T,VH:ViewHolder>创建一个类:FooViewBinder.kt。

class FooViewBinder: ItemViewBinder<Foo, FooViewBinder.ViewHolder>() {

  override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
    return ViewHolder(inflater.inflate(R.layout.item_foo, parent, false))
  }

  override fun onBindViewHolder(holder: ViewHolder, item: Foo) {
    holder.fooView.text = item.value
    Log.d("ItemViewBinder API", "position: ${getPosition(holder)}")
    Log.d("ItemViewBinder API", "items: $adapterItems")
    Log.d("ItemViewBinder API", "adapter: $adapter")
    Log.d("More", "Context: ${holder.itemView.context}")
  }

  class ViewHolder(itemView : View): RecyclerView.ViewHolder(itemView) {
    val fooView: TextView = itemView.findViewById(R.id.foo)
  }
}

第三步:界面中实现,用register注册用到的布局类型,并设置RecycleView相关属性。

class SampleActivity : AppCompatActivity() {

  private val adapter = MultiTypeAdapter()
  private val items = ArrayList<Any>()

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_list)
    val recyclerView = findViewById<RecyclerView>(R.id.list)

     //注册不同的布局类型  注册和View的对应关系
    adapter.register(TextItemViewBinder())
    adapter.register(ImageItemViewBinder())
    adapter.register(RichItemViewBinder())
      
    recyclerView.adapter = adapter

    val textItem = TextItem("world")
    val imageItem = ImageItem(R.mipmap.ic_launcher)
    val richItem = RichItem("小艾大人赛高", R.drawable.img_11)

    for (i in 0..19) {
      items.add(textItem)
      items.add(imageItem)
      items.add(richItem)
    }
    adapter.items = items
    adapter.notifyDataSetChanged()
  }
}

ItemViewBinder 源码解读:

abstract class ItemViewBinder<T, VH : ViewHolder> {

  @Suppress("PropertyName")
  internal var _adapter: MultiTypeAdapter? = null

  /**
   * Gets the associated [MultiTypeAdapter].
   * @since v2.3.4
   */
  val adapter: MultiTypeAdapter
    get() {
      if (_adapter == null) {
        throw IllegalStateException(
          "This $this has not been attached to MultiTypeAdapter yet. " +
              "You should not call the method before registering the binder."
        )
      }
      return _adapter!!
    }
    
      var adapterItems: List<Any>
    get() = adapter.items
    set(value) {
      adapter.items = value
    }
    
   abstract fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): VH
    
    abstract fun onBindViewHolder(holder: VH, item: T)
    
    open fun onBindViewHolder(holder: VH, item: T, payloads: List<Any>) {
    onBindViewHolder(holder, item)
  }
    
    fun getPosition(holder: ViewHolder): Int {
    return holder.adapterPosition
  }
    
    open fun getItemId(item: T): Long = RecyclerView.NO_ID
    
    open fun onViewRecycled(holder: VH) {}
    
    open fun onFailedToRecycleView(holder: VH): Boolean {
    return false
  }
    
    open fun onViewAttachedToWindow(holder: VH) {}
    
    open fun onViewDetachedFromWindow(holder: VH) {}

ItemViewBinder 是个抽象类,其中 onCreateViewHolder 方法用于生产你的 Item View Holder, onBindViewHolder 用于绑定数据到 View 。 一般一个 ItemViewBinder 类在内存中只会有一个实例对象,MultiType 内部将复用这个 binder 对象来生产所有相关的 item views 和绑定数据。

该开源库中的onCreateViewHolderonBindViewHolder方法沿用了RecycleView中的习惯,降低学习成本和理解难度。

实际项目中往往会加入上拉刷新或下拉加载更多。

纵观近几年的开发经验,下拉刷新控件经历了:PullToRefreshListViewandroid-Ultra-Pull-To-Refresh,再到后来的官方组件SwipeRefreshLayout 技术在不停的迭代中,没有最好,选择适合自己的项目中的就行,保持一定的技术新鲜度最好。

2.2.1 上拉刷新实现

引入SwipeRefreshLayout

官方SwipeRefreshLayout简要说明

在竖直滑动时想要刷新页面可以用SwipeRefreshLayout来实现。它通过设置OnRefreshListener来监听界面的滑动从而实现刷新。也可以通过一些方法来设置SwipeRefreshLayout是否可以刷新。如:setRefreshing(true),展开刷新动画。setRefreshing(false),取消刷新动画。setEnable(false)下拉刷新将不可用。
使用这个布局要想达到刷新的目的,需要在这个布局里包裹可以滑动的子控件,如ListView等,并且只能有一个子控件。

主要方法:

  • isRefreshing()
    判断当前的状态是否是刷新状态。

  • setColorSchemeResources(int… colorResIds)
    设置下拉进度条的颜色主题,参数为可变参数,并且是资源id,最多设置四种不同的颜色,每转一圈就显示一种颜色。

  • setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener listener)
    设置监听,需要重写onRefresh()方法,顶部下拉时会调用这个方法,在里面实现请求数据的逻辑,设置下拉进度条消失等等。

  • setProgressBackgroundColorSchemeResource(int colorRes)
    设置下拉进度条的背景颜色,默认白色。

  • setRefreshing(boolean refreshing)
    设置刷新状态,true表示正在刷新,false表示取消刷新。

在xml布局中:(截取实际项目中的运用示例)

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@id/swipeRefreshLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="@dimen/dp_minus_10">

        <cc.utimes.chejinjia.common.widget.layoutstatus.StatusLayout
            android:id="@id/statusLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <androidx.recyclerview.widget.RecyclerView
                android:id="@id/rvList"
                style="@style/CommonRecyclerViewStyle"
                android:layout_height="match_parent"
                android:background="@color/common_gray_F2" />

        </cc.utimes.chejinjia.common.widget.layoutstatus.StatusLayout>

    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

注意:swipeRefreshLayout在布局中包裹最外层,中间的StatusLayout为加载中的多种状态(加载中、加载成功、加载失败(服务器异常等失败)、数据为空)这里不展开叙述,后期看情况再详细补充说明。最下面的为列表布局。

这个布局为项目中有列表中的通用布局,在此基础上根据业务再添加头文件和尾部文件等。

在Activity中调用:(伪代码示例)

swipeRefreshLayout.setOnRefreshListener {
      		// 禁用上拉加载更多
            adapter.setEnableLoadMore(false)
            // 调用下拉刷新
            refresh.onRefresh()
        }

2.2.2 下拉加载更多实现

重写OnScrollListener

//加载更多
abstract class OnLoadMoreListener : RecyclerView.OnScrollListener() {
    private var itemCount: Int = 0
    private var lastPosition: Int = 0
    private var lastItemCount: Int = 0
    private var layoutManager: RecyclerView.LayoutManager? = null
	//是否可以滑动
    private var isCanScrolled = false
    //加载更多
    abstract fun onLoadMore()

    override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
        super.onScrollStateChanged(recyclerView, newState)

        // 拖拽或者惯性滑动时isScolled设置为true
        isCanScrolled = (newState == SCROLL_STATE_DRAGGING || newState == SCROLL_STATE_SETTLING)
    }

    override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)
        layoutManager = recyclerView?.layoutManager
        
        if (recyclerView!!.layoutManager is LinearLayoutManager) {
            itemCount = layoutManager!!.itemCount
            lastPosition = (layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
        }

        if (lastItemCount != itemCount && lastPosition == itemCount - 1) {
            lastItemCount = itemCount
            this.onLoadMore()
        }
    }
}

注:上面这段代码还可以用于 判断界面中回到顶部的按钮显示逻辑判断。比如:在当前界面数据显示时不显示“回到顶部”按钮,当其他界面时再显示。

在Activity界面中调用:

    rvList.addOnScrollListener(object : OnLoadMoreListener() { 
           override fun onLoadMore() {
               //TODO:实现加载更多方法
            }
        })

在一款音视频App开发过程中,在订阅频道 我们用到了多布局。就采用了该开源库

根据用户是否登录,显示不同的界面,登录时显示推荐数据或订阅的数据,未登录则提示请登录界面。并用到了嵌套横向的RecycleView。由于业务较为复杂,代码实现与开源代码示例类似,此处不再展示代码。仅展示一下效果图。

RecycleView中使用总结以及在项目中的实际运用场景总结

2.3 版本3.0时代

在版本和技术的不断迭代中,我们用到了第三方的开源控件 BaseRecyclerViewAdapterHelper 截止目前有18.6 K star 貌似运用人数最多。

项目介绍:

BRVAH:Powerful and flexible RecyclerAdapter http://www.recyclerview.org/

简单来说: 就是很好很强大,能满足项目的开发需要,可替代之前的开源库。

具体的项目介绍 可点击前往

下面谈谈使用感受和实际运用。

2.3.1 精简代码

没用这个库之前的代码实现:

public class DefAdpater extends RecyclerView.Adapter<DefAdpater.ViewHolder> {
    private final List<Status> sampleData = DataServer.getSampleData();
    private Context mContext;
    public DefAdpater(Context context) {
        mContext = context;
    } 
   @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View item = LayoutInflater.from(parent.getContext()).inflate(R.layout.tweet, parent, false);
        return new ViewHolder(item);
    }
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Status status = sampleData.get(position);
        holder.name.setText(status.getUserName());
        holder.text.setText(status.getText());
        holder.date.setText(status.getCreatedAt());
        Picasso.with(mContext).load(status.getUserAvatar()).into(holder.avatar);
        holder.rt.setVisibility(status.isRetweet() ? View.VISIBLE : View.GONE);
    }
    @Override
    public int getItemCount() {
        return sampleData.size();
    }
    public static class ViewHolder extends RecyclerView.ViewHolder {
        private ImageView avatar;
        private ImageView rt;
        private TextView name;
        private TextView date;
        private TextView text;
        public ViewHolder(View itemView) {
            super(itemView);
            text = (TextView) itemView.findViewById(R.id.tweetText);
            name = (TextView) itemView.findViewById(R.id.tweetName);
            date = (TextView) itemView.findViewById(R.id.tweetDate);
            avatar = (ImageView) itemView.findViewById(R.id.tweetAvatar);
            rt = (ImageView) itemView.findViewById(R.id.tweetRT);
        }
    }
}

用了这个库优化后,就可以这样实现,代码量减少三分之二。

public class QuickAdapter extends BaseQuickAdapter<Status> {
    public QuickAdapter(Context context) {
        super(context, R.layout.tweet, DataServer.getSampleData());
    }
    @Override
    protected void convert(BaseAdapterHelper helper, Status item) {
        helper.setText(R.id.tweetName, item.getUserName())
                .setText(R.id.tweetText, item.getText())
                .setText(R.id.tweetDate, item.getCreatedAt())
                .setImageUrl(R.id.tweetAvatar, item.getUserAvatar())
                .setVisible(R.id.tweetRT, item.isRetweet())
                .linkify(R.id.tweetText);
    }
}

优化思路:

找到重复部分代码,抽取到基类,非重复部分用抽象方法代替,具体让子类实现。

说了思路,看看BaseQuickAdapter源码是怎么写的:

基于版本 2.9.46分析

api 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.46'
public abstract class BaseQuickAdapter<T, K extends BaseViewHolder> extends RecyclerView.Adapter<K> {

   @Override
    public int getItemCount() {
        int count;
        if (getEmptyViewCount() == 1) {
            count = 1;
            if (mHeadAndEmptyEnable && getHeaderLayoutCount() != 0) {
                count++;
            }
            if (mFootAndEmptyEnable && getFooterLayoutCount() != 0) {
                count++;
            }
        } else {
            count = getHeaderLayoutCount() + mData.size() + getFooterLayoutCount() + getLoadMoreViewCount();
        }
        return count;
    }
    
      @Override
    public K onCreateViewHolder(ViewGroup parent, int viewType) {
        K baseViewHolder = null;
        this.mContext = parent.getContext();
        this.mLayoutInflater = LayoutInflater.from(mContext);
        switch (viewType) {
            case LOADING_VIEW:
                baseViewHolder = getLoadingView(parent);
                break;
            case HEADER_VIEW:
                baseViewHolder = createBaseViewHolder(mHeaderLayout);
                break;
            case EMPTY_VIEW:
                baseViewHolder = createBaseViewHolder(mEmptyLayout);
                break;
            case FOOTER_VIEW:
                baseViewHolder = createBaseViewHolder(mFooterLayout);
                break;
            default:
                baseViewHolder = onCreateDefViewHolder(parent, viewType);
                bindViewClickListener(baseViewHolder);
        }
        baseViewHolder.setAdapter(this);
        return baseViewHolder;

    }
    
    //绑定监听事件
    private void bindViewClickListener(final BaseViewHolder baseViewHolder) {
        if (baseViewHolder == null) {
            return;
        }
        final View view = baseViewHolder.itemView;
        if (view == null) {
            return;
        }
        if (getOnItemClickListener() != null) {
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    setOnItemClick(v, baseViewHolder.getLayoutPosition() - getHeaderLayoutCount());
                }
            });
        }
        if (getOnItemLongClickListener() != null) {
            view.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    return setOnItemLongClick(v, baseViewHolder.getLayoutPosition() - getHeaderLayoutCount());
                }
            });
        }
    }
        /**
     * To bind different types of holder and solve different the bind events
     *
     * @param holder
     * @param position
     * @see #getDefItemViewType(int)
     */
    @Override
    public void onBindViewHolder(K holder, int position) {
        //Add up fetch logic, almost like load more, but simpler.
        autoUpFetch(position);
        //Do not move position, need to change before LoadMoreView binding
        autoLoadMore(position);
        int viewType = holder.getItemViewType();

        switch (viewType) {
            case 0:
                convert(holder, getItem(position - getHeaderLayoutCount()));
                break;
            case LOADING_VIEW:
                mLoadMoreView.convert(holder);
                break;
            case HEADER_VIEW:
                break;
            case EMPTY_VIEW:
                break;
            case FOOTER_VIEW:
                break;
            default:
                convert(holder, getItem(position - getHeaderLayoutCount()));
                break;
        }
    }
    
     /**
     * Implement this method and use the helper to adapt the view to the given item.
     *
     * @param helper A fully initialized helper.
     * @param item   The item that needs to be displayed.
     */
    protected abstract void convert(K helper, T item);

}

接下来再看看BaseViewHolder怎么写的:


public class BaseViewHolder extends RecyclerView.ViewHolder {
    
   /**
     * Views indexed with their IDs
     */
    private final SparseArray<View> views;

    public Set<Integer> getNestViews() {
        return nestViews;
    }

    private final HashSet<Integer> nestViews;

    private final LinkedHashSet<Integer> childClickViewIds;

    private final LinkedHashSet<Integer> itemChildLongClickViewIds;
    private BaseQuickAdapter adapter;
    /**
     * use itemView instead
     */
    @Deprecated
    public View convertView;
    
      public BaseViewHolder(final View view) {
        super(view);
        this.views = new SparseArray<>();
        this.childClickViewIds = new LinkedHashSet<>();
        this.itemChildLongClickViewIds = new LinkedHashSet<>();
        this.nestViews = new HashSet<>();
        convertView = view;
    }
    
    //下面是各组件绑定赋值
    public BaseViewHolder setText(@IdRes int viewId, CharSequence value) {
        TextView view = getView(viewId);
        view.setText(value);
        return this;
    }

    public BaseViewHolder setText(@IdRes int viewId, @StringRes int strId) {
        TextView view = getView(viewId);
        view.setText(strId);
        return this;
    }
    
    public BaseViewHolder setImageResource(@IdRes int viewId, @DrawableRes int imageResId) {
        ImageView view = getView(viewId);
        view.setImageResource(imageResId);
        return this;
    }
    
       public BaseViewHolder setBackgroundColor(@IdRes int viewId, @ColorInt int color) {
        View view = getView(viewId);
        view.setBackgroundColor(color);
        return this;
    }
    
     public BaseViewHolder setGone(@IdRes int viewId, boolean visible) {
        View view = getView(viewId);
        view.setVisibility(visible ? View.VISIBLE : View.GONE);
        return this;
    }
    
      public BaseViewHolder setVisible(@IdRes int viewId, boolean visible) {
        View view = getView(viewId);
        view.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
        return this;
    }
    
       public BaseViewHolder setProgress(@IdRes int viewId, int progress, int max) {
        ProgressBar view = getView(viewId);
        view.setMax(max);
        view.setProgress(progress);
        return this;
    }
    
    
        /**
     * add childView id
     *
     * @param viewIds add the child views id can support childview click
     * @return if you use adapter bind listener
     * @link {(adapter.setOnItemChildClickListener(listener))}
     * <p>
     * or if you can use  recyclerView.addOnItemTouch(listerer)  wo also support this menthod
     */
    public BaseViewHolder addOnClickListener(@IdRes final int ...viewIds) {
        for (int viewId : viewIds) {
            childClickViewIds.add(viewId);
            final View view = getView(viewId);
            if (view != null) {
                if (!view.isClickable()) {
                    view.setClickable(true);
                }
                view.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (adapter.getOnItemChildClickListener() != null) {
                            adapter.getOnItemChildClickListener().onItemChildClick(adapter, v, getClickPosition());
                        }
                    }
                });
            }
        }
        return this;
    }
    
      /**
     * Sets the adapter of a adapter view.
     *
     * @param adapter The adapter;
     * @return The BaseViewHolder for chaining.
     */
    protected BaseViewHolder setAdapter(BaseQuickAdapter adapter) {
        this.adapter = adapter;
        return this;
    }
    
    

    @SuppressWarnings("unchecked")
    public <T extends View> T getView(@IdRes int viewId) {
        View view = views.get(viewId);
        if (view == null) {
            view = itemView.findViewById(viewId);
            views.put(viewId, view);
        }
        return (T) view;
    }

    //省略其他点击事件和组件的绑定操作
}
  • 利用SparseArray来做缓存,把常用方法全部写好,从而避免冗余代码。
  • 用了 建造者模式来进行组件的绑定操作,调用时链式调用,方便扩展。

2.3.2 扩展功能

原生的RecycleView是没有点击事件的,需要我们自己实现。前面的代码已经做了很好的优化封装,我们只需要进行扩展该功能即可,方法也很简单,原理类似是上面介绍的基本用法中。不同的是我们的创建时机不同。

网上有很多写法都是在onBindViewHolder里面写,功能是可以实现但是会导致频繁创建,应该在 onCreateViewHolder()中每次为新建的 View 设置一次就行了。

如果你第一次了解如何实现点击事件,很有可能在网上找到的代码类似上面介绍的基本用法,在``onBindViewHolder`实现,可能不会考虑还有没有其他更好的方式,随着技术的迭代,有时间优化时我们去深入源码研究或参考别人的做法。我们就会进一步优化。

以上的分析大多参考网上的分析思路,说了这么多,我们在项目中是如何使用的呢?

3. 实际项目中的运用

3.1 简单界面运用

技术调研后,我们在项目进行运用,运用时进行简单必要的封装,方便随时替换第三的方框架。

abstract class BaseAdapter<T> : BaseQuickAdapter<T, BaseViewHolder> {

    constructor() : super(0, null)

    constructor(@LayoutRes layoutResId: Int) : super(layoutResId, null)

    constructor(data: List<T>?) : super(0, data)

    constructor(@LayoutRes layoutResId: Int, data: List<T>?) : super(layoutResId, data)

    override fun setNewData(data: List<T>?) {
        super.setNewData(data)

        if (data != null && data is ObservableArrayList) {
            data.setOnListChangedListener(object : ObservableArrayList.OnListChangedListener<ObservableArrayList<T>> {
                override fun onChanged(sender: ObservableArrayList<T>) {
                    notifyDataSetChanged()
                }

                override fun onItemRangeChanged(sender: ObservableArrayList<T>, positionStart: Int, itemCount: Int) {
                    notifyItemRangeChanged(positionStart + headerLayoutCount, itemCount)
                }

                override fun onItemRangeInserted(sender: ObservableArrayList<T>, positionStart: Int, itemCount: Int) {
                    notifyItemRangeInserted(positionStart + headerLayoutCount, itemCount)
                }

                override fun onItemMoved(sender: ObservableArrayList<T>, fromPosition: Int, toPosition: Int) {
                    notifyItemMoved(fromPosition + headerLayoutCount, toPosition + headerLayoutCount)
                }

                override fun onItemRangeRemoved(sender: ObservableArrayList<T>, positionStart: Int, itemCount: Int) {
                    notifyItemRangeRemoved(positionStart + headerLayoutCount, itemCount)
                }

            })
        }
    }

    //这里默认第三方框架的方法
    override fun convert(helper: BaseViewHolder, item: T) {}

    /** 
     * 优化点击事件,方法写在这
     * 重写了item点击事件,加上了防止重复点击
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
        val baseViewHolder = super.onCreateViewHolder(parent, viewType)
        if (viewType != LOADING_VIEW && viewType != HEADER_VIEW && viewType != EMPTY_VIEW && viewType != FOOTER_VIEW) {
            if (onItemClickListener != null) {
                baseViewHolder.itemView.onClick {
                    onItemClickListener.onItemClick(
                        this,
                        baseViewHolder.itemView,
                        baseViewHolder.layoutPosition - headerLayoutCount
                    )
                }
            }
        }

        return baseViewHolder
    }
    //考虑局部刷新的实现
    override fun onBindViewHolder(holder: BaseViewHolder, position: Int, payloads: MutableList<Any>) {
        if (payloads.isEmpty()) {
            super.onBindViewHolder(holder, position, payloads)
        } else {
            handleLocalRefresh(holder, mData[holder.layoutPosition - headerLayoutCount], payloads)
        }
    }

    protected open fun handleLocalRefresh(helper: BaseViewHolder, item: T, payloads: MutableList<Any>) {}
}

其中的OnListChangedListener接口实现如下:

interface OnListChangedListener<T : ObservableArrayList<*>> {

        /**
         * Called whenever a change of unknown type has occurred, such as the entire list being
         * set to new values.
         *
         * @param sender The changing list.
         */
        fun onChanged(sender: T)

        /**
         * Called whenever one or more items in the list have changed.
         * @param sender The changing list.
         * @param positionStart The starting index that has changed.
         * @param itemCount The number of items that have changed.
         */
        fun onItemRangeChanged(sender: T, positionStart: Int, itemCount: Int)

        /**
         * Called whenever items have been inserted into the list.
         * @param sender The changing list.
         * @param positionStart The insertion index
         * @param itemCount The number of items that have been inserted
         */
        fun onItemRangeInserted(sender: T, positionStart: Int, itemCount: Int)

        /**
         * Called whenever items in the list have been moved.
         * @param sender The changing list.
         * @param fromPosition The position from which the items were moved
         * @param toPosition The destination position of the items
         */
        fun onItemMoved(sender: T, fromPosition: Int, toPosition: Int)

        /**
         * Called whenever items in the list have been deleted.
         * @param sender The changing list.
         * @param positionStart The starting index of the deleted items.
         * @param itemCount The number of items removed.
         */
        fun onItemRangeRemoved(sender: T, positionStart: Int, itemCount: Int)
    }

上面的封装可以做成通用的库文件。

在项目中还可以进一步封装 (根据情况可选):

open class MyBaseAdapter<T> : BaseAdapter<T> {

    constructor() : super(0, null)

    constructor(@LayoutRes layoutResId: Int) : super(layoutResId, null)

    constructor(@Nullable data: List<T>) : super(0, data)

    constructor(@LayoutRes layoutResId: Int, @Nullable data: List<T>) : super(layoutResId, data)

    fun setArrayListLiveData(lifecycleOwner: LifecycleOwner, liveData: ArrayListLiveData<T>) {
        setNewData(liveData.rawList)

        ListChangedObserver(lifecycleOwner, this, liveData)
    }
}

自定义adapter:

class VehicleBrandAdapter : MyBaseAdapter<VehicleBrandBean>(R.layout.recycle_item_vehicle_brand_header) {

    override fun convert(helper: BaseViewHolder, item: VehicleBrandBean) {
        super.convert(helper, item)

        helper.setText(R.id.tvBrandName, item.brandName)
        
        //注册点击事件
        helper.addOnClickListener(R.id.ivDelete)
    }
}

在界面上调用 :



//添加adapter中的子控件事件    
vehicleBrandAdapter.setOnItemChildClickListener { _, view, position ->
            if (view.id == R.id.ivDelete) {
    			//do what you want to do 
            }
        }

3.2 复杂布局运用

3.2.1 嵌套布局

界面中RecycleView中嵌套一个横向的RecycleView。先看看我们实现的效果图:

RecycleView中使用总结以及在项目中的实际运用场景总结

在布局文件中:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="10dp"
    android:orientation="vertical">

    <!--标题-->
    <TextView
        android:id="@+id/titleLayout"
        android:layout_width="match_parent"
        android:layout_height="44"
        app:layout_constraintTop_toTopOf="parent"
        android:text="选择4s店"/>

    <!--选择日期-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/common_bg_white"
        android:orientation="horizontal">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rvCalendar"
            android:layout_width="match_parent"
            android:layout_height="55dp"
            android:layout_marginStart="15dp"
            android:layout_marginTop="@dimen/dp_5"
            android:orientation="horizontal"
            android:layout_marginEnd="@dimen/dp_15"
            tools:layoutManager=" LinearLayoutManager"
            android:layout_marginBottom="@dimen/dp_5"
            tools:listitem="@layout/shop4s_maintenance_item_calendar" />
    </LinearLayout>

    <!-- 门店地址和时间 item-->
    <cc.xxx.widget.status.StatusLayout
        android:id="@+id/statusLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycleList"
            style="@style/CommonRecyclerViewStyle"
            android:background="@color/common_bg_white"
            tools:listitem="@layout/shop4s_maintenance_item_store" />
    </cc.xxx.widget.status.StatusLayout>

</LinearLayout>

预览效果如图所示:

RecycleView中使用总结以及在项目中的实际运用场景总结

Tips: Android Studio 预览RecyclerView 小技巧

加上tools命名空间后使用以下属性可以得到直观的预览:

tools:layoutManager="GridLayoutManager"
tools:orientation="horizontal"
tools:listitem="4"
tools:spanCount ="2"

其中的门店地址和时间 item布局中又嵌套了一个RecycleView用来显示时间段的选择。(代码略)

在Adapter中:有两个,一个是最外层的Adapter,取名为ChooseStoreItemAdapter,

另一个是子项中的Adapter,取名为 ChooseTimeSubItemAdapter

ChooseStoreItemAdapter伪代码如下:

class ChooseStoreItemAdapter : MyBaseAdapter<ListStoreEntity>(R.layout.shop4s_item_store) {
    override fun convert(helper: BaseViewHolder, item: ListStoreEntity) {
        super.convert(helper, item)}

       helper.setText(R.id.tvDescription, item.storeNickName)
        helper.setText(R.id.tvAddress, item.address)
 
       //时间列表处理 处理子项recycleView
        handleRecyclerView(helper.getView(R.id.recycleListTimeItem), item.timePrices,item)
}
       private fun handleRecyclerView(recycle: RecyclerView, list: ArrayList<ListStoreEntity.TimePricesEntity>,storeEntity: ListStoreEntity) {

        if (recycle.layoutManager == null) {
            recycle.layoutManager = GridLayoutManager(recycle.context, 3)
            //添加间距,必须放到这里,防止刷新时界面错位 代码略
        }
        var adapter = recycle.adapter
        if (adapter == null) {
            adapter = ChooseTimeSubItemAdapter(storeEntity)
            recycle.adapter = adapter
        }
        (adapter as ChooseTimeSubItemAdapter).setNewData(list)
    }
             //添加点击事件 代码略
   
}

ChooseTimeSubItemAdapter伪代码如下:

class ChooseTimeSubItemAdapter(storeEntity: ListStoreEntity) :
    MyBaseAdapter<TimePricesEntity>(R.layout.shop4s_maintenance_item_time) {

    override fun convert(helper: BaseViewHolder, item: TimePricesEntity) {
        super.convert(helper, item)
        //组件赋值
        helper.setText(R.id.tvPrice, item.salePrice)
        //……
        }
   }

最后在主界面中调用:

private val mAdapterStore = ChooseStoreItemAdapter()

    private fun initRecycleView() {        
        //初始化店铺
        recycleList.layoutManager = LinearLayoutManager(this)
        mAdapterStore.bindToRecyclerView(recycleList)
    }
    
    private fun handleData(data: StoreEntity) {
       //网络获取数据后 绑定值
            mAdapterStore.setNewData(data.list)
            //……
        }
    }
//点击事件处理……

3.2.2 多布局

优化前:

public class MultipleItemAdapter extends BaseQuickAdapter<String> {
   private final int TEXT_TYPE = 1;
   private int mTextLayoutResId; 
    
   public MultipleItemAdapter(Context context, List data, int... layoutResId) {
        super(context, layoutResId[0], data);
        mTextLayoutResId = layoutResId[1];
    }
    @Override
    protected int getDefItemViewType(int position) {
        if (position % 2 == 0)
            return TEXT_TYPE;
        return super.getDefItemViewType(position);
   } 
   @Override
    protected BaseViewHolder onCreateDefViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TEXT_TYPE)
            return new TextViewHolder(getItemView(mTextLayoutResId, parent));
        return super.onCreateDefViewHolder(parent, viewType);
    }
    @Override
    protected void onBindDefViewHolder(BaseViewHolder holder, String item) {
        if (holder instanceof TextViewHolder)
            holder.setText(R.id.tv, item); 
   }
    @Override
    protected void convert(BaseViewHolder helper, String item) {
        helper.setImageUrl(R.id.iv, item);
    }
    public class TextViewHolder extends BaseViewHolder {
        public TextViewHolder(View itemView) {
            super(itemView.getContext(), itemView);
        }
    }
}

优化后:

public class MultipleItemQuickAdapter extends BaseMultiItemQuickAdapter<MultipleItem> {
    public MultipleItemQuickAdapter(Context context, List data) {
        super(context, data);
        addItmeType(MultipleItem.TEXT, R.layout.text_view);
        addItmeType(MultipleItem.IMG, R.layout.image_view);
    }
    @Override
    protected void convert(BaseViewHolder helper, MultipleItem item) {
        switch (helper.getItemViewType()) {
            case MultipleItem.TEXT:
                helper.setImageUrl(R.id.tv, item.getContent());
                break;
            case MultipleItem.IMG:
                helper.setImageUrl(R.id.iv, item.getContent());
                break;
        }
    }
}

原理分析:

基本用法中,多个不同类型的布局一定会用到getItemViewTypeonCreateViewHolder 优化后的代码中代码中却省略了,如何做到的?

优化前:getItemViewType

  @Override
    protected int getDefItemViewType(int position) {
        if (position % 2 == 0)
            return TEXT_TYPE;
        return super.getDefItemViewType(position);
   } 

BaseMultiItemQuickAdapter中:

   @Override
    protected int getDefItemViewType(int position) {
        T item = mData.get(position);
        if (item != null) {
            return item.getItemType();
        }
        return DEFAULT_VIEW_TYPE;
    }

这样做,在填充数据的时候就把view type给添加进去了。

优化前:onCreateViewHolder

 @Override
    protected BaseViewHolder onCreateDefViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TEXT_TYPE)
            return new TextViewHolder(getItemView(mTextLayoutResId, parent));
        return super.onCreateDefViewHolder(parent, viewType);
    }

优化后:

public abstract class BaseMultiItemQuickAdapter<T extends MultiItemEntity, K extends BaseViewHolder> extends BaseQuickAdapter<T, K> {    
/**
     * layouts indexed with their types
     */
    private SparseIntArray layouts;

    private static final int DEFAULT_VIEW_TYPE = -0xff;
    public static final int TYPE_NOT_FOUND = -404;

    @Override
    protected K onCreateDefViewHolder(ViewGroup parent, int viewType) {
        return createBaseViewHolder(parent, getLayoutId(viewType));
    }

    private int getLayoutId(int viewType) {
        return layouts.get(viewType, TYPE_NOT_FOUND);
    }

    protected void addItemType(int type, @LayoutRes int layoutResId) {
        if (layouts == null) {
            layouts = new SparseIntArray();
        }
        layouts.put(type, layoutResId);
    }   
}

原理分析:addItemType中以type为key , layoutResId为 value 以 key-value键值对的形式存储到 SparseIntArray中,在onCreateDefViewHolder中再根据viewType来获取相应的 layoutResId。

在项目中消息推送模块我们用到了多布局。

实现效果如下图所示:

RecycleView中使用总结以及在项目中的实际运用场景总结

实现伪代码如下:

class DrivingMulDialogueAdapter : MyBaseMultiItemAdapter<MessageListEntity.MessageEntity>() {

    companion object {
        //立即添加
        const val ITEM_TYPE_ADD_NOW = 1
        //查询分数
        const val ITEM_TYPE_QUERY_SCORE = 2
        //以上一个选择 其他两个选择
        //我已换证
        const val ITEM_TYPE_HAVE_CHANGED_DRIVING_LICENSE = 3
    }

    //添加不同类型,并关联对应的布局文件
    init {
        addItemType(
            ITEM_TYPE_ADD_NOW,
            R.layout.driving_license_dialogue_recycle_item_add
        )
        addItemType(
            ITEM_TYPE_QUERY_SCORE,
            R.layout.driving_license_dialogue_recycle_item_add
        )
        addItemType(
            ITEM_TYPE_HAVE_CHANGED_DRIVING_LICENSE,
            R.layout.driving_license_dialogue_recycle_item_have_changed
        )
    }

    //不同类型的布局进行不同业务的处理
    override fun convert(helper: BaseViewHolder, item: MessageEntity) {
        super.convert(helper, item)
        helper.setText(R.id.tvContent, item.dialogContent)
        helper.setText(R.id.tvDialogTitle, item.dialogueTitle)
        helper.setText(R.id.tvTime, TimeTool.getFullTime(item.addTime))

        when (item.dialogueType) {
            //立即添加
            ITEM_TYPE_ADD_NOW -> {

                helper.setText(R.id.tvOneSelected, ResourcesUtil.getString(R.string.driving_license_add))
                helper.addOnClickListener(R.id.tvOneSelected)
            }
            //查询分数
            ITEM_TYPE_QUERY_SCORE -> {
                helper.setText(R.id.tvOneSelected, ResourcesUtil.getString(R.string.driving_license_query_score))
                helper.addOnClickListener(R.id.tvOneSelected)
            }
            //我已换证
            ITEM_TYPE_HAVE_CHANGED_DRIVING_LICENSE, ITEM_TYPE_TO_TIME-> {
                helper.setText(R.id.tvFirstSelected, ResourcesUtil.getString(R.string.driving_license_changed))
                helper.setText(R.id.tvSecondSelected, ResourcesUtil.getString(R.string.driving_license_look))
                helper.addOnClickListener(R.id.tvFirstSelected,R.id.tvSecondSelected)
            }
          //其他类型 省略
        }
    }
}

在Activity中调用:

override fun initListener() {
        super.initListener()

    //监听 子控件事件,同时判断消息的类型,做不同处理
        adapter.setOnItemChildClickListener { _, view, position ->
            val item = adapter.data[position]
             when (view.id) {
                R.id.tvOneSelected -> {

                    if (item.dialogueType == ITEM_TYPE_ADD_NOW) {
                        // TODO:
                    } else {
                        // TODO:
                    }

                }

                R.id.tvFirstSelected -> {
                    // TODO:
                }
}

3.3 如何集成下拉刷新和上拉加载更多

BaseRecyclerViewAdapterHelper中已经集成了 上拉加载更多功能,下拉加载刷新可以直接用Google官方的swipeRefreshLayout,也可以用其他三方的组件。

在项目中使用时 可以封装一个工具类,如这样的

/**
*  处理刷新、加载完成、加载失败、自定义加载状态布局文件等等。
**/
class RefreshAndLoadMoreUtils(
        private val rvList: RecyclerView,
        private val swipeRefreshLayout: SwipeRefreshLayout,
        private val adapter: BaseQuickAdapter<*, *>,
        private val refresh: IRefreshAndLoadMore,
        private val loadMoreView: MyLoadMoreView
) {  

init {
        initListener()
    }
private fun initListener() {
        // 下拉刷新监听
        swipeRefreshLayout.setOnRefreshListener {
         
            // 调用下拉刷新
            refresh()
        }

        // 设置加载更多时显示的布局
        adapter.setLoadMoreView(loadMoreView)

        // 加载更多监听
        adapter.disableLoadMoreIfNotFullPage(recycleList)

        adapter.setOnLoadMoreListener({
            // 禁用下拉刷新
            swipeRefreshLayout.isEnabled = false
            // 调用上拉加载更多
            loadMore()
        }, recycleList)
    }

   private fun refresh() {
      
   }
 
}

同于获取列表的布局比较通用,可以抽取出来进一步封装。封装一个通用的布局、通用的下拉刷新功能、上拉加载更多等通用的功能,并重写不同界面中相同方法实现接口(如IRecycler)。然后在不同的界面中直接继承通用的CommmonRecycleActivity处理逻辑即可。此处不在展示详细代码,有不清楚的朋友,可以留言讨论。


interface IRecycler<D : AbsLoadList<T>, T> { //泛型类中 D标示 界面的类,T 标示数据    
    //请示网络接口地址Url实现
    //请求网络接口的参数params实现
    //不同界面中的自定义适配器Adapter实现
    // ……
}

abstract class AbsLoadList<T> {
    // 滑动方向:1:向上滑动(加载下一页数据);2:向下滑动(加载上一页数据)
    var slide = 0
    // 页首参数, 向下滑动时使用
    var top = ""
    // 页尾参数, 向上滑动时使用
    var bottom = ""
    // 是否有下一页数据
    var hasMore = false
    // 数据列表
    var list = ArrayList<T>()
}

3.4 其他场景运用

  • 左右滑动删除
  • 滑动 冲突
  • 订单明细中显示不全如何解决
  • TV端的运用

后期有时间再补充。

4. 总结

本文 总结了RecycleView的使用情况,不同时期的项目 用到了不同的技术,即分析了基本用法,又分析了优化后的版本迭代情况,最后结合项目实际使用给出了几种运用场景。在介绍实际使用时,重在总结当时的使用思路,具体实现得根据不同业务来完善,基础运用+思路 就可以实现开发中常用的业务功能。在总结时想要展开的知识点太多,无法一一展开。后期会不断完善,如有不对的地方,欢迎大家指正。To be continuted……

参考资料:

0.RecycleView开源项目BRVAH分析

1.Android 解决切换Tab后RecycleView/ListView自动回滚至顶部条目的Bug

2.RecyclerView使用常见的问题和需求

3.Android:RecyclerView 的使用,有这一篇就够了

4.RecyclerView的基本设计结构

5.RecyclerView优秀文集

6.读源码-用设计模式解析RecyclerView

7.Android 复杂的多类型列表视图新写法:MultiType 3.0

8.RecyclerView.Adapter优化了吗

9.BaseRecyclerAdapter之添加不同布局(优化篇)