文章目录
概述
RecycleView中多种数据结构的item进行解耦,单一职责显示多种类型的item布局
MultiTypeAdapter会给items中每一种类型的itemData找到对应的itemViewBinder进行数据绑定
基于版本 implementation ‘com.drakeet.multitype:multitype:4.2.0’
itemViewBinder
在处理item点击事件时候使用了自定义回调接口和ItemViewDelegate中的getPosition(holder)的api
package com.bliss.multitype.binder
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bliss.multitype.app.R
import com.bliss.multitype.data.TextItem
import com.drakeet.multitype.ItemViewBinder
/**
* Author: yangtianfu
* Date: 2021/1/28 20:13
* Describe:每一种itemViewBinder绑定指定类型的data - TextItem
*/
class TextItemViewBinder(val listener:OnItemClickedListener):ItemViewBinder<TextItem,TextItemViewBinder.TextHolder>() {
private var lastShownAnimationPosition: Int = 0
//holder初始化item控件
class TextHolder(itemView: View):RecyclerView.ViewHolder(itemView){
val text:TextView = itemView.findViewById(R.id.text)
}
override fun onBindViewHolder(holder: TextHolder, item: TextItem) {
holder.text.text = "hello ${item.text}"
//item点击事件,此处position可能会返回-1,故需要做一下判断
holder.itemView.setOnClickListener {
val position = getPosition(holder)
if (position>=0){
listener.onClick(holder.text,position,item)
}
}
setAnimation(holder.itemView,holder.position)
}
private fun setAnimation(itemView: View, position: Int) {
if (position>lastShownAnimationPosition){
itemView.startAnimation(AnimationUtils.loadAnimation(itemView.context,android.R.anim.slide_in_left))
lastShownAnimationPosition = position
}
}
override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): TextHolder {
return TextHolder(inflater.inflate(R.layout.item_text,parent,false))
}
//view销毁时清除动画
override fun onViewDetachedFromWindow(holder: TextHolder) {
holder.itemView.clearAnimation()
}
interface OnItemClickedListener{
fun onClick(textView: TextView,position: Int,textItem: TextItem)
}
}
/**
* 单一item类型
* android:descendantFocusability="blocksDescendants"处理item不显示的问题
* beforeDescendants:viewgroup会优先其子类控件而获取到焦点
* afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点
* blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点,常用
*/
private fun imageItemShow() {
//注册ItemViewBinder
adapter.register(ImageItemViewBinder())
binding.recycleView.adapter = adapter
//数据源
val imageItem = ImageItem(R.mipmap.ic_launcher)
for (i in 0..19) {
items.add(imageItem)
}
adapter.items = items
adapter.notifyDataSetChanged()
}
MultiTypeAdapter在UI中的使用和item点击事件处理
private fun imageAndTextItemsShow() {
//注册ItemViewBinder,每一种数据源对应一种viewBinder,都需要进行注册
adapter.register(ImageItemViewBinder())
//MultiTypeAdapter中item点击事件回调处理
val itemTextItemBinder = TextItemViewBinder(object : TextItemViewBinder.OnItemClickedListener {
override fun onClick(textView: TextView, position: Int, textItem: TextItem) {
Log.e("onClick ", "MultiTypeAdapter中item点击事件回调处理 - 点击了- ${textView.text} position = $position 当前item数据:${textItem.toString()}" )
}
})
adapter.register(itemTextItemBinder)
adapter.register(RichViewDelegate())
binding.recycleView.adapter = adapter
//多类型数据源
val imageItem = ImageItem(R.mipmap.ic_launcher)
val textItem = TextItem("world")
val richItem = RichItem("自定义view做item", R.drawable.img_11)
for (i in 0..19) {
items.add(textItem)
items.add(imageItem)
items.add(richItem)
}
adapter.items = items //adapter会给items中每一种类型的itemData找到对应的itemViewBinder进行数据绑定
adapter.notifyDataSetChanged()
}
item点击效果如下
多种item数据类型
class ImageItemViewBinder : ItemViewBinder<ImageItem, ImageItemViewBinder.ImageHolder>() {
//①创建holder,然后在ItemViewBinder泛型中填入改holder
//标记为 inner 的嵌套类能够访问其外部类的成员。内部类会带有一个对外部类的对象的引用,不是单例,可以被定义多个
inner class ImageHolder(itemView: View):RecyclerView.ViewHolder(itemView){
val image:ImageView = itemView.findViewById(R.id.image)
}
override fun onBindViewHolder(holder: ImageHolder, item: ImageItem) {
//③给item控件赋值
holder.image.setImageResource(item.resId)
}
override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ImageHolder {
//②创建的时候给ImageHolder赋值布局
return ImageHolder(inflater.inflate(R.layout.item_image,parent,false))
}
}
/**
* Author: yangtianfu
* Date: 2021/1/28 20:39
* Describe:ViewDelegate是ItemViewDelegate子类
* 用以自定义view做item的数据绑定方式
*/
class RichViewDelegate :ViewDelegate<RichItem,RichView>() {
override fun onBindView(view: RichView, item: RichItem) {
view.imageView.setImageResource(item.imageResId)
view.textView.text = item.text
}
override fun onCreateView(context: Context): RichView {
return RichView(context).apply {
layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT) }
}
}
class RichView(context: Context):LinearLayout(context) {
val imageView = AppCompatImageView(context).apply {
addView(this, LayoutParams(72.dp, 72.dp))
}
val textView = AppCompatTextView(context).apply {
setTextColor(Color.BLACK)
addView(this, LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
))
}
init {
orientation = VERTICAL
gravity = Gravity.CENTER
setPadding(16.dp, 16.dp, 16.dp, 16.dp)
}
}
//顶层工具类,数字转dp
val Number.dp: Int get() = (toInt() * Resources.getSystem().displayMetrics.density).toInt()
多类型item数据注册显示
private fun imageAndTextItemsShow() {
//注册ItemViewBinder,每一种数据源对应一种viewBinder,都需要进行注册
adapter.register(ImageItemViewBinder())
adapter.register(TextItemViewBinder())
adapter.register(RichViewDelegate())
binding.recycleView.adapter = adapter
//多类型数据源
val imageItem = ImageItem(R.mipmap.ic_launcher)
val textItem = TextItem("world")
val richItem = RichItem("自定义view做item", R.drawable.img_11)
for (i in 0..19) {
items.add(textItem)
items.add(imageItem)
items.add(richItem)
}
adapter.items = items //adapter会给items中每一种类型的itemData找到对应的itemViewBinder进行数据绑定
adapter.notifyDataSetChanged()
}
效果图
包含GridView复杂视图使用
灵活配置每一行的列数
主要在于列数的动态设置,如下
layoutManager.spanSizeLookup = object :GridLayoutManager.SpanSizeLookup(){
override fun getSpanSize(position: Int): Int {
//当前位置的 item 跨度大小,(SPAN_COUNT ÷ 此处返回值)== 该item所在行一共有几列
//此处Category所在行要求只显示一列作为标题,所以返回计算公式如下
return if (items[position] is Category) SPAN_COUNT else 1
}
}
- 标题行item
class Category(var title: String)
class CategoryItemViewBinder:ItemViewBinder<Category,CategoryItemViewBinder.ViewHolder>() {
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.findViewById(R.id.title)
}
override fun onBindViewHolder(holder: ViewHolder, item: Category) {
holder.title.text = item.title
}
override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
return ViewHolder(inflater.inflate(R.layout.item_category,parent,false))
}
}
- GridView的item
class Square(val number: Int) {
var isSelected: Boolean = false
}
class SquareViewBinder(val selectedSet: MutableSet<Int>):ItemViewBinder<Square,SquareViewBinder.ViewHolder>() {
override fun onBindViewHolder(holder: ViewHolder, item: Square) {
holder.square = item
holder.squareView.text = item.number.toString()
holder.squareView.isSelected = item.isSelected
}
override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
return ViewHolder(inflater.inflate(R.layout.item_square,parent,false))
}
inner class ViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
val squareView: TextView = itemView.findViewById(R.id.square)
lateinit var square: Square
init {
itemView.setOnClickListener {
square.apply {
squareView.isSelected = !isSelected
this.isSelected = !isSelected
}
if (square.isSelected){
selectedSet.add(square.number)
}else{
selectedSet.remove(square.number)
}
}
}
}
}
复杂页面实现
class MultiSelectableActivity : AppCompatActivity() {
private lateinit var binding: ActivityMultiSelectableBinding
private val adapter = MultiTypeAdapter()
private val items = ArrayList<Any>()
private lateinit var fab: Button
private lateinit var selectedSet: TreeSet<Int>//内部元素排序
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this,R.layout.activity_multi_selectable)
initRecycle()
}
private fun initRecycle() {
val layoutManager = GridLayoutManager(this, SPAN_COUNT)
layoutManager.spanSizeLookup = object :GridLayoutManager.SpanSizeLookup(){
override fun getSpanSize(position: Int): Int {
//当前位置的 item 跨度大小,(SPAN_COUNT ÷ 此处返回值)== 该item所在行一共有几列
//此处Category所在行要求只显示一列作为标题,所以返回计算公式如下
return if (items[position] is Category) SPAN_COUNT else 1
}
}
selectedSet = TreeSet()
binding.recycleView.layoutManager = layoutManager
adapter.register(CategoryItemViewBinder())
adapter.register(SquareViewBinder(selectedSet))
loadData()
binding.recycleView.adapter = adapter
binding.fab.setOnClickListener { v ->
val content = StringBuilder()
for (number in selectedSet) {
content.append(number).append(" ")
}
Toast.makeText(v.context, "Selected items: $content", Toast.LENGTH_SHORT).show()
}
}
private fun loadData() {
val spacialCategory = Category("特别篇")
items.add(spacialCategory)
for (i in 0..6) {
items.add(Square(i + 1))
}
val currentCategory = Category("本篇")
items.add(currentCategory)
for (i in 0..999) {
items.add(Square(i + 1))
}
adapter.items = items
adapter.notifyDataSetChanged()
}
companion object{
private const val SPAN_COUNT = 5//每行排列 item 个数,在GridLayoutManager对象创建时需要传入
}
}