社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
目录
文章属于学习总结 ,如有错漏之处,敬请指正。
同系列文章:
Android控件RecyclerView(一)——大家都知道的RecyclerView
Android控件RecyclerView(二)——LayoutManager及其自定义
Android自带DividerItemDecoration类加上RecyclerView.addItemDecoration()方法可设置简单分割线。
val itemDecoration = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
recyclerView.addItemDecoration(itemDecoration)
这个分割线使用的是系统自带分割线样式 android.R.attr.listDivider
val itemDecoration = DividerItemDecoration(this, DividerItemDecoration.VERTICAL).apply {
setDrawable(resources.getDrawable(R.drawable.shape_divider_line_vertical_red_2dp))
}
recyclerView.addItemDecoration(itemDecoration)
drawable为高2dp的红色分割线 shape_divider_line_vertical_red_2dp
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/holo_red_dark" />
<size android:height="2dp" />
</shape>
查看效果
在前文的基础上贴上显示RecyclerView列表显示的相关的代码
数据类
/**
* 联系人 initial 拼音首字母 name 联系人名字
*/
data class Contact(val initial: String, val name: String)
Adapter
class ContactAdapter : RecyclerView.Adapter<ContactAdapter.ViewHolder>() {
var items: List<Contact> = ArrayList()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactAdapter.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return ViewHolder(inflater.inflate(android.R.layout.simple_list_item_1, parent, false))
}
override fun getItemCount(): Int = items.size
override fun onBindViewHolder(viewHolder: ContactAdapter.ViewHolder, position: Int) {
viewHolder.bindView(items[position])
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView: TextView = itemView.findViewById(android.R.id.text1)
fun bindView(contact: Contact) {
textView.text = contact.name
}
}
}
Activity
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.*
import cn.xhuww.recyclerview.R
import cn.xhuww.recyclerview.adapter.ContactAdapter
import kotlinx.android.synthetic.main.activity_recycle_view.*
class ItemDecorationActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recycle_view)
val linearLayoutManager = LinearLayoutManager(this)
val contactAdapter = ContactAdapter().apply {
// 生成首字母A - Z 开头的数据,5个为一组
items = (325..454).map {
val ascii = it / 5
Contact(ascii.toChar().toString(), "${ascii.toChar()} $it")
}
}
val itemDecoration = DividerItemDecoration(this, DividerItemDecoration.VERTICAL).apply {
setDrawable(resources.getDrawable(R.drawable.shape_divider_line_vertical_red_2dp))
}
recyclerView.addItemDecoration(itemDecoration)
recyclerView.apply {
layoutManager = linearLayoutManager
adapter = contactAdapter
addItemDecoration(itemDecoration)
}
}
}
ItemDecoration 除了用于分割线,还可对其自定义来装饰Item,下面准备自定一个 悬浮吸顶效果 的ItemDecoration。
先上效果图
自定义ItemDecoration一般需要进行如下步骤
新建 SuspendItemDecoration,重写3个方法,然后定义需要外部给定的属性,如数据、背景、文字样式等等
class SuspendItemDecoration(val context: Context) : RecyclerView.ItemDecoration() {
var dividerDrawable: Drawable? = null //普通Item间的分割线
var groupDividerDrawable: Drawable? = null //每组之间的分割线
var contacts: List<Contact>? = null //联系人数据
private val bounds = Rect()
private val textPaint: TextPaint
@ColorRes
var textColor: Int = android.R.color.white
set(value) {
field = value
textPaint.color = context.resources.getColor(value)
}
var textSize: Float = 20F
set(value) {
field = value
textPaint.textSize = context.sp2px(value)
}
var textTypeface = Typeface.DEFAULT_BOLD!!
set(value) {
field = value
textPaint.typeface = value
}
init {
//系统默认的分割线
val tapeArray = context.obtainStyledAttributes(intArrayOf(android.R.attr.listDivider))
dividerDrawable = tapeArray.getDrawable(0)
tapeArray.recycle()
textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
textPaint.color = context.resources.getColor(textColor)
textPaint.textSize = context.sp2px(textSize)
textPaint.typeface = textTypeface
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
}
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
}
}
扩展方法 sp转px
fun Context.sp2px(sp: Float): Float =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, resources.displayMetrics)
根据数据规则,以及传入的Drawable来设置Item间的高
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
outRect.set(0, 0, 0, 0)
val list = contacts ?: return
val position = (view.layoutParams as RecyclerView.LayoutParams).viewLayoutPosition
val currentInitial = list[position].initial
val lastInitial = if (position >= 1) {
list[position - 1].initial
} else {
null
}
//当前首字母与上一个首字母相同 则属于同一组,使用 dividerDrawable
val drawable = if (currentInitial == lastInitial) {
dividerDrawable
} else {
groupDividerDrawable
}
val height = drawable?.intrinsicHeight ?: 0
//设置Item的上下左右偏移
outRect.set(0, height, 0, 0)
}
然后设置分割线Drawable,分别是红色的2dp 与45 dp
class ItemDecorationActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recycle_view)
val list = (325..454).map {
val ascii = it / 5
Contact(ascii.toChar().toString(), "${ascii.toChar()} $it")
}
val itemDecoration = SuspendItemDecoration(this).apply {
dividerDrawable = resources.getDrawable(R.drawable.shape_divider_line_vertical_red_2dp)
groupDividerDrawable = resources.getDrawable(R.drawable.shape_divider_line_vertical_red_45dp)
contacts = list
}
recyclerView.addItemDecoration(itemDecoration)
val linearLayoutManager = LinearLayoutManager(this)
val contactAdapter = ContactAdapter().apply { items = list }
recyclerView.apply {
layoutManager = linearLayoutManager
adapter = contactAdapter
addItemDecoration(itemDecoration)
}
}
}
此时查看效果
间隔有了但并没有预想的红色分割线,因为 getItemOffsets 方法中只是设置了Item之间的间距,红色Drawable还需要在onDraw中绘制。
与 getItemOffsets逻辑一致,两个Item之间相同首字母则为一组,item之间就不绘制首字母文本,不相同则绘制文本
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
val list = contacts ?: return
c.save()
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
for (i in 0 until parent.childCount) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val position = params.viewLayoutPosition
val currentInitial = list[position].initial
val lastInitial = if (position >= 1) {
list[position - 1].initial
} else {
null
}
var isDrawText = false
parent.getDecoratedBoundsWithMargins(child, bounds)
val drawable = if (currentInitial == lastInitial) {
dividerDrawable
} else {
isDrawText = true
groupDividerDrawable
}
//绘制分割线
val top = bounds.top
val bottom = top + (drawable?.intrinsicHeight ?: 0)
drawable?.setBounds(left, top, right, bottom)
drawable?.draw(c)
//绘制文本内容
if (isDrawText) {
if (drawable != null) {
textPaint.getTextBounds(currentInitial, 0, currentInitial.length, bounds)
val textX = child.paddingLeft.toFloat()
val textY = (child.top - (drawable.intrinsicHeight - bounds.height()) / 2).toFloat()
c.drawText(currentInitial, textX, textY, textPaint)
}
}
}
c.restore()
}
此时查看效果
分割线背景颜色有了,联系人首字母文本也有了,就差顶部悬浮的功能了
看名字就可以猜想,此方法是在xxx之上绘制内容,使用此方法就可以在RecyclerView表面上方绘制我们想要展示的内容
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
val list = contacts ?: return
val drawable = groupDividerDrawable ?: return
//只判断layoutManager为LinearLayoutManager的情况,其他情况的不做处理
val layoutManager = parent.layoutManager as? LinearLayoutManager ?: return
val position = layoutManager.findFirstVisibleItemPosition()
if (position < 0 || position > list.size - 1) {
return
}
val child = parent.findViewHolderForLayoutPosition(position)!!.itemView
val currentInitial = list[position].initial
val nextInitial = if (position + 1 < list.size) {
list[position + 1].initial
} else {
null
}
parent.getDecoratedBoundsWithMargins(child, bounds)
c.save()
if (currentInitial != nextInitial) {
//顶部移出效果
if (child.top + child.height < drawable.intrinsicHeight) {
c.translate(0f, (child.height + child.top - drawable.intrinsicHeight).toFloat())
}
}
val left = parent.paddingLeft
val top = parent.paddingTop
val right = parent.right - parent.paddingRight
val bottom = parent.paddingTop + drawable.intrinsicHeight
drawable.setBounds(left, top, right, bottom)
drawable.draw(c)
textPaint.getTextBounds(currentInitial, 0, currentInitial.length, bounds)
val textX = child.paddingLeft.toFloat()
val textY =
(parent.paddingTop + drawable.intrinsicHeight - (drawable.intrinsicHeight - bounds.height()) / 2).toFloat()
c.drawText(currentInitial, textX, textY, textPaint)
c.restore()
}
此时在运行查看效果就可以发现已经达到了前面发出的效果。
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.support.annotation.ColorRes
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.text.TextPaint
import android.view.View
import cn.xhuww.recyclerview.itemdecoration.Contact
import cn.xhuww.recyclerview.utils.sp2px
class SuspendItemDecoration(val context: Context) : RecyclerView.ItemDecoration() {
var dividerDrawable: Drawable? = null //普通Item间的分割线
var groupDividerDrawable: Drawable? = null //每组之间的分割线
var contacts: List<Contact>? = null //联系人数据
private val bounds = Rect()
private val textPaint: TextPaint
@ColorRes
var textColor: Int = android.R.color.white
set(value) {
field = value
textPaint.color = context.resources.getColor(value)
}
var textSize: Float = 20F
set(value) {
field = value
textPaint.textSize = context.sp2px(value)
}
var textTypeface = Typeface.DEFAULT_BOLD!!
set(value) {
field = value
textPaint.typeface = value
}
init {
//系统默认的分割线
val tapeArray = context.obtainStyledAttributes(intArrayOf(android.R.attr.listDivider))
dividerDrawable = tapeArray.getDrawable(0)
tapeArray.recycle()
textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
textPaint.color = context.resources.getColor(textColor)
textPaint.textSize = context.sp2px(textSize)
textPaint.typeface = textTypeface
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
val list = contacts ?: return
c.save()
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
for (i in 0 until parent.childCount) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val position = params.viewLayoutPosition
val currentInitial = list[position].initial
val lastInitial = if (position >= 1) {
list[position - 1].initial
} else {
null
}
var isDrawText = false
parent.getDecoratedBoundsWithMargins(child, bounds)
val drawable = if (currentInitial == lastInitial) {
dividerDrawable
} else {
isDrawText = true
groupDividerDrawable
}
//绘制分割线
val top = bounds.top
val bottom = top + (drawable?.intrinsicHeight ?: 0)
drawable?.setBounds(left, top, right, bottom)
drawable?.draw(c)
//绘制文本内容
if (isDrawText) {
drawable ?: return
textPaint.getTextBounds(currentInitial, 0, currentInitial.length, bounds)
val textX = child.paddingLeft.toFloat()
val textY = (child.top - (drawable.intrinsicHeight - bounds.height()) / 2).toFloat()
c.drawText(currentInitial, textX, textY, textPaint)
}
}
c.restore()
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
val list = contacts ?: return
val drawable = groupDividerDrawable ?: return
//只判断layoutManager为LinearLayoutManager的情况,其他情况的不做处理
val layoutManager = parent.layoutManager as? LinearLayoutManager ?: return
val position = layoutManager.findFirstVisibleItemPosition()
if (position < 0 || position > list.size - 1) {
return
}
val child = parent.findViewHolderForLayoutPosition(position)!!.itemView
val currentInitial = list[position].initial
val nextInitial = if (position + 1 < list.size) {
list[position + 1].initial
} else {
null
}
parent.getDecoratedBoundsWithMargins(child, bounds)
c.save()
if (currentInitial != nextInitial) {
//顶部移出效果
if (child.top + child.height < drawable.intrinsicHeight) {
c.translate(0f, (child.height + child.top - drawable.intrinsicHeight).toFloat())
}
}
val left = parent.paddingLeft
val top = parent.paddingTop
val right = parent.right - parent.paddingRight
val bottom = parent.paddingTop + drawable.intrinsicHeight
drawable.setBounds(left, top, right, bottom)
drawable.draw(c)
textPaint.getTextBounds(currentInitial, 0, currentInitial.length, bounds)
val textX = child.paddingLeft.toFloat()
val textY =
(parent.paddingTop + drawable.intrinsicHeight - (drawable.intrinsicHeight - bounds.height()) / 2).toFloat()
c.drawText(currentInitial, textX, textY, textPaint)
c.restore()
}
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
outRect.set(0, 0, 0, 0)
val list = contacts ?: return
val position = (view.layoutParams as RecyclerView.LayoutParams).viewLayoutPosition
val currentInitial = list[position].initial
val lastInitial = if (position >= 1) {
list[position - 1].initial
} else {
null
}
//当前首字母与上一个首字母相同 则属于同一组,使用 dividerDrawable
val drawable = if (currentInitial == lastInitial) {
dividerDrawable
} else {
groupDividerDrawable
}
val height = drawable?.intrinsicHeight ?: 0
//设置Item的上下左右偏移
outRect.set(0, height, 0, 0)
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!