RecyclerView的基础篇-列表的展示
2020-08-30 本文已影响0人
dashingqi
Android_Banner.jpg
简介
- 在本篇文章中,我将介绍使用RecyclerView展示一个列表数据
- 展示的过程中,将介绍使用常规的写法,配合DataBinding的写法,以及使用第三方框架(binding-collection-adapter
) -
这三种写法最终展示的效果如下图所示
UI展示
使用
- 本文中使用网络图片,需要添加Glide
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
- 同时别忘了给权限
<uses-permission android:name="android.permission.INTERNET" />
- 本文中使用的网络图片地址
//准备的图片地址
private var imgUrls = mutableListOf(
"https://img.fulaishiji.com/images/goods/19883/big/03957c4d-6869-4cef-ad3e-824852f9da2b_800x800.png",
"https://img.fulaishiji.com/images/goods/19307/big/e2635d9a-d5eb-4acf-b08c-44483a8554e2_800x800.jpg",
"https://img.fulaishiji.com/images/goods/17249/big/3389ee28-5b14-4b55-8f5d-64a00d5deb37_800x800.jpg",
"https://img.fulaishiji.com/images/goods/12461/big/b0be6cbd-3164-470b-b355-d9b41b0ce0e6_800x800.jpg",
"https://img.fulaishiji.com/images/goods/16743/big/65843bf9-7ba1-48d0-884e-aefeebf9b635_964x964.jpg",
"https://img.fulaishiji.com/images/goods/12534/big/3dabd1b5-f37f-4320-9832-fccbdc8e1ecf_800x800.jpg",
"https://img.fulaishiji.com/images/goods/14897/big/e1c774c9-7003-4a4a-9e12-1bdf1d729457_800x800.jpg",
"https://img.fulaishiji.com/images/goods/12753/middle/95f064c5-8bfc-44d6-a7d8-5058cb3f93e7_800x800.jpg",
"https://img.fulaishiji.com/images/goods/10396/middle/e174305c-b32c-4b9d-a0b1-a14de0ae011a_3648x3648.jpg",
"https://img.fulaishiji.com/images/goods/17824/middle/19d6253c-1ef9-48f0-b63f-d01d2f354eee_2728x2728.jpg",
"https://img.fulaishiji.com/images/goods/17337/middle/9974521a-34c4-4daf-9c07-717dface9cce_800x800.jpg",
"https://img.fulaishiji.com/images/goods/13223/middle/a0a32982-3af5-45ae-9252-a554503ec829_800x800.jpg",
"https://img.fulaishiji.com/images/goods/19906/middle/41dca304-69b2-490f-b001-0b99fa4fcc60_1008x1008.jpg",
"https://img.fulaishiji.com/images/goods/19653/middle/c31cca8b-7468-4d5f-a06c-8427a7d01656_800x800.jpg"
)
常规写法
- activity_main.xml布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".GridLayoutManagerActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
- MainActivity中的代码
class GridLayoutManagerActivity : AppCompatActivity() {
private val items = ArrayList<Food>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_grid_layout_manager)
for (index in 0 until 200) {
val food = Food()
food.name = "name $index"
food.desc = "desc $index"
val position = (Math.random() * (imgUrls.size - 1)).toInt()
food.imgUrl = imgUrls[position]
items.add(food)
}
var adapter = GridLayoutAdapter(items)
rv.adapter = adapter
}
}
- Adapter中的代码
class GridLayoutAdapter(var foodList: ArrayList<Food> = ArrayList()) :
RecyclerView.Adapter<GridLayoutAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
var itemView = View.inflate(parent.context, R.layout.grid_layout_item, null)
return MyViewHolder(itemView)
}
override fun getItemCount(): Int {
return foodList.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemView.tvName.text = foodList[position].name
holder.itemView.tvDesc.text = foodList[position].desc
Glide.with(holder.itemView.context).load(foodList[position].imgUrl)
.into(holder.itemView.ivCover)
}
class MyViewHolder(item: View) : RecyclerView.ViewHolder(item) {
}
}
- item的布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/ivCover"
android:layout_width="80dp"
android:scaleType="centerCrop"
android:layout_height="80dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="4dp"
android:gravity="center"
android:textColor="#333333"
android:textSize="18sp"
app:layout_constraintLeft_toRightOf="@+id/ivCover"
app:layout_constraintTop_toTopOf="@+id/ivCover" />
<TextView
android:id="@+id/tvDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@+id/ivCover"
app:layout_constraintLeft_toLeftOf="@+id/tvName"
app:layout_constraintTop_toBottomOf="@+id/tvName" />
</androidx.constraintlayout.widget.ConstraintLayout>
配合DataBinding使用
- 启用DataBinding
// 在module 的 build.gradle中的 android{}中加入如下
dataBinding{
enabled true
}
- Activity的布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
- Item的布局文件
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="item"
type="com.dashingqi.module.recyclerview.Food" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/ivCover"
// 这里使用了DataBinding中自定义属性功能
// 自定义属性的文件在下文中也给出了
imageUrl="@{item.imgUrl}"
android:layout_width="80dp"
android:scaleType="centerCrop"
android:layout_height="80dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="4dp"
android:gravity="center"
android:text="@{item.name}"
android:textColor="#333333"
android:textSize="18sp"
app:layout_constraintLeft_toRightOf="@+id/ivCover"
app:layout_constraintTop_toTopOf="@+id/ivCover" />
<TextView
android:id="@+id/tvDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@{item.desc}"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@+id/ivCover"
app:layout_constraintLeft_toLeftOf="@+id/tvName"
app:layout_constraintTop_toBottomOf="@+id/tvName" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
- Activity中的代码
class MainActivity : AppCompatActivity() {
private var data = ArrayList<Food>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
configRv()
}
private fun configRv() {
for (index in 0 until 200) {
val food = Food()
food.name = "name $index"
food.desc = "desc $index"
val position = (Math.random() * (imgUrls.size - 1)).toInt()
food.imgUrl = imgUrls[position]
data.add(food)
}
// RecyclerView中 可以通过在xml文件中指定LayoutManager,这里我们是通过代码形式的
rv.layoutManager = LinearLayoutManager(this)
rv.adapter = RVAdapter(data)
}
}
- xml文件中指定RecyclerView的LayoutManager
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
// xml文件中指定的LayoutManager
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</androidx.constraintlayout.widget.ConstraintLayout>
- Adapter中的代码
class RVAdapter(var itemData: ArrayList<Food> = ArrayList()) :
RecyclerView.Adapter<RVAdapter.MyViewHolder>() {
private val TAG = "RVAdapter"
private var viewHolderCount = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
viewHolderCount++
Log.d(TAG, "onCreateViewHolder ---->$viewHolderCount")
var itemBinding = DataBindingUtil.inflate<RvItemBinding>(
LayoutInflater.from(parent.context),
R.layout.rv_item,
parent,
false
)
return MyViewHolder(itemBinding)
}
override fun getItemCount(): Int {
return itemData.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
Log.d(TAG, ": onBindViewHolder")
var dataBinding = holder.viewDataBinding as RvItemBinding
dataBinding.item = itemData[position]
dataBinding.executePendingBindings()
}
class MyViewHolder(var viewDataBinding: ViewDataBinding) :
RecyclerView.ViewHolder(viewDataBinding.root) {
}
companion object {
private const val TAG = "RVAdapter"
}
}
- 自定义属性文件
object ImageBindAdapter {
@JvmStatic
@BindingAdapter(value = ["imageUrl"], requireAll = false)
fun setImageUrl(view: ImageView, url: String) {
Glide.with(view.context).load(url).into(view)
}
}
binding-collection-adapter+DataBinding+ViewModel
- 上文中介绍的是把数据放到了Activity中,这回我们把数据交给ViewModel来处理
- binding-collection-adapter 框架可以帮我们省去写繁琐Adapter的过程
- 这里仅仅介绍使用方法,不做原理的介绍
引入binding-collection-adapter
implementation 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter:4.0.0'
implementation 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter-recyclerview:4.0.0'
- MainActivity中的代码
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val mainBinding =
DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
//绑定ViewModel
val foodViewModel = ViewModelProvider(this)[FoodViewModel::class.java]
mainBinding.viewModel = foodViewModel
}
}
- activity_main 文件
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="viewModel"
type="com.dashingqi.module.recyclerview.FoodViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
itemBinding="@{viewModel.itemBinding}"
items="@{viewModel.items}"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
- ViewModel文件
class FoodViewModel: ViewModel() {
val items = ObservableArrayList<Food>()
val itemBinding = ItemBinding.of<Food>(BR.item, R.layout.rv_item)
init {
for (index in 0 until 200) {
val food = Food()
food.name = "name $index"
food.desc = "desc $index"
val position = (Math.random() * (imgUrls.size - 1)).toInt()
food.imgUrl = imgUrls[position]
items.add(food)
}
}
}
- 由于Item和自定义属性的文件都是一样的,这里就不贴出了,可参照上文
总结
针对上述写法做一个对比
- 常规写法,需要自己写Adapter,需要在onBindViewHolder中设置数据
- DataBinding,同样需要写Adapter,相比较于常规写法,不需要在onBindViewHolder中设置每一个控件的数据数据,是把携带数据的Bean与item的xml文件进行绑定,数据设置的过程是在xml文件中设置的
- binding-collection-adapter:不用写Adapter,一切数据的设置显示都是在xml文件中操作,更符合数据驱动UI的显示,绑定数据源,数据源发生变化,UI自动显示。
实际项目
- 在目前的项目中,我们开始使用binding-collection-adapter 在mvvm框架中,配合kotlin,代码更加简洁,但是使用过程中也发现了,如果xml中有错误的写法,在编译的时候,会报错,但是错误的指明不是很直观,这也是目前我是用它发现的不足之处吧。