更强大的滚动控件:RecyclerView

ListView功能很强大,但并不是完美无缺的,比如如果不使用一些技巧来提升它的运行效率,那么ListView的性能就会非常差。还有,ListView的扩展性也不够好,它只能实现数据纵向滚动的效果,如果我们想实现横向滚动的话,ListView是做不到的。

Android提供了更强大的RecyclerView,可以轻松实现ListView的功能,同时优化了ListView的不足指出,未来会有更多的程序由ListView转为RecyclerView。

RecyclerView基本用法:

RecyclerView属于新增控件,为了让新增控件在所有Android系统版本上都能使用,Google将RecyclerView控件定义在了AndroidX当中,我们只需要在项目的build.gradle中添加RecyclerView库的依赖,就能保证在所有Android系统版本上都可以使用RecyclerView控件了。

在app下的 build.gradle中的dependencies内添加依赖代码:

    implementation 'androidx.recyclerview:recyclerview:1.2.1'

上述代码就表示将RecyclerView库引入我们的项目当中,其中除了版本号部分可能会变化,其
他部分是固定不变的。版本号可通过Alt+Enter自动生成最新版本。然后点击弹出的对话框中的Sync Now来进行同步,将RecyclerView库引入。

使用RecyclerView,在activity_main.xml中,代码如下:

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

由于RecyclerView不是内置控件,需要将完整的包路径写出来。

我们想实现和ListView相同的效果,需要将Fruit类(定义类)和fruit_item.xml(子项布局)复制过来,然后需要为RecyclerView创建一个适配器继承自 RecyclerView.Adapter并将泛型指定为FruitAdapter.ViewHolder()ViewHolder是我们在FruitAdapter中创建的内部类。

class FruitAdapter(private 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 = fruitList.size
}

这是RecyclerView适配器标准写法!!!!!!

首先、定义一个内部类  ViewHolder  ,它继承自  RecyclerView.ViewHolder  

然后ViewHolder  的主构造函数中要传入一个 View 参数,这个参数通常是  RecyclerView 的最外层布局,这样就可以通过 findViewById() 来获取  ImageView  和 TextView 的实例对象id。

FruitAdapter 中也有一个主构造函数,他有一个参数,用于把想要展示的数据源传入进来,这里的参数为  private val fruitList: List<Fruit>    ,后续将在这个数据源的基础上进行操作。

由于FruitAdapter是继承自RecyclerView.Adapter的,那么就必须重写onCreateViewHolder()onBindViewHolder()getItemCount()这3个方法。

onCreateViewHolder()用于创建 ViewHolder()实例,我们在这个方法中将fruit_item布局加载进来,然后创建一个ViewHolder实例,并把加载出来的布局传入构造函数当中,最后将ViewHolder的实例返回。

onBindViewHolder()用于对RecyclerView子项的数据进行赋值,会在每个子项被滚动到屏幕内的时候执行,这里我们通过position参数得到当前项的Fruit实例,然后再将数据设置到ViewHolder的ImageView和TextView当中即可。

getItemCount()方法就非常简单了,它用于告诉RecyclerView一共有多少子项,直接返回数据源的长度就可以了。

定义好之后,就可以使用了,在mainActivity中调用,代码如下:

class MainActivity : AppCompatActivity() {
    private val fruitList = ArrayList<Fruit>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruit()
        val layoutManager = LinearLayoutManager(this)
        val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
        recyclerView.layoutManager = layoutManager
        val adapter = FruitAdapter(fruitList)
        recyclerView.adapter = adapter
    }

    private fun initFruit() {
        repeat(2) {
            fruitList.add(Fruit("Apple", R.drawable.apple_pic))
            fruitList.add(Fruit("Banana", R.drawable.banana_pic))
            fruitList.add(Fruit("Orange", R.drawable.orange_pic))
            fruitList.add(Fruit("Watermelon", R.drawable.watermelon_pic))
            fruitList.add(Fruit("Pear", R.drawable.pear_pic))
            fruitList.add(Fruit("Grape", R.drawable.grape_pic))
            fruitList.add(Fruit("Pineapple", R.drawable.pineapple_pic))
            fruitList.add(Fruit("Strawberry", R.drawable.strawberry_pic))
            fruitList.add(Fruit("Cherry", R.drawable.cherry_pic))
            fruitList.add(Fruit("Mango", R.drawable.mango_pic))
        }
    }
}

在onCreate()方法中先创建了一个LinearLayoutManager对象,并将它设置到RecyclerView当中。

LayoutManager用于指定RecyclerView的布局方式,这里使用的LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果。

接下来我们创建了FruitAdapter的实例,并将水果数据传入FruitAdapter的构造函数中,最后调用
RecyclerViewsetAdapter()方法来完成适配器设置

利用RecyclerView实现横向滚动和瀑布流布局

首先修改fruit_item.xml中的布局,改成垂直排列。代码如下:

<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_marginLeft="10dp" />

    <TextView
        android:id="@+id/fruitName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginLeft="10dp" />

</LinearLayout>

将宽度修改为固定值80dp,是因为不同水果的长度不一致。

并在MainActivit中更改  layoutManager   为水平布局,代码为:

val layoutManager = LinearLayoutManager(this)
layoutManager.orientation = LinearLayoutManager.HORIZONTAL
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = layoutManager

为什么ListView很难或者根本无法实现的效果在RecyclerView上这么轻松就实现了呢?

这主要得益于RecyclerView出色的设计。ListView的布局排列是由自身去管理的,而RecyclerView则将这个工作交给了LayoutManager。LayoutManager制定了一套可扩展的布局排列接口,
子类只要按照接口的规范来实现,就能定制出各种不同排列方式的布局了。

除了LinearLayoutManager之外,RecyclerView还给我们提供了GridLayoutManagerStaggeredGridLayoutManager这两种内置的布局排列方式。GridLayoutManager可以用于实现网格布局,StaggeredGridLayoutManager可以用于实现瀑布流布局。

实现瀑布流布局

首先调整fruit_item.xml中的布局,如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    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_marginStart="10dp" />

    <TextView
        android:id="@+id/fruitName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_gravity="left" />

</LinearLayout>

将LinearLayout的宽度由80 dp改成了match_parent,因为瀑布流布局的宽度应该是根据布局的列数来自动适配的,而不是一个固定值

在MainActivity中修改代码如下:

val layoutManager = StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL)
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = layoutManager

创建了一个StaggeredGridLayoutManager的实例。StaggeredGridLayoutManager的构造函数接收两个参数:

第一个参数用于指定布局的列数,传入3表示会把布局分为3列;

第二个参数用于指定布局的排列方向,传入StaggeredGridLayoutManager.VERTICAL表示会让布局纵向排列。

只需要修改   layoutManager 就可以实现因为RecyclerView将布局的工作全部交给了layoutManager

RecyclerView的点击事件

不同于ListView的是,RecyclerView并没有提供类似于setOnItemClickListener()这样的注册监听器方法。而是需要自己给子项具体的View去注册点击事件。具体原因如下:

ListView在点击事件上的处理并不人性化,setOnItemClickListener()方法注册的是子项的点击事件,但如果我想点击的是子项里具体的某一个按钮呢?虽然ListView也能做到,但是实现起来就相对比较麻烦了。为此,RecyclerView干脆直接摒弃了子项点击事件的监听器,让所有的点击事件都由具体的View去注册,就再没有这个困扰了。

RecyclerView中注册点击事件,需要在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 view ${fruit.name}", Toast.LENGTH_SHORT).show()
        }
        return viewHolder
    }

我们在 onCreateViewHolder() 中注册点击事件。

这里我们分别注册了最外层布局和ImageView的点击事件。itemView为最外层布局,即包含ImageViewTextView的整个布局,为了让我们点击属于某个水果的那部分区域均能响应点击事件。

RecyclerView的强大之处也在于此,它可以轻松实现子项中任意控件或布局的点击事件!!!

再点击事件中,先通过viewHolder.adapterPosition获取用户点击的position,然后通过对应的position获取Fruit实例。然后通过Toast显示!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值