1.RecyclerView的基本用法
与之前的控件都不同,RecyclerView(RV)属于新增控件,要想在之前版本能够使用,需要在build.gradle中加入RV库的依赖。
新建RecyclerViewTest项目,打开app/build.gradle文件,加入依赖如下:
然后写入布局,需要注意的是,因为RV并不是内置在系统SDK中的,所以需要写出完整的包路径(其实会自动补全的)。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
随后,我们需要实现和ListView同样的页面,先把图片文件夹和Fruit类及fruit_item.xml也复制过来,把里面的button删掉。
接下来需要为RV准备一个适配器,新建FruitAdapter类继承自RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder,这个类型是我们自己定义的内部类。
代码如下:
class FruitAdapter(val fruitList: List<Fruit>):
RecyclerView.Adapter<FruitAdapter.ViewHolder>(){
inner class ViewHolder(view: View): RecyclerView.ViewHolder(view){
val fruitImage: ImageView = view.findViewById(R.id.fruitImage)
val fruitName: TextView = view.findViewById(R.id.fruitName)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fruit_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val fruit = fruitList[position]
holder.fruitImage.setImageResource(fruit.imageId)
holder.fruitName.text = fruit.name
}
override fun getItemCount(): Int {
return fruitList.size
}
}
ViewHolder
onCreateViewHolder()
onBindViewHolder()
getItemCount()
这是RV适配器的标准写法。首先定义一个内部类ViewHolder,它要继承自RV.ViewHolder,ViewHolder的著构造函数中要传入一个View参数,这个参数通常就是RV子项的最外层布局,这样就可以通过findViewById()
方法来获取布局中的ImageView和TextView实例了。
FruitAdapter必须重写RV.Adapter的三个方法:
onCreateViewHolder()
用于创建ViewHolder实例,将fruit_item布局加载进来,然后创建一个ViewHolder实例;并把加载出来的布局传入构造函数中,最后将ViewHolder的实例返回。
onBindViewHolder()
用于对RV子项数据进行赋值,会在每个子项被滚动到屏幕内的时候执行,这里通过position参数的到当前项的Fruit实例,然后再将数据设置到ViewHolder的ImageView和TextView当中即可。
getItemCount()
用于告诉RV一共有多少子项,直接返回数据源的长度就可以了。
适配器准备好了,就开始使用RV了,修改MainActivity中的代码:
private val fruitList = ArrayList<Fruit>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initFruits()
val layoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = layoutManager
val adapter = FruitAdapter(fruitList)
recyclerView.adapter = adapter
}
private fun initFruits(){
repeat(2){
fruitList.apply {
add(Fruit("Apple", R.drawable.apple_pic))
add(Fruit("Banana", R.drawable.banana_pic))
add(Fruit("Orange", R.drawable.orange_pic))
add(Fruit("Watermelon", R.drawable.watermelon_pic))
add(Fruit("Pear", R.drawable.pear_pic))
add(Fruit("Grape", R.drawable.grape_pic))
add(Fruit("Pineapple", R.drawable.pineapple_pic))
add(Fruit("Strawberry", R.drawable.strawberry_pic))
add(Fruit("Cherry", R.drawable.cherry_pic))
add(Fruit("Mango", R.drawable.mango_pic))
}
}
}
一样的,先初始化水果数据,然后在recyclerView中设置一个LinearLayoutManager的实例,指定RV的布局为线性布局,这样展现效果就和ListView一样了;其次将水果数据传给FruitAdapter的实例,并将其配置给recyclerView,这样就可以了。
运行结果:
2.横向滚动和瀑布流布局
2.1 横向滚动
现在尝试一下使用RV进行横向的滚动。
首先对fruit_item进行修改,因为现在的布局是水平排列的,不适合横向滚动,需要改成垂直排列比较合理。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="80dp"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/fruitImage"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"/>
<TextView
android:id="@+id/fruitName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"/>
</LinearLayout>
MainActivity增添一行代码,配置布局排列方式为horizontal
val layoutManager = LinearLayoutManager(this)
layoutManager.orientation = LinearLayoutManager.HORIZONTAL
recyclerView.layoutManager = layoutManager
2.2 瀑布流布局
ListView的布局排列时由自身去管理的,而RV则将这个工作叫个了LayoutManager,它制定了一套可扩展的布局排列接口,子类要按照接口的规范来实现,就能制定出各种不同排列方式的布局了。除了LinearLayoutManager之外,RV还给我们提供了GridLayoutManager网络布局 和 StaggeredGridLayoutManager瀑布流布局 这两种内置的布局排列方式。
接下来试试瀑布流布局
修改fruit_item布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<ImageView
android:id="@+id/fruitImage"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"/>
<TextView
android:id="@+id/fruitName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginTop="10dp"/>
</LinearLayout>
其实只改了一点点
然后修改MainActivity中的代码:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initFruits()
// val layoutManager = LinearLayoutManager(this)
// layoutManager.orientation = LinearLayoutManager.HORIZONTAL
// recyclerView.layoutManager = layoutManager
val layoutManager = StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL)
recyclerView.layoutManager = layoutManager
val adapter = FruitAdapter(fruitList)
recyclerView.adapter = adapter
}
private fun initFruits(){
repeat(2){
fruitList.apply {
add(Fruit(getRandomLengthString("Apple"), R.drawable.apple_pic))
add(Fruit(getRandomLengthString("Banana"), R.drawable.banana_pic))
add(Fruit(getRandomLengthString("Orange"), R.drawable.orange_pic))
add(Fruit(getRandomLengthString("Watermelon"), R.drawable.watermelon_pic))
add(Fruit(getRandomLengthString("Pear"), R.drawable.pear_pic))
add(Fruit(getRandomLengthString("Grape"), R.drawable.grape_pic))
add(Fruit(getRandomLengthString("Pineapple"), R.drawable.pineapple_pic))
add(Fruit(getRandomLengthString("Strawberry"), R.drawable.strawberry_pic))
add(Fruit(getRandomLengthString("Cherry"), R.drawable.cherry_pic))
add(Fruit(getRandomLengthString("Mango"), R.drawable.mango_pic))
}
}
}
private fun getRandomLengthString(str: String): String{
val n = (1..20).random()
val builder = StringBuilder()
repeat(n){
builder.append(str)
}
return builder.toString()
}
修改为在声明layoutManager时指定StaggeredGridLayoutManager,其构造函数有两个参数,第一个指定布局的列数,第二个指定布局的排列方向。由于瀑布流布局需要各个子项的高度不一致才能看出明显的效果,所以这里写了一个方法将TextView中的内容进行了随机的加长,使长度层次不齐。
运行结果:
3.RecyclerView的点击事件
不同于ListView,RV并没有提供方法去注册点击事件,我们需要自己给子项具体的View去注册点击事件。
修改FruitAdapter里的代码
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fruit_item, parent, false)
val viewHolder = ViewHolder(view)
viewHolder.itemView.setOnClickListener{
val position = viewHolder.adapterPosition
val fruit = fruitList[position]
Toast.makeText(parent.context, "You clicked view ${fruit.name}", Toast.LENGTH_SHORT).show()
}
viewHolder.fruitImage.setOnClickListener{
val position = viewHolder.adapterPosition
val fruit = fruitList[position]
Toast.makeText(parent.context, "You clicked image ${fruit.name}", Toast.LENGTH_SHORT).show()
}
return viewHolder
}
上述代码为最外层布局和ImageView都注册了点击事件,RV可以轻松实现子项中任意控件或布局的点击事件。现在两个点击事件中获取用户点击的position,通过position拿到相应的Fruit实例,分别弹出不同内容。
运行结果:
点击图片返回图片的点击结果,点击文字部分,由于TextView没有注册点击事件,因此点击文字是个事件会被子项的最外层布局捕获。