View.onDragListener(View)
Api 11引入的工具類谈截,用于實現(xiàn)View的拖拽操作,列表拖動
適用于用戶的托起,放下操作,重在內(nèi)容的移動挣磨,拖拽時可以附加拖拽數(shù)據(jù),數(shù)據(jù)分為本地數(shù)據(jù)LocalState(App內(nèi)進行拖拽)荤懂,跨進程數(shù)據(jù)ClipData(兩個App之間進行拖拽)茁裙;比如添加物品進購物車
不需要進行自定義View,使用view.startDrag()/startDragAndDrop()來啟動拖拽操作
通過view.setDragListener()或者重寫View的onDragEvent()來監(jiān)聽View的拖拽狀態(tài)拖拽原理:創(chuàng)建一個圖層(DragShadowBuilder)在屏幕的最上層,這個圖層會隨著用戶手指的移動而移動
使用
開啟拖拽
val clipData = ClipData.newPlainText("name", "drag data") ViewCompat.startDragAndDrop(view, clipData, View.DragShadowBuilder(it), "LocalState", 0) //或者view.startDragAndDrop() val clipData = ClipData.newPlainText("name", "drag data") view.startDragAndDrop(clipData, View.DragShadowBuilder(it), "LocalState", 0)
拖拽監(jiān)聽
view.setOnDragListener(HDragListener) //重寫OnDragListener實現(xiàn)拖拽監(jiān)聽 inner class HDragListener : OnDragListener { override fun onDrag(v: View, event: DragEvent): Boolean { when (event.action) { DragEvent.ACTION_DRAG_STARTED ->{ //開始拖拽時回調(diào) } DragEvent.ACTION_DRAG_ENTERED ->{ //拖拽到View的邊界內(nèi)回調(diào)(可進行排序操作) } DragEvent.ACTION_DRAG_ENDED ->{ //結(jié)束拖拽時回調(diào) } } return true } } //或者重寫View的onDragEvent()實現(xiàn)拖拽監(jiān)聽 override fun onDragEvent(event: DragEvent): Boolean { //當前界面中所有View都會回調(diào)這個方法 }
ViewDragHelper(ViewGroup)
2015年 SupportV4 包新增的工具類节仿,主要用于ViewGroup中子View的拖拽操作
需要開發(fā)者在自定義ViewGroup中使用晤锥,重寫ViewGroup的onInterceptTouchEvent()和onTouchEvent()來接管觸摸事件
拖拽原理:實時修改被拖拽的子View的mLeft,mTop,mRight,mBottom值
使用:
自定義Callback實現(xiàn)ViewDragHelper.callback接口,監(jiān)聽拖拽回調(diào)
inner class HDragCallback : ViewDragHelper.Callback() { //記錄下被托起的View的初始位置 private var capturedLeft = 0 private var capturedTop = 0 /*** 返回true時View可以被拖起來*/ override fun tryCaptureView(child: View, pointerId: Int): Boolean { return true } override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int { return left //水平偏移 } override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int { return top //垂直偏移 } /*** View被拖起時回調(diào)*/ override fun onViewCaptured(capturedChild: View, activePointerId: Int) { capturedChild.elevation = elevation + 1 capturedLeft = capturedChild.left capturedTop = capturedChild.top } /*** 拖拽狀態(tài)改變時回調(diào)*/ override fun onViewDragStateChanged(state: Int) { if (state == ViewDragHelper.STATE_IDLE) { val capturedView = mDragHelper.capturedView!! capturedView.elevation = capturedView.elevation - 1 } } override fun onViewPositionChanged(changedView: View, left: Int, top: Int,dx: Int, dy: Int) { //位置改變時回調(diào) } /*** View被松開時回調(diào)*/ override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) { mDragHelper.settleCapturedViewAt(capturedLeft, capturedTop) //更新下一幀的繪制 和computeScroll結(jié)合 postInvalidateOnAnimation() } }
重寫ViewGroup的onInterceptTouchEvent()和onTouchEvent()來接管觸摸事件
private val mDragHelper by lazy { ViewDragHelper.create(this, HDragCallback()) } override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { return mDragHelper.shouldInterceptTouchEvent(ev) } override fun onTouchEvent(event: MotionEvent): Boolean { mDragHelper.processTouchEvent(event) return true } //和DragCallback的onViewReleased()相結(jié)合,循環(huán)繪制每一幀 override fun computeScroll() { if (mDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this) } }
嵌套滑動
不同向嵌套
onInterceptTouchEvent 父 View 攔截子View
requestDisallowInterceptTouchEvent() 子 View 阻止父 View 攔截
同向嵌套
父 View 會徹底卡住子 View(滑動沖突)
原因:搶奪條件一致粟耻,但父View的onInterceptTouchEvent() 早于子View的dispatchTouchEvent()-
本質(zhì)上是策略問題:嵌套狀態(tài)下用戶手指滑動查近,他是想滑誰?
場景一:NestedScrollView 子View能滑動的時候滑動子View挤忙;滑不動的時候滑動父View
-
場景二:Google 的樣例
父View展開的時候:
? 上滑:優(yōu)先滑動父View
? 下滑:滑不動
父View半展開的時候:
? 上滑:優(yōu)先滑動父View霜威,滑到父View完全折疊后開始滑動子View
? 下滑:優(yōu)先滑動父View,滑到父View完全展開后開始滑動子View
父View折疊的時候:
? 上滑:滑動子View
? 下滑:優(yōu)先滑動子View册烈,滑到子View頂部后開始滑動父View
滑動嵌套解決方案: 自定義滑動策略(父View戈泼,子View誰來消費滑動事件)
-
實現(xiàn):
大多數(shù)場景下SDk就能解決:ScrollView嵌套問題,換成NestedScrollView
自己實現(xiàn):實現(xiàn) NestedScrollingChild2 接口來實現(xiàn)自定義的嵌套滑動邏輯