自定义RecylerView.LayoutManager和用ItemHelper实现自己的滑动效果的简单入门教程

很早就一直想自定义这个布局管理器,这个的难度和自定义viewGroup的难度差不多。懒癌一直发作,最近和很闲,就把这个东西搞了,顺便写个博客记录一下。

学习的文章来自于https://www.jianshu.com/p/b7ac36190e2c

这个大佬很厉害,对RecylerView的滑动,源码解析得和透彻。

这个是大佬写的滑动卡片:

这个是我的山寨版本。。。。。。。。。。。。。。。自定义RecylerView.LayoutManager和用ItemHelper实现自己的滑动效果的简单入门教程_第1张图片

搞了两天还是有些绕,到处互相回调感觉耦合严重,基本思路差不多理清楚了。把步骤差不多写一下吧

   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?) : BaseQuickAdapter(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的,主要目的不是写特效效果,而是属性这个流程和看了一些源码执行的流程

我觉得还是有必要写下来,不然过一会又忘了。。。。。。。。。。

 

 

你可能感兴趣的:(kotlin,recylerview)