很早就一直想自定義這個佈局管理器,這個的難度和自定義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的,主要目的不是寫特效效果,而是屬性這個流程和看了一些源碼執行的流程
我覺得還是有必要寫下來,不然過一會又忘了。。。。。。。。。。