首页 > 技术文章 > 《Android 编程权威指南》学习笔记 : 第9章 使用 RecyclerVIew 显示列表

easy5weikai 2022-05-29 08:43 原文

第9章 使用 RecyclerVIew 显示列表

引用的类库

代码清单:app/build.gradle

dependencies {
    // ......
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    implementation 'androidx.recyclerview:recyclerview:1.2.1'
}

其中:
ViewModel 需要引用: androidx.lifecycle:lifecycle-extensions
Recyclerview需要引用: androidx.recyclerview:recyclerview
注意:ViewModel的创建方法在 2.2.0版本中已经弃用旧的方法,新方法如下:

class CrimeListFragment {
    ...
    private val crimeListViewModel: CrimeListViewModel by lazy {
        ViewModelProvider(this)[CrimeListViewModel::class.java]
        // or ViewModelProvider(this).get(CrimeListViewModel::class.java)
    }
}

CrimeListViewModel

代码清单:app/src/main/java/com.example.criminallintent/CrimeListViewModel

class CrimeListViewModel : ViewModel() {
    val crimes = mutableListOf<Crime>()
    init {
        for (i in 0 until 100){
            val crime = Crime()
            crime.title = "Crime  #$i"
            crime.isSolved = i%2 == 0
            crimes += crime
        }
    }
}

CrimeListViewModel 得继承:ViewModel
在 ViewModel 中定义列表所有要展示的数据 crimes

CrimeListFragment

知识要点

  • CrimeListFragment 是显示一系列 Crime 的列表页,继承fragment,列表组件使用 Recyclerview做布局控件,父Activity通过 FragmentManager 对其添加显示
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 设备旋转或回收内存时,MainActivity 被销毁,但它的 FragmentManager会将fragment队列保存下来
        // activity 重建时,新的 FragmentManager 会获取保存下来的fragment队列重建 fragment 队列,
        // 从而回到原来的状态
        val currentFragment =
            supportFragmentManager.findFragmentById(R.id.fragment_container)

        if (currentFragment == null) {
            val fragment = CrimeListFragment.newInstance()
            supportFragmentManager.beginTransaction()
                .add(R.id.fragment_container, fragment)
                .commit()
        }
    }
}
  • Recyclerview 只负责列表View的显示和回收利用,其如何显示又交给其成员变量crimeRecycleView.layoutManager管理,可以具体实例化不同的布局管理对象:

     crimeRecycleView.layoutManager = LinearLayoutManager(context)
    
  • 列表项(item)的视图是由 ViewHolder 负责,其继承:RecyclerView.ViewHolder(view) 类,其注意职责是:

    • 声明列表项(item)的视图中的各个子组件,
    • 定义数据绑定函数,供 Adapter 调用
    • 监听列表项(item)的视图的各种事件,比如:点击列表项(item)的视图的事件,或其各个子组件的点击事件
  • Adapter 是 Recyclerview 与 ViewHolder 的沟通桥梁,也是数据与ViewHolder的绑定桥梁,其注意职责是:实现父类 RecyclerView.Adapter 的4个方法

    • 通过构造函数传入列表数据 crimes
    • 获取 Item的布局文件创建 ItemView,并封装成 ViewHolder 返回
    • 根据 position 获取对应的列表项数据 crime = crimes[position]的数据,调用 ViewHolder 封装的数据绑定方法 holder.bind(crime), ViewHolder负责数据绑定,做到职责分工明确
    • 返回列表数据 crimes 的总个数
    • 根据 position得到不同的数据而设置不同的ViewType,并返回 ViewType
      以便 onCreateViewHolder(parent: ViewGroup, viewType: Int)方法根据 ViewType 创建不同的 ViewHolder
  • 数据流:ViewModel.数据 -> Adpter(实现4个方法:处理数据与ViewHodler对应绑定)-> RecycleView(包括:ItemView的回收利用等性能优化):

      private fun updateUI() {
          val crimes = crimeListViewModel.crimes
          adapter = CrimeAdapter(crimes)
          crimeRecycleView.adapter = adapter
      }
    
  • kotlin的知识点:

    • 惰性初始化

      by lazy { ... }
      
    • 内部类

       class CrimeListFragment {
        ...
        private inner class CrimeHolder(view: View) {
           ...
        }
      
        private inner class CrimeAdapter(var crimes: List<Crime>) {
        }
      }
      
    • 伴生对象:相当于java中的静态方法
      定义:

      class CrimeListFragment {
      ...
        companion object {
          fun newInstance(): CrimeListFragment {
              return  CrimeListFragment()
           }
         }
       }
      

      调用:

        val fragment = CrimeListFragment.newInstance()
      

代码清单:app/src/main/java/com.example.criminallintent/CrimeListFragment

package com.example.criminalintent

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

private const val TAG = "CrimeListFragment"

class CrimeListFragment : Fragment() {

    private lateinit var crimeRecycleView : RecyclerView

    private val crimeListViewModel: CrimeListViewModel by lazy {
        ViewModelProvider(this)[CrimeListViewModel::class.java]
    }

    private var adapter :CrimeAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG, "Total crimes:${crimeListViewModel.crimes.size}")
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_crime_list, container, false)

        crimeRecycleView = view.findViewById(R.id.crime_recycler_view) as RecyclerView
        crimeRecycleView.layoutManager = LinearLayoutManager(context)

        updateUI()

        return view
    }

    private fun updateUI() {
        val crimes = crimeListViewModel.crimes
        adapter = CrimeAdapter(crimes)
        crimeRecycleView.adapter = adapter
    }

    /**
     * 内部类:https://blog.csdn.net/kmyhy/article/details/122525449
     * */
    private inner class CrimeHolder(view: View)
        : RecyclerView.ViewHolder(view), View.OnClickListener {

        private lateinit var crime: Crime

        private val titleTextView : TextView = itemView.findViewById(R.id.crime_title)
        private val dateTextView : TextView = itemView.findViewById(R.id.crime_date)

        init {
            itemView.setOnClickListener(this)
        }

        fun bind(crime: Crime) {
            this.crime = crime
            titleTextView.text = crime.title
            dateTextView.text = crime.date.toString()
        }

        override fun onClick(v: View?) {
            Toast.makeText(context, "${crime.title} pressed!", Toast.LENGTH_SHORT)
                .show()
        }
    }

    private inner class CrimeAdapter(var crimes: List<Crime>)
        : RecyclerView.Adapter<CrimeHolder>() {

        /**
         * 负责创建 item 视图,并封装到一个 ViewHolder 中返回
         * 只创建满屏时需要的View的个数,比如11个 View,然后然后就不再调用,
         * 滚出屏幕的视频会被回收,调用 onBindViewHolder(...)更新数据,循环利用 View,优化了性能
          */
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrimeHolder {
            val view = layoutInflater.inflate(R.layout.list_item_crime, parent, false)
            return CrimeHolder(view)
        }

        /** 负责绑定数据
         * 滚出屏幕的视频会被回收,调用 onBindViewHolder(...)更新数据,循环利用 View,优化了性能
        **/
        override fun onBindViewHolder(holder: CrimeHolder, position: Int) {
            val crime = crimes[position]
            holder.bind(crime)
        }

        // 返回数据总数
        override fun getItemCount(): Int {
            return crimes.size
        }

        // 根据 position得到不同的数据而设置不同的ViewType,并返回 ViewType
        // 以便 onCreateViewHolder(parent: ViewGroup, viewType: Int)方法根据
        // ViewType 创建不同的 ViewHolder
        override fun getItemViewType(position: Int): Int {
            return super.getItemViewType(position)
        }
    }

    companion object {
        fun newInstance(): CrimeListFragment {
            return  CrimeListFragment()
        }
    }
}

列表项的布局 list_item_crime

代码清单:res/layout/list_item_crime.xml

<?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:padding="8dp">

    <TextView
        android:id="@+id/crime_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Crime title"/>

    <TextView
        android:id="@+id/crime_date"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Crime date"/>

</LinearLayout>

MainActivity

MainActivity中使用 frament
代码清单:src/main/java/<包>/MainActivity

package com.example.criminalintent

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 设备旋转或回收内存时,MainActivity 被销毁,但它的 FragmentManager会将fragment队列保存下来
        // activity 重建时,新的 FragmentManager 会获取保存下来的fragment队列重建 fragment 队列,
        // 从而回到原来的状态
        val currentFragment =
            supportFragmentManager.findFragmentById(R.id.fragment_container)

        if (currentFragment == null) {
 +          val fragment = CrimeListFragment.newInstance()
            supportFragmentManager.beginTransaction()
                .add(R.id.fragment_container, fragment)
                .commit()
        }
    }

}

推荐阅读