先来看下ItemDecoration的源码
public abstract static class ItemDecoration {
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) {
onDraw(c, parent);
}
@Deprecated
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent) {
}
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) {
onDrawOver(c, parent);
}
@Deprecated
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent) {
}
@Deprecated
public void getItemOffsets(@NonNull Rect outRect, int itemPosition, @NonNull RecyclerView parent) {
outRect.set(0, 0, 0, 0);
}
// 预留分割线的位置
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), parent);
}
}
onDraw()和onDrawOver()都是绘制,区别在于绘制的流程。那我们看下分别在什么时候调用的
RecyclerView.draw(Canvas c)
// 先是调用父类的draw()方法,也就是View.draw()
--> super.draw(c);
// 第一步,绘制背景
--> drawBackground(canvas);@View
// 第二步,绘制自己的内容
--> onDraw(canvas);
// RecyclerView重写了onDraw()方法。RecyclerView.onDraw()中调用了ItemDecoration.onDraw()
--> mItemDecorations.get(i).onDraw(c, this, mState);@RecyclerView
// 第三步,绘制子view
--> dispatchDraw(canvas);
// 第四步,绘制前景
--> onDrawForeground(canvas);
// ItemDecoration.onDrawOver() 在 ItemDecoration.onDraw()之后绘制。相当于覆盖在onDraw上面
--> mItemDecorations.get(i).onDrawOver(c, this, mState);
接下来,我们给RecyclerView的item之间添加分割线。
系统给我们提供了DividerItemDecoration这样一个类,实现了RecyclerView.ItemDecoration。
mRecyclerView.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.HORIZONTAL));
--> fill(recycler, mLayoutState, state, false);@LinearLayoutManager
--> layoutChunk(recycler, state, layoutState, layoutChunkResult);
--> measureChildWithMargins(view, 0, 0);
// 获取到分割线的区域
--> final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
// 分割线的区域是由我们自己定义的,通过重写ItemDecoration的getItemOffsets()方法。
--> mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);@RecyclerView
--> getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), parent);@RecyclerView.ItemDecoration
// 默认的分割线是没有的
--> outRect.set(0, 0, 0, 0);
我们来看下DividerItemDecoration中getItemOffsets()的具体实现
//根据RecyclerView的方向,设置分割线
if (mOrientation == VERTICAL) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
既然知道了分割线的具体处理位置是在getItemOffsets()中,那么来尝试自定义一个ItemDecoration。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recycler_view.layoutManager = LinearLayoutManager(this)
recycler_view.addItemDecoration(StudentItemDecoration())
recycler_view.adapter = StudentAdapter(this, initStudentList())
}
// 模拟数据
private fun initStudentList(): MutableList<Student>? {
val studentList = mutableListOf<Student>()
for (i in 0..3) {
for (j in 0..6) {
studentList.add(Student("班级$i 学生$j", "班级$i"))
}
}
return studentList
}
}
class StudentAdapter(private val context: Context, private val list: MutableList<Student>?) :
RecyclerView.Adapter<StudentAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val inflate = LayoutInflater.from(context).inflate(
R.layout.layout_item_recycler_student,
null
)
return MyViewHolder(inflate)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
list?.run {
holder.tvName.text = "$position ---- ${get(position).name}"
}
}
override fun getItemCount(): Int = list?.size ?: 0
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var tvName: TextView = itemView.findViewById<TextView>(R.id.tv_student_name)
}
/**
* 通过position判断view是不是这个类别的第一个
*/
fun isFirstView(position: Int): Boolean {
if (list == null) {
return false
}
if (position == 0) {
return true
}
return list[position - 1].groupName != list[position].groupName
}
fun getClassName(position: Int): String = list?.get(position)?.groupName ?: ""
fun getStudentName(position: Int): String = list?.get(position)?.name ?: ""
/**
* 通过屏幕上第一个position和范围,获取范围内的最后一个数据的index
*/
fun getLastIndex(startIndex: Int, endIndex: Int): Int {
if (list == null) {
return -1
}
for (index in startIndex..endIndex) {
//遍历,直到班级名不相等为止。
if (list[index].groupName != list[index + 1].groupName) {
return index
}
}
return -1
}
}
定义一个Student数据类
data class Student(val name: String, val groupName: String)
定义一个StudentItemDecoration,实现ItemDecoration
class StudentItemDecoration : RecyclerView.ItemDecoration() {
var paintBack: Paint = Paint()
var paintText: Paint
val headerHeight = 200 // 头部的高度
val dividerHeight = 2 // 分隔线高度
init {
paintBack.isDither = true
paintBack.isAntiAlias = true
paintBack.color = Color.RED
paintText = Paint()
paintText.isDither = true
paintText.isAntiAlias = true
paintText.textSize = 30f
paintText.color = Color.WHITE
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
val adapter: StudentAdapter = parent.adapter as StudentAdapter
//获取到recyclerView的padding值
val rectLeft = parent.paddingLeft.toFloat()
val rectTop = parent.paddingTop.toFloat()
val rectRight = parent.width - parent.paddingRight.toFloat()
//这里绘制头布局。
val childCount = parent.childCount
//遍历当前可见的所有view
for (childIndex in 0..childCount) {
val childView = parent.getChildAt(childIndex)
if (childView != null) {
// 通过view获取它的位置。这里要区分getChildLayoutPosition()和getChildAdapterPosition()。
// 这里用getChildAdapterPosition() 会索引越界
val childAdapterPosition = parent.getChildLayoutPosition(childView)
if (adapter.isFirstView(childAdapterPosition)) {
// 如果view是第一个,那就绘制在它的上面
c.drawRect(
rectLeft,
childView.top - headerHeight + rectTop,
rectRight,
childView.top + rectTop,
paintBack
)
// 接下来是绘制标题
c.drawText(
adapter.getClassName(childAdapterPosition),
rectLeft,
childView.top - headerHeight + rectTop + 30,
paintText
)
}
}
}
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
val adapter: StudentAdapter = parent.adapter as StudentAdapter
//获取到recyclerView的padding值
val rectLeft = parent.paddingLeft.toFloat()
val rectTop = parent.paddingTop.toFloat()
val rectRight = parent.width - parent.paddingRight.toFloat()
// 获取到第一个view
val layoutManager = parent.layoutManager as LinearLayoutManager
val itemHeight = parent[0].height
val findFirstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
// 接下来是绘制标题
// if (headerHeight > itemHeight){
//如果头布局的高度大于item的高度。那就需要加
// val childView = parent.getChildAt( )
// }
val endIndexedValue = findFirstVisibleItemPosition + headerHeight / itemHeight
val lastViewIndex = adapter.getLastIndex(
findFirstVisibleItemPosition,
endIndexedValue
)
println("onDrawOver: lastViewIndex=$lastViewIndex start=$findFirstVisibleItemPosition endIndexedValue=$endIndexedValue")
// 这里需要判断固定在头部的头布局的高度是否需要变动
if (lastViewIndex != -1
) {
val childView = parent.getChildAt(lastViewIndex - findFirstVisibleItemPosition)
//这里绘制悬浮停留的效果
c.drawRect(
rectLeft,
((childView.bottom - headerHeight).toFloat() + rectTop).coerceAtMost(rectTop),
rectRight,
(childView.bottom.toFloat() + rectTop).coerceAtMost(headerHeight + rectTop),
paintBack
)
c.drawText(
adapter.getStudentName(findFirstVisibleItemPosition),
rectLeft,
(childView.bottom - headerHeight + rectTop + 30f).coerceAtMost(rectTop + 30),
paintText
)
} else {
//这里绘制悬浮停留的效果
c.drawRect(
rectLeft,
rectTop,
rectRight,
rectTop + headerHeight,
paintBack
)
c.drawText(
adapter.getStudentName(findFirstVisibleItemPosition),
rectLeft,
rectTop + 30,
paintText
)
}
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
outRect.set(0, 10, 0, 0)
//班级之间的分隔大一些
//获取到当前的位置
val childAdapterPosition = parent.getChildAdapterPosition(view)
val adapter: StudentAdapter = parent.adapter as StudentAdapter
if (adapter.isFirstView(childAdapterPosition)) {
outRect.set(0, headerHeight, 0, 0)
} else {
outRect.set(0, dividerHeight, 0, 0)
}
}
}
网友评论