RecyclerView实现吸顶效果

本文主要讲述得方式是通过自定义RecyclerView.ItemDecoration来实现RecyclerView的吸顶效果

本文使用Kotlin代码

先看效果:

实现的方式:主要是通过重写绘制方法 onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State)

文中吸顶的内容是通过绘制单行Text文字+背景,RecyclerView的Item是一个含有TextView的LinearLayout

涉及文字居中绘制会使用paint.drawText(),baseLine的获取采用的方式:

// 方式一
float baseline = targetRect.top + (targetRect.bottom - targetRect.top)/2f 
- (fontMetrics.bottom - fontMetrics.top)/2 - fontMetrics.top;

// 方式二
float baseline = targetRect.top + (targetRect.bottom - targetRect.top)/2f 
+ (fontMetrics.bottom - fontMetrics.top)/2 - fontMetrics.bottom;

自定义RecyclerView.ItemDecoration:

class ThisItemDecoration : RecyclerView.ItemDecoration() {

    private var mDrawable:Drawable = ColorDrawable(Color.BLACK)

    /**
     * 间隔高度
     * */
    private val dividerHeight = 30.dp2px()
   /**
   * 顶高
   * */
private val headerTopHeight = 66.dp2px() private val paintHeaderText = Paint().apply { isDither = true isAntiAlias = true color = Color.YELLOW textSize = 18.dp2px().toFloat() } private val paintHeader = Paint().apply { isDither = true isAntiAlias = true style = Paint.Style.FILL color = Color.RED }
   // 标记顶的横线
private val paint3 = Paint().apply { isDither = true isAntiAlias = true style = Paint.Style.FILL strokeWidth = 1.dp2px().toFloat() color = Color.BLACK } override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { canvas.save() var left = 0 var right = parent.width if (parent.clipToPadding) { left = parent.paddingLeft right = parent.width - parent.paddingRight canvas.clipRect( left, parent.paddingTop, right, parent.height - parent.paddingBottom ) } if (parent.adapter !is ThisRecyclerViewAdapter) { return } val adapter = parent.adapter as ThisRecyclerViewAdapter val position = (parent.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() val isHeader = adapter.isHeader(position + 1) if(isHeader) { val itemView = parent.findViewHolderForAdapterPosition(position + 1)?.itemView if (null != itemView) { val bottom = min(headerTopHeight * 1.0f, itemView.top - headerTopHeight - parent.paddingTop.toFloat()) val top = bottom - headerTopHeight val targetRect = RectF(left.toFloat(), top + parent.paddingTop, right.toFloat(), bottom + parent.paddingTop) canvas.drawRect(targetRect, paintHeader) val groupType = adapter.getTypeByPositionString(position) val fontMetrics = paintHeaderText.fontMetrics val baseline = targetRect.top + (targetRect.bottom - targetRect.top) / 2f - (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.top canvas.drawText( groupType, left.toFloat() + 5.dp2px(), baseline, paintHeaderText) } return } val top = parent.paddingTop.toFloat() val bottom = top + headerTopHeight val targetRect = RectF(left.toFloat(), top, right.toFloat(), bottom) canvas.drawRect(targetRect, paintHeader) val groupType = adapter.getTypeByPositionString(position) val fontMetrics = paintHeaderText.fontMetrics val baseline = targetRect.top + (targetRect.bottom - targetRect.top) / 2f - (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.top canvas.drawText( groupType, left.toFloat() + 5.dp2px(), baseline, paintHeaderText) canvas.drawLine(0f, top + headerTopHeight / 2f, right.toFloat(), top + headerTopHeight / 2f, paint3) canvas.restore() } override fun onDraw( canvas: Canvas, parent: RecyclerView, state: RecyclerView.State ) { canvas.save() var left = 0 var right = parent.width if (parent.clipToPadding) { left = parent.paddingLeft right = parent.width - parent.paddingRight canvas.clipRect( left, parent.paddingTop, right, parent.height - parent.paddingBottom ) } val childCount = parent.childCount; for (i in 0 until childCount) { val child = parent.getChildAt(i) drawHeaderText(canvas, parent, child, left, right) val lp = child.layoutParams as RecyclerView.LayoutParams val top: Int = child.bottom + lp.bottomMargin val bottom: Int = top + dividerHeight mDrawable.setBounds(left, top, right, bottom); mDrawable.draw(canvas) } canvas.restore() } private fun drawHeaderText( canvas: Canvas, parent: RecyclerView, view: View, left: Int, right: Int ): Boolean { if (parent.adapter !is ThisRecyclerViewAdapter) { return false } val adapter = parent.adapter as ThisRecyclerViewAdapter val position = parent.getChildLayoutPosition(view) if (adapter.isHeader(position)) { val targetRect = RectF(left.toFloat(), (view.top - headerTopHeight).toFloat(), right.toFloat(), view.top.toFloat()) canvas.drawRect(targetRect, paintHeader) val groupType = adapter.getTypeByPositionString(position) val fontMetrics = paintHeaderText.fontMetrics val baseline = targetRect.top + (targetRect.bottom - targetRect.top) / 2f - (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.top canvas.drawText( groupType, left.toFloat() + 5.dp2px(), baseline, paintHeaderText) return true } return false } /** * 设置预留空间 */ override fun getItemOffsets( outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State ) { if (parent.adapter is ThisRecyclerViewAdapter) { val adapter = parent.adapter as ThisRecyclerViewAdapter val position = parent.getChildLayoutPosition(view) if (adapter.isHeader(position)) { outRect.set(0, headerTopHeight, 0, dividerHeight) } else { if(adapter.isLastLine(position)) { outRect.set(0, 0, 0, 0) } else { outRect.set(0, 0, 0, dividerHeight) } } return } outRect.set(0, 0, 0, 0) } }

RecyclerView的Item的Bean:

class Carrier constructor(val type: CarrierType) {

    private var id by Delegates.notNull<Int>()
    var name by Delegates.notNull<String>()
    var detail by Delegates.notNull<String>()
    var segment: MutableList<String> = arrayListOf()

    init {
        when (type) {
            CarrierType.TYPE_TANK -> {
                id = 1
                name = "坦克"
                detail = "攻击性载具,攻城略地必备"
            }
            CarrierType.TYPE_ARMORED_CAR -> {
                id = 2
                name = "装甲车"
                detail = "运输士兵,能极大的保证运输效率和士兵安全"
            }
        }
    }

}

enum class CarrierType {
    // 坦克
    TYPE_TANK,
    // 装甲车
    TYPE_ARMORED_CAR,
    // 导弹车
}

Adapter中重要的方法:

private var list: MutableList<Carrier> = arrayListOf()

fun isHeader(position: Int): Boolean {
    return if (itemCount <= 1) {
        false
    } else {
        if (position == 0 && position + 1 < itemCount) {
            list[position].name == list[position + 1].name
        } else {
            list[position].name != list[position - 1].name
        }
    }
}

fun isLastLine(position: Int): Boolean {
    return position == itemCount - 1
}

fun getTypeByPositionString(position: Int): String {
    return when (list[position].type) {
        CarrierType.TYPE_TANK -> {
            "载具:坦克"
        }
        CarrierType.TYPE_ARMORED_CAR -> {
            "载具:装甲车辆"
        }
        else -> {
            ""
        }
    }
}

MainActivity的实现:

class MainActivity : AppCompatActivity() {

    private lateinit var bd2: LayoutTest2Binding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        test2()
    }

    private fun test2() {
        bd2 = LayoutTest2Binding.inflate(layoutInflater)
        setContentView(bd2.root)
        val list = arrayListOf<Carrier>()
        for (i in 1..5) {
            for(j in 1..20) {
                if (i % 2 == 0) {
                    list.add(Carrier(CarrierType.TYPE_TANK))
                } else {
                    list.add(Carrier(CarrierType.TYPE_ARMORED_CAR)
                    )
                }
            }
        }
        val rv = bd2.rv
        rv.layoutManager = LinearLayoutManager(this)
        rv.addItemDecoration(ThisItemDecoration())
        rv.adapter = ThisRecyclerViewAdapter(list)
    }
}

 

以上,暂时这样子

 

posted @ 2022-10-27 02:36  swalka`x  阅读(517)  评论(0编辑  收藏  举报