RecycleView中使用总结以及在项目中的实际运用场景总结(持续更新)

前言

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

1.RecycleView

官方对RecyclerView的描述:

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

1.1 使用RecycleView的优缺点

优点:

  • 内部实现了回收机制,无需我们考虑View的复用情况
  • 支持不同方向,不同排版模式,实现多种展现数据的形式,涵盖了ListView,GridView,瀑布流等数据表现的形式
  • RecycleView强制封装ViewHolder
  • 可设置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的基本用法 ,然后在实际使用中,需要不断完善。比如需要考虑下拉刷新、添加头文件或是尾部布局、或是多种类型的布局,还要考虑局部刷新、网络异常、数据异常时不同的布局显示等等。

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

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
    }
}

实现效果如图所示:

在这里插入图片描述

注意:为了实现如上图的线性和网格的混合视图效果,只需要一个 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.xx.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.xx.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。由于业务较为复杂,代码实现与开源代码示例类似,此处不再展示代码。仅展示一下效果图。

在这里插入图片描述

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。先看看我们实现的效果图:

在这里插入图片描述

在布局文件中:

<?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>

预览效果如图所示:

在这里插入图片描述

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。

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

实现效果如下图所示:

在这里插入图片描述

实现伪代码如下:

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之添加不同布局(优化篇)

发布了50 篇原创文章 · 获赞 177 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/jun5753/article/details/102862126