9. UI控件-RecyclerView
ListView 现在基本上不使用了,目前使用更加强大的RecyclerView。
RecyclerView 能实现横向滑动的效果。
9.1 基本用法
<LinearLayout 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=".RecyclerActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
androidx.recyclerview.widget.RecyclerView。由于RecyclerView并不是内置在系统SDK当中的,所以需要把完整的包路径写出来。
Fruit类可以共用:
data class Fruit(val name: String, val imageId: Int) {
}
Adapter需要新建:
class FruitAdapter1(val fruitList: List<Fruit>): RecyclerView.Adapter<FruitAdapter1.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 getItemCount(): Int {
return fruitList.size
}
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
}
}
Adapter类似IOS中UITableView、UICollectionView的代理方法。
getItemCount:返回多少的子项;类似IOS的 numberOfRowsInSection;
onCreateViewHolder:创建ViewHolder实例的,中将fruit_item布局加载进来,然后创建一个ViewHolder实例,并把加载出来的布局传入构造函数当中,最后将ViewHolder的实例返回;
onBindViewHolder:用于对RecyclerView子项的数据进行赋值,会在每个子项被滚动到屏幕内的时候执行,这里我们通过position参数得到当前项的Fruit实例,然后再将数据设置到ViewHolder的ImageView和TextView当中即可;
然后使用Adapter:
val layoutManager = LinearLayoutManager(this)
// 布局方式
binding.recyclerView.layoutManager = layoutManager
val adapter = FruitAdapter1(fruitList)
binding.recyclerView.adapter = adapter
LayoutManager用于指定RecyclerView的布局方式,这里使用的LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果。
9.2 实现横向滚动和瀑布流布局
把子项item的布局改成竖直布局,适配里面的控件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="100dp"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/fruitImage"
android:layout_width="80dp"
android:layout_height="80dp"
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>
然后改下LinearLayoutManager的布局方向即可:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityRecyclerBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
initFruits()
val layoutManager = LinearLayoutManager(this)
// 设置布局方向 横向
layoutManager.orientation = LinearLayoutManager.HORIZONTAL
// 布局方式
binding.recyclerView.layoutManager = layoutManager
val adapter = FruitAdapter1(fruitList)
binding.recyclerView.adapter = adapter
}
RecyclerView的布局工作交给了LayoutManager。
LayoutManager制定了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能定制出各种不同排列方式的布局了。
除了LinearLayoutManager之外,RecyclerView还给我们提供了GridLayoutManager和StaggeredGridLayoutManager这两种内置的布局排列方式。GridLayoutManager可以用于实现网格布局,StaggeredGridLayoutManager可以用于实现瀑布流布局。
9.3 点击事件
需要我们自己给子项具体的View去注册点击事件。在adapter的onCreateViewHolder方法中:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fruit_staggered_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
}
9.4 不同类型的子布局
比如微信聊天框,接收Cell和发送Cell
左布局 msg_left_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<TextView
android:id="@+id/leftMsgTextView"
android:layout_margin="10dp"
android:padding="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:textColor="#ffffff"
android:background="@drawable/left_message"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
右布局 msg_right_item.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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="parent"
android:layout_margin="10dp">
<TextView
android:id="@+id/rightMsgTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:textColor="#ffffff"
android:padding="10dp"
android:gravity="right"
android:background="@drawable/right_message"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
Msg类
class Msg(val content: String, val type: Int) {
companion object {
const val TYPE_RECEIVED = 0
const val TYPE_SENT = 1
}
}
MsgAdapter类
class MsgAdapter(val msgList: List<Msg>): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
inner class LeftViewHolder(view: View): RecyclerView.ViewHolder(view) {
val leftMsg: TextView = view.findViewById(R.id.leftMsgTextView)
}
inner class RightViewHolder(view: View): RecyclerView.ViewHolder(view) {
val rightMsg: TextView = view.findViewById(R.id.rightMsgTextView)
}
override fun getItemViewType(position: Int): Int {
val msg = msgList[position]
return msg.type
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (viewType == Msg.TYPE_RECEIVED) {
val leftView = LayoutInflater.from(parent.context)
.inflate(R.layout.msg_left_item, parent, false)
return LeftViewHolder(leftView)
} else {
val rightView = LayoutInflater.from(parent.context)
.inflate(R.layout.msg_right_item, parent, false)
return RightViewHolder(rightView)
}
}
override fun getItemCount(): Int {
return msgList.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val msg = msgList[position]
when (holder) {
is LeftViewHolder -> {
holder.leftMsg.text = msg.content
}
is RightViewHolder -> {
holder.rightMsg.text = msg.content
}
}
}
}
需要注意的:
- 根据不同的布局声明不同的内联
ViewHolder override fun getItemViewType(position: Int): Int根据position返回不同的标识,类似IOS重用标识符adapter中其它方法按需判断类型再做操作
Activity中
val layoutManager = LinearLayoutManager(this)
adapter = MsgAdapter(msgList)
binding.recyclerView.layoutManager = layoutManager
binding.recyclerView.adapter = adapter