前言
在 Android 5.0 之前,如果你需要展示一个可以滚动的列表,我们用的是 ListView 控件。Android 5.0 后,官方在 support-v7 包推出了一个新的控件:RecyclerView,用来替代 ListView,解决 ListView 的一些问题和缺陷。可以说,RecyclerView 是一个先进的、灵活的加强版 ListView。
本文将使用
kotlin作为开发语言展示示例代码,实现一个完整的通用的RecyclerView.Adapter,并用它来实现一个类似苹果 AppStore 的典型布局。
RecyclerView 的基础使用
如果你已熟悉使用
RecyclerView,可以跳过此节
要使用 RecyclerView,我们需要导入 support-v7 库:
- 打开 app module 的
build.gradle - 添加库的依赖
dependencies { implementation 'com.android.support:recyclerview-v7:28.0.0' }
讯享网 - 在 layout 文件中引入(可以作为整个布局的父容器)
讯享网
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent"/> - 新建一个 Adapter 类继承
RecyclerView.Adapter并重写其以下方法
这里我们把类命名为fun onCreateViewHolder(parent: ViewGroup, type: Int): VH fun getItemCount(): Int fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int)RecyclerAdapter讯享网
class RecyclerAdapter(private val dataSet: Array<String>) : RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() { class ViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView) // 供LayoutManager调用,创建新的视图 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerAdapter.ViewHolder { val textView = LayoutInflater.from(parent.context) .inflate(R.layout.recycler_text_view, parent, false) as TextView return ViewHolder(textView) } // 供LayoutManager调用,绑定视图数据 override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { holder.textView.text = dataSet[position] } // 供LayoutManager调用,返回视图数量大小 override fun getItemCount() = dataSet.size } - 将
LayoutManager、Adapter和RecyclerView关联起来class RecyclerActivity: Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.recycler_activity) val dataSet = arrayListOf("one", "two", "three") val layoutManager = LinearLayoutManager(this) val adapter = RecyclerAdapter(dataSet) recyclerView = findViewById<RecyclerView>(R.id.recyclerView) recyclerView.layoutManager = linearLayoutManager recyclerView.adapter = adapter } }
通过以上5个步骤,我们用 RecyclerView 完成了最基本的布局和数据绑定
现在我们思考一个问题:在开发过程中,对于每个类似滚动列表或者网格甚至瀑布流的页面,我们是否都需要对每个页面都创建一个属于该页面的
Adapter,然后重写第4步中的每一个方法?
答案很明显是否定的
接下来我们来看看如何编写一个通用的 Adapter,以便让它胜任各类复杂布局,以避免我们每开发一个页面都创建一个属于该页面的 Adapter 的情况
一步步实现 “万能的” Adapter
要胜任各种布局,就要从 Adapter 中有关视图的方法中寻找解决方法:
讯享网// onCreateViewHolder 方法通过 viewType 创建视图 fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerAdapter.ViewHolder
onCreateViewHolder 第二个参数是 viewType,也就是视图的类型。可以看出,通过这个参数,我们可以只在一个 Adapter 中便创建出 Int.MAX_VALUE 也就是 种类型的视图,我们的万能 Adapter 就从这里开始突破
RecyclerView.Adapter 中还有一个方法:
// 根据 position 返回 viewType fun getItemViewType(position: Int): Int
所有关键点就在 viewType 这里!因此我们的 ReclclerAdapter 必须重写 getItemViewType 方法,用于返回我们想要的 viewType 给视图创建者 LayoutManager
在这里我们定义一个通用的 ViewModel 类,用这个类的 layout 属性来保存视图模型中每一条数据的视图类型 viewType,我们还需要为每一个 ViewModel 标识它的 spanSize,以便我们使用 GridLayoutManager(或者其它 LayoutManager) 时决定这个视图所占的行数或者列数,最后用类型为 Any 的属性 value 来保存任意的实体数据:
讯享网/* * ViewModel * RecyclerAdapter子类 * @param layout: 就是我们的viewType * @param spanSize: 当使用GridLayoutManager时View占据的列数(水平布局时为行数) * @param value: 保存各类实体数据 */ data class ViewModel( val layout: Int, val spanSize: Int, val value: Any)
于是我们的 RecyclerAdapter 有如下代码:
// RecyclerAdapter ... private val models = ArrayList<ViewModel>() override fun getItemViewType(position: Int): Int {
return models[position].layout } override fun onCreateViewHolder(parent: ViewGroup, type: Int) : RecyclerView.ViewHolder {
// type is layout // see fun getItemViewType val view = LayoutInflater.from(parent.context).inflate(type, parent, false) return ViewHolder(view) } /* * 当使用GridLayoutManager时,我们可以这样: * layoutManager.spanSizeLookup = adapter.getSpanSizeLookup() */ fun getSpanSizeLookup(): GridLayoutManager.SpanSizeLookup {
return object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
// empty spanCount must equal to GridLayoutManager's spanCount return models[position].spanSize } } } class ViewHolder(val view: View): RecyclerView.ViewHolder(view) ...
也就是说,我们平时编写的 layout 文件夹中的 xml 文件就是 viewType !
于是,我们就可以用下面示例代码实现混合布局(3种布局),用 spanSize 来决定行数或者列数:

讯享网// Some Activity or Fragment ... val models = arrayListOf( RecyclerAdapter.ViewModel( R.layout.layout_1, 2, "I'm Layout 1" ) RecyclerAdapter.ViewModel( R.layout.layout_2, 1, "I'm Layout 2" ) RecyclerAdapter.ViewModel( R.layout.layout_3, 1, "I'm Layout 3" ) ) val adapter = RecyclerAdapter(models) ...
我们甚至还可以在 RecycleView 中嵌套 RecycleView,用 RecyclerAdapter 实现纵横交错的布局。
上面我们用 onCreateViewHolder 方法通过核心参数 viewType 在同一个 Adapter 中创建出我们想要的各种layout视图,接下来我们需要对视图的数据进行绑定。视图的引用保存在 RecyclerAdapter.ViewHolder 中,这里我们优化一下这个类:
class ViewHolder(val context: Context, val view: View) : RecyclerView.ViewHolder(view) {
private val views: SparseArray<View> = SparseArray() fun <T: View> findView(key: Int): T {
var v = views[key] if (v == null) {
v = view.findViewById<T>(key) views.put(key, v) } @Suppress("UNCHECKED_CAST") return v as T } }
各类View是以树的形式组织的,而且 RecyclerView 会对 viewType 相同的视图进行回收重用,也就是 Recycle,为了避免大量视图绑定时频繁调用 findViewById 方法(递归),我们使用 SparseArray 来缓存视图中的子视图(其实优化效果并不明显 _)。
接下来我们定义一个函数别名用作数据绑定的接口:
讯享网typealias OnModelViewBind = ( index: Int, viewModel: RecyclerAdapter.ViewModel, viewHolder: RecyclerAdapter.ViewHolder ) -> Unit
index 为视图模型的下标,等同于 position。
为 RecyclerAdapter 定义一个函数引用:
// RecyclerAdapter ... var onModelViewBind: OnModelViewBind? = null ...
然后 onBindViewHolder 函数实现如下:
讯享网// RecyclerAdapter override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
val model = models[position] onModelViewBind?.invoke(position, model, viewHolder) }
于是在外部代码中,视图与数据的绑定代码就这样写:
// Some Activity or Fragment adapter.onModelViewBind = {
index, viewModel, viewHolder -> when (viewModel.layout) {
R.layout.layout_1 -> {
// get view from viewHolder.findView function // get model form viewModel.value } R.layout.layout_2 -> {
// get view from viewHolder.findView function // get model form viewModel.value } R.layout.layout_3 -> {
// get view from viewHolder.findView function // get model form viewModel.value } } }
我们还需要两个函数来为 RecyclerAdapter 初始化和添加 ViewModel,请仔细看它们的区别:
讯享网// RecyclerAdapter ... / * initial the items of recycler adapter * @param items items to display */ fun setItems(items: ArrayList<ViewModel>) {
models.clear() models.addAll(items) notifyDataSetChanged() } / * add the items of recycler adapter * @param items items to add */ fun addItems(items: ArrayList<ViewModel>) {
models.addAll(items) notifyDataSetChanged() } ...
接下来我们给 RecyclerView 的每一个视图项添加单击事件和长按事件,先定义两个函数别名作为单击事件和长按事件的接口:
... typealias OnModelViewClick = ( index: Int, viewModel: RecyclerAdapter.ViewModel ) -> Unit typealias OnModelViewLongClick = ( index: Int, viewModel: RecyclerAdapter.ViewModel ) -> Unit ...
然后为 RecyclerAdapter 定义两个函数引用:
讯享网 // RecyclerAdapter ... var onModelViewClick: OnModelViewClick? = null var onModelViewLongClick: OnModelViewLongClick? = null ...
继承 View.OnClickListener 和 View.OnLongClickListener 接口并实现其方法:
class RecyclerAdapter(private val context: Context, private val spanCount: Int = 1) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), View.OnClickListener, View.OnLongClickListener {
override fun onClick(view: View) {
val position = recyclerView.getChildAdapterPosition(view) if (!models.isEmpty() && position >= 0) {
val model = models[position] onModelViewClick?.invoke(position, model) } } override fun onLongClick(view: View): Boolean {
val position = recyclerView.getChildAdapterPosition(view) if (!models.isEmpty() && position >= 0) {
val model = models[position] onModelViewLongClick?.invoke(position, model) } return true } }
修改 onCreateViewHolder 方法:
讯享网override fun onCreateViewHolder(parent: ViewGroup, type: Int): RecyclerView.ViewHolder {
// type is layout // see fun getItemViewType val view = LayoutInflater.from(parent.context).inflate(type, parent, false) view.setOnClickListener(this) view.setOnLongClickListener(this) return ViewHolder(view) }
这样便实现了 RecyclerView 的视图项的单击和长按事件。
我们还可以给 RecyclerAdapter 添加更多的功能,用来支持更多的场景:
- 视图模型数量为零时显示一个
空视图 - 视图模型的多选
- 为
RecyclerView添加下拉刷新和上拉加载更多 - …
文章开头提到的 App Store 典型布局以及更多具体详情示例,可以在我的 github 项目 recyclerkit 中查看

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/34043.html