實現(xiàn)高亮某行的RecyclerView效果

最終效果

全部代碼:github

方式有二

  1. 組合控件,RecyclerView + View
  2. 自定義RecyclerView

1中只需要控制View,但是不好封裝。
2中需要重寫RecyclerView中一些東西,最終就是一個CustomRecyclerView。

所以采用的是第二種方法。代碼100來行。
主要步驟

    1. 添加ItemDecoration使第一個和最后一個Item可以滾動到屏幕中央
    1. 添加SnapHelper重寫方法
    1. 重寫OnDrawForeGround繪制高亮區(qū)域
    1. 添加OnScrollListener當滑動停止可以根據(jù)當前Item高度重繪高亮區(qū)域

1. 添加ItemDecoration使第一個和最后一個Item可以滾動到屏幕中央

很簡單
判斷是第一個/最后一個Item,頂部/底部添加距離

val decoration = object : RecyclerView.ItemDecoration() {
    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: State
    ) {
        super.getItemOffsets(outRect, view, parent, state)
        this@HighLightRecyclerView.adapter?.let {
            if (parent.getChildAdapterPosition(view) == 0)
                outRect.top = (parent.measuredHeight - view.layoutParams.height).shr(1)
            else if (parent.getChildAdapterPosition(view) == it.itemCount - 1)
                outRect.bottom = (parent.measuredHeight - view.layoutParams.height).shr(1)
        }
    }
}

2. RecyclerView添加SnapHelper并重寫findSnapView()方法

思路就是,根據(jù)停止時各個Item的位置判斷RecyclerView應(yīng)該對齊哪個Item
無非就是一個工具類的兩個比較特別的方法

OrientationHelpergetDecoratedStart(v:View)有兩種情況,字面意思是“獲取Item距離頂部位置”。

    1. Item設(shè)置了Margin或Decoration這些偏移量,注意是Start,也就是一般情況下的Left或者Top,視Orientation而定。那么返回值為Item頂部距RecyclerView頂部距離 - 偏移量
    1. 沒有設(shè)置偏移量,返回值為Item高度

相應(yīng)地,OrientationHelpergetDecoratedMeasurement(v:View)也有兩種情況。字面意思是“獲取Item所占空間大小”。

    1. 有偏移,返回偏移+高度
    1. 無偏移,返回高度

高亮Item的需求因為涉及到第一步中偏移的設(shè)置,主要有三種情況

    1. 頂部 Start為0,Measurement為頂部偏移+高度
    1. 中部 Start為頂部距離,Measurement為Item高度
    1. 底部 Start為頂部距離,Measurement為底部偏移+高度

這地方不弄情況特別亂,我列出表格大概是這個樣子:

位置 getDecoratedStart getDecoratedMeasurement
頂部 0 OffsetTop + Height
中部 TopDistance Height
底部 TopDistance OffsetBottom + Height

我們讓Item居中,它應(yīng)該是 頂部距離+ItemHeight/2 == RecyclerView.height/2
偽代碼就是 TopDistance+Height/2 = Parent.Height/2
嘗試用一行代碼寫,發(fā)現(xiàn)沒辦法,只能分情況
也就是
pos為0 helper.getDecoratedStart(it) + helper.getDecoratedMeasurement(it) - it.layoutParams.height
其余情況為 helper.getDecoratedStart(it) + it.layoutParams.height.shr(1)
想明白了也就那么回事。

代碼如下:

override fun findSnapView(layoutManager: RecyclerView.LayoutManager?): View? {
    return when {
        layoutManager == null -> null
        layoutManager.childCount == 0 -> null
        else -> {
            var closestChild: View? = null
            var absClosest = Int.MAX_VALUE
            val helper = OrientationHelper.createVerticalHelper(layoutManager)
            val center = helper.startAfterPadding + helper.totalSpace.shr(1)
            var childCenter: Int
            var distance: Int
            for (i in 0 until layoutManager.childCount) {
                layoutManager.getChildAt(i)?.let {
                    childCenter = if (i == 0)
                        helper.getDecoratedStart(it) + helper.getDecoratedMeasurement(it) - it.layoutParams.height
                    else helper.getDecoratedStart(it) + it.layoutParams.height.shr(1)
                    distance = abs(center - childCenter)
                    if (distance < absClosest) {
                        absClosest = distance
                        closestChild = it
                    }
                }
            }
            closestChild
        }
    }
}

3. 重寫OnDrawForeGround繪制高亮區(qū)域

 path.addRect(0f, 0f, width.toFloat(), height.toFloat(), Path.Direction.CW)
        path.addRect(
            0f,
            (height - highLightHeight).shr(1).toFloat(),
            width.toFloat(),
            (height + highLightHeight).shr(1).toFloat(),
            Path.Direction.CCW
        )

沒啥好講的,主要就是Path.Direction要講講
這個高亮區(qū)域,有兩個矩形組成,第一個是畫全屏的遮罩,第二個是從第一個矩形中摳出這個區(qū)域,反方向與Android繪制定義有關(guān),記住它能摳出形狀就行。就不用涉及PorterDuff類的相關(guān)概念了。

4. 添加OnScrollListener當滑動停止可以根據(jù)當前Item高度重繪高亮區(qū)域

啊,其實這個如果不要動畫,就很簡單,一行代碼
highLightHeight = height
但是,要動畫也是一行代碼

if (newState == SCROLL_STATE_IDLE) {
    snapHelper.findSnapView(layoutManager)?.let {
        animator = ValueAnimator.ofInt(highLightHeight, it.layoutParams.height)
            .setDuration(300)
        animator.removeAllUpdateListeners()
        animator.addUpdateListener { va ->
            highLightHeight = va.animatedValue as Int
        }
        animator.start()
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容