帶有吸附效果的RecyclerView裝飾器

我是綠色大米呀發表於2017-12-15

通過本文你講了解到: 1.RecyclerView.ItemDecoration的一般寫法 2.View的DrawingCache的相關內容 3.Kotlin的簡單語法 4.這個一個支援任意分組的,任意佈局的Item裝飾。外加可以頂部吸附效果(當然你不喜歡,也可以不啟用)

靈感來源:(http://www.jianshu.com/p/b335b620af39 ) 先偷一張圖,想要的效果就是這樣的。

理想

然後是我寫的程式碼的效果:

我的效果.gif

####1.先說說ItemDecoration#### ItemDecoration主要一下三個方法,可以給Item新增裝飾,這就是裝飾器的意思,一般用來做分割線。

public void onDraw(Canvas c, RecyclerView parent, State state)
public void onDrawOver(Canvas c, RecyclerView parent, State state)
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
複製程式碼

onDraw是在Item內容之前繪製,onDrawOver是在Item內容之上繪製,getItemOffsets是給Item設定一個偏移量,來給裝飾器留下繪製的空間。

再偷一個圖:

圖.png

本次的效果,頂部懸浮的裝飾是在onDrawOver中繪製,其他的裝飾是在onDraw中繪製,getItemOffsets中判斷分組的第一個,設定偏移。

####2.然後是DrawingCache####

繪製裝飾器是先通過cache機制,將裝飾器儲存為bitmap,然後用canvas繪製上去。

我們要獲取cache首先要通過setDrawingCacheEnable方法開啟cache,然後再呼叫getDrawingCache方法就可以獲得view的cache圖片了。

buildDrawingCache方法可以不用呼叫,因為呼叫getDrawingCache方法時,若果cache沒有建立,系統會自動呼叫buildDrawingCache方法生成cache。因為會更新裝飾器的內容, 所以要呼叫destoryDrawingCache方法把舊的cache銷燬,才能建立新的。

當呼叫setDrawingCacheEnabled方法設定為false, 系統也會自動把原來的cache銷燬。

####3.最後是程式碼####

package top.greendami.mykotlinapp

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Rect
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup


/**
 *
 * Created by GreendaMi on 2017/6/14.
 */
class SectionDecoration<T>(var context: Context, var dataList: ArrayList<T>, val layoutId: Int, val groupListener: GroupListener,var isFloat : Boolean = true, var sectionLayout: ViewGroup = LayoutInflater.from(context).inflate(layoutId, null) as ViewGroup) : RecyclerView.ItemDecoration() {

    var lastBitmap: Bitmap? = null
    var firstTop: Int = 0

    init {
        val layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
        sectionLayout.layoutParams = layoutParams
        sectionLayout.measure(
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
    }

    //繪製分割區域
    override fun onDraw(c: Canvas?, parent: RecyclerView?, state: RecyclerView.State?) {
        super.onDrawOver(c, parent, state)
        val left = parent!!.paddingLeft
        for (i: Int in 0..parent.childCount) {
            if (groupListener.isFirst(parent.getChildAdapterPosition(parent.getChildAt(i)))) {
                //設定內容
                groupListener.setContnt(sectionLayout, parent.getChildAdapterPosition(parent.getChildAt(i)))
                //設定內容後重新測量(此處預設父佈局寬度)
                sectionLayout.measure(
                        View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY),
                        View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
                sectionLayout.layout(0, 0, parent.width - parent.paddingRight, sectionLayout.measuredHeight)
                sectionLayout.isDrawingCacheEnabled = true
                sectionLayout.buildDrawingCache(true)
                c!!.drawBitmap(sectionLayout.drawingCache, left.toFloat(), (parent.getChildAt(i).top - sectionLayout.measuredHeight).toFloat(), null)
                //獲取影像(儲存滾出螢幕的最後一個)
                if ((parent.getChildAt(i).top - sectionLayout.measuredHeight) <= 0) {
                    lastBitmap = Bitmap.createBitmap(sectionLayout.drawingCache)
                }
                sectionLayout.destroyDrawingCache()
                sectionLayout.isDrawingCacheEnabled = false
            } else {
                continue
            }
        }
    }

    //如果開啟了懸浮,繪製懸浮的那個分割區域
    override fun onDrawOver(c: Canvas?, parent: RecyclerView?, state: RecyclerView.State?) {
        super.onDrawOver(c, parent, state)
        if(!isFloat){
            return
        }
        val left = parent!!.paddingLeft
        for (i: Int in 0..parent.childCount) {
            if (groupListener.isFirst(parent.getChildAdapterPosition(parent.getChildAt(i)))) {
                //parent.getChildAt(i).top是Item內容的高度,不包含Decoration的高度;sectionLayout.measuredHeight是Decoration的高度
                if (parent.getChildAt(i).top in sectionLayout.measuredHeight..sectionLayout.measuredHeight * 2) {
                    firstTop = parent.getChildAt(i).top - sectionLayout.measuredHeight * 2
                    c!!.drawBitmap(lastBitmap, left.toFloat(), firstTop.toFloat(), null)
                    //發現是交換的過程,繪製完交換後的Decoration後,不再繪製top位置是0的Decoration
                    return
                } else {
                    firstTop = 0
                }
            } else {
                firstTop = 0
            }
        }
        //繪製top位置是0的Decoration
        c!!.drawBitmap(lastBitmap, left.toFloat(), firstTop.toFloat(), null)
    }

    //每個Item給留出分割區域的繪製控制元件
    override fun getItemOffsets(outRect: Rect?, itemPosition: Int, parent: RecyclerView?) {
        super.getItemOffsets(outRect, itemPosition, parent)
        if (groupListener.isFirst(itemPosition)) {
            outRect!!.top = sectionLayout.measuredHeight
        }
    }

}

abstract class GroupListener {
    abstract fun isFirst(position: Int): Boolean
    abstract fun setContnt(contentView: ViewGroup, position: Int)
}
複製程式碼

1.png

//獲取影像(儲存滾出螢幕的最後一個) if ((parent.getChildAt(i).top - sectionLayout.measuredHeight) <= 0) { lastBitmap = Bitmap.createBitmap(sectionLayout.drawingCache) } 是在尋找最後一個滾出螢幕的裝飾器,用於繪製吸附效果。

if (parent.getChildAt(i).top in sectionLayout.measuredHeight..sectionLayout.measuredHeight * 2) { firstTop = parent.getChildAt(i).top - sectionLayout.measuredHeight * 2 c!!.drawBitmap(lastBitmap, left.toFloat(), firstTop.toFloat(), null) //發現是交換的過程(後一個把前一個頂出的過程),繪製完交換後的Decoration後,不再繪製top位置是0的Decoration return }

後一個把前一個頂出的過程,計算被頂出的裝飾器的top座標。

外部回撥,list是一個普通的RecycleView,R.layout.header是裝飾器的佈局。


list.addItemDecoration(SectionDecoration(this, datas, R.layout.header, object : GroupListener() {
override fun isFirst(position: Int): Boolean {
return position % 5 == 0
}

override fun setContnt(contentView: ViewGroup, position: Int) {
(contentView.findViewById(R.id.text) as TextView).text = "標題:" + datas[position]
}

}))
複製程式碼

Github( https://github.com/GreendaMi/PPColorPicker/blob/master/app/src/main/java/top/greendami/mykotlinapp/PPSectionDecoration.kt )

相關文章