《Android 编程权威指南》学习笔记 : 第9章 使用 RecyclerVIew 显示列表
第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()
}
}
}