很早就一直想自定义这个布局管理器,这个的难度和自定义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?) : 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的,主要目的不是写特效效果,而是属性这个流程和看了一些源码执行的流程
我觉得还是有必要写下来,不然过一会又忘了。。。。。。。。。。