自定義RecylerView.LayoutManager和用ItemHelper實現自己的滑動效果的簡單入門教程

很早就一直想自定義這個佈局管理器,這個的難度和自定義viewGroup的難度差不多。懶癌一直髮作,最近和很閒,就把這個東西搞了,順便寫個博客記錄一下。

學習的文章來自於https://www.jianshu.com/p/b7ac36190e2c

這個大佬很厲害,對RecylerView的滑動,源碼解析得和透徹。

這個是大佬寫的滑動卡片:

這個是我的山寨版本。。。。。。。。。。。。。。。

搞了兩天還是有些繞,到處互相回調感覺耦合嚴重,基本思路差不多理清楚了。把步驟差不多寫一下吧

   var mMyItemTouchHelperCallback = MyItemTouchHelperCallback(mHomeListAdapter, 10)
                            var itemTouchHelper = ItemTouchHelper(mMyItemTouchHelperCallback)
                            var mLinearLayoutManager = OverViewLayoutManager(rec_home_lsit,
                                    10, itemTouchHelper)
                            rec_home_lsit.layoutManager = mLinearLayoutManager
                            rec_home_lsit.adapter = mHomeListAdapter
                            itemTouchHelper.attachToRecyclerView(rec_home_lsit)

上面是調用layoutManager和itemTouchHelper時候的引用。

準備按照調用的順序 和思維來寫

1------------------------------

:自定義layoutmanager

 


class OverViewLayoutManager : RecyclerView.LayoutManager  {
    var mRecyclerView: RecyclerView? = null
    var mMaxVisibleCount: Int = 0
    var mItemTouchHelper: ItemTouchHelper? = null

    constructor(mRecyclerView: RecyclerView?, mMaxVisibleCount: Int, mItemTouchHelper: ItemTouchHelper?) : super() {
        this.mRecyclerView = mRecyclerView
        this.mMaxVisibleCount = mMaxVisibleCount
        this.mItemTouchHelper = mItemTouchHelper
    }

    override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams {
        return RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT)

    }

    override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
        super.onLayoutChildren(recycler, state)
        recycler?.let { detachAndScrapAttachedViews(it) }
        var layoutCount = Math.min(itemCount, mMaxVisibleCount)
        while (layoutCount-1>0) {
            var mView = recycler?.getViewForPosition(layoutCount)
            addView(mView)
            measureChildWithMargins(mView!!, 0, 0)
            val widthSpace = width - getDecoratedMeasuredWidth(mView!!)
            val heightSpace = height - getDecoratedMeasuredHeight(mView!!)
            layoutDecoratedWithMargins(mView!!, widthSpace / 2, heightSpace / 2,
                    widthSpace / 2 + getDecoratedMeasuredWidth(mView!!),
                    heightSpace / 2 + getDecoratedMeasuredHeight(mView!!))
            if (layoutCount == 0) {
                mView?.setOnTouchListener(object : View.OnTouchListener {
                    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
                        var childHolder = v?.let { mRecyclerView?.getChildViewHolder(it) }
                        if (event?.actionMasked == MotionEvent.ACTION_DOWN) {
                            childHolder?.let { mItemTouchHelper?.startSwipe(it) }
                        }
                        return false

                    }
                })
            } else {
                mView?.setOnTouchListener(null)
            }
        --layoutCount
        }
    }

}

構造方法中需要傳一個ItemHelper的接口參數和recylerview、

在onlayouchildren重寫每一個新的itemView的位置和初始化,這一點比較像自定義viewGroup

detachAndScrapAttachedViews(recycler);

之後用倒敘的方式遍歷所有的子view,因爲listview,recylerview的item最舊的在後面,新的放在前面,注意數量要-1

之後用

measureChildWithMargins測量子view 然後用
layoutDecoratedWithMargins給view設置位置大小

後面因爲recylerview複用view的原因,i==0時設置滑動監聽

mItemTouchHelper.startSwipe(childViewHolder);

2---------------------------------

:定義itemTouchHelper需要傳入一個

ItemTouchHelper.Callback 的參數,那麼就new一個類繼承他
class MyItemTouchHelperCallback : ItemTouchHelper.Callback {
    var myItemTouchStatus: MyItemTouchStatus? = null
    var mMaxVisibleCount = 0

    constructor(myItemTouchStatus: MyItemTouchStatus?, mMaxVisibleCount: Int) : super() {
        this.myItemTouchStatus = myItemTouchStatus
        this.mMaxVisibleCount = mMaxVisibleCount
    }

    override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
        val swipeFlag = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
        return makeMovementFlags(0, swipeFlag)

    }

    override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
        return false

    }

    override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
        val itemView = viewHolder.itemView
        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
            var ratio = dX / getThreshold(recyclerView, viewHolder)
            if (ratio > 1) {
                ratio = 1f
            } else if (ratio < -1) {
                ratio = -1f
            }
            // 跟着角度旋轉
            itemView.rotation = ratio * 15
            for (i in 0..2) {
                // 下面的ItemView跟着手指縮放
                val child = recyclerView.getChildAt(i)
                val currentScale = Math.pow(0.85, (2 - i).toDouble()).toFloat()
                val nextScale = currentScale / 0.85f
                val scale = nextScale - currentScale
                child.scaleX = Math.min(1f, currentScale + scale * Math.abs(ratio))
                child.scaleY = Math.min(1f, currentScale + scale * Math.abs(ratio))
            }
        }

    }

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        myItemTouchStatus?.onItemRemove(viewHolder.adapterPosition)

    }
    private fun getThreshold(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Float {
        return recyclerView.width * getSwipeThreshold(viewHolder)
    }
    interface MyItemTouchStatus {

        fun onItemMove(fromPosition: Int, toPosition: Int): Boolean

        fun onItemRemove(position: Int): Boolean
    }


}

這裏需要重寫幾個重要的方法。

getMovementFlags:這裏可以理解成返回一對數據,第一個dragFlag,我的什麼方向屬於拖拽的方法,第二個swipeFlage:我的什麼方向屬於滑動的方向。

比如

val swipeFlag = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT

我對着itemView左右滑動屬於滑動。

onSwiped方法:滑動之後的回調操作,滑動之後需要把adapter的一個數據清除,然後就需要一個接口把這個callbak和adapter聯繫起來
    interface MyItemTouchStatus {

        fun onItemMove(fromPosition: Int, toPosition: Int): Boolean

        fun onItemRemove(position: Int): Boolean
    }

所以onswiped裏面寫

myItemTouchStatus?.onItemRemove(viewHolder.adapterPosition)

最後 最主要和重要的就是繪製,重寫onChildDraw,裏面有一大堆參數,足夠發揮創意來寫自定義操作

獲取itemview:

val itemView = viewHolder.itemView

 可以通過滑動的x,y距離來使其拉伸或者放大縮小操作

最後,構造方法中需要定義這個與adapter聯繫起來的接口參數。

3---------------------------------

上面callbak已經寫完了,實例化的時候需要傳入上面說的與adapter連接其起來的接口

那就讓adapter實現這個接口


class OverViewAdapter(data: MutableList<HomeListBean.NewslistBean>?) : BaseQuickAdapter<HomeListBean.NewslistBean, BaseViewHolder>(R.layout.item_overview_pager, data),MyItemTouchHelperCallback.MyItemTouchStatus {
    override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
        return true
    }

    override fun onItemRemove(position: Int): Boolean {
        data.removeAt(position)
        notifyDataSetChanged()
        return true
    }


    override fun convert(helper: BaseViewHolder?, item: HomeListBean.NewslistBean?) {


    }

在滑動之後把itemview刪除 

4--------------------------------

最後 實例化layoutmanager,

var mLinearLayoutManager = OverViewLayoutManager(rec_home_lsit,
        10, itemTouchHelper)

需要傳入itemTouchHelper,然後實例化itemTouchHelper

var itemTouchHelper = ItemTouchHelper(mMyItemTouchHelperCallback)

需要傳入callback,然後實例化callback

var mMyItemTouchHelperCallback = MyItemTouchHelperCallback(mHomeListAdapter, 10),傳入adpter。。
最後 還有困擾我很久的bug,itemhelper和recylerview連接起來。。
itemTouchHelper.attachToRecyclerView(rec_home_lsit)

最後,特效變化的具體縮放我直接copy的,主要目的不是寫特效效果,而是屬性這個流程和看了一些源碼執行的流程

我覺得還是有必要寫下來,不然過一會又忘了。。。。。。。。。。

 

 

發佈了12 篇原創文章 · 獲贊 8 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章