View.onDragListener(View)
Api 11引入的工具类,用于实现View的拖拽操作,列表拖动
适用于用户的托起,放下操作,重在内容的移动,拖拽时可以附加拖拽数据,数据分为本地数据LocalState(App内进行拖拽),跨进程数据ClipData(两个App之间进行拖拽);比如添加物品进购物车
不需要进行自定义View,使用view.startDrag()/startDragAndDrop()来启动拖拽操作
通过view.setDragListener()或者重写View的onDragEvent()来监听View的拖拽状态拖拽原理:创建一个图层(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)
拖拽监听
view.setOnDragListener(HDragListener) //重写OnDragListener实现拖拽监听 inner class HDragListener : OnDragListener { override fun onDrag(v: View, event: DragEvent): Boolean { when (event.action) { DragEvent.ACTION_DRAG_STARTED ->{ //开始拖拽时回调 } DragEvent.ACTION_DRAG_ENTERED ->{ //拖拽到View的边界内回调(可进行排序操作) } DragEvent.ACTION_DRAG_ENDED ->{ //结束拖拽时回调 } } return true } } //或者重写View的onDragEvent()实现拖拽监听 override fun onDragEvent(event: DragEvent): Boolean { //当前界面中所有View都会回调这个方法 }
ViewDragHelper(ViewGroup)
2015年 SupportV4 包新增的工具类,主要用于ViewGroup中子View的拖拽操作
需要开发者在自定义ViewGroup中使用,重写ViewGroup的onInterceptTouchEvent()和onTouchEvent()来接管触摸事件
拖拽原理:实时修改被拖拽的子View的mLeft,mTop,mRight,mBottom值
使用:
自定义Callback实现ViewDragHelper.callback接口,监听拖拽回调
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被拖起时回调*/ override fun onViewCaptured(capturedChild: View, activePointerId: Int) { capturedChild.elevation = elevation + 1 capturedLeft = capturedChild.left capturedTop = capturedChild.top } /*** 拖拽状态改变时回调*/ 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) { //位置改变时回调 } /*** View被松开时回调*/ override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) { mDragHelper.settleCapturedViewAt(capturedLeft, capturedTop) //更新下一帧的绘制 和computeScroll结合 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()相结合,循环绘制每一帧 override fun computeScroll() { if (mDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this) } }
嵌套滑动
不同向嵌套
onInterceptTouchEvent 父 View 拦截子View
requestDisallowInterceptTouchEvent() 子 View 阻止父 View 拦截
同向嵌套
父 View 会彻底卡住子 View(滑动冲突)
原因:抢夺条件一致,但父View的onInterceptTouchEvent() 早于子View的dispatchTouchEvent()-
本质上是策略问题:嵌套状态下用户手指滑动,他是想滑谁?
场景一:NestedScrollView 子View能滑动的时候滑动子View;滑不动的时候滑动父View
-
场景二:Google 的样例
父View展开的时候:
上滑:优先滑动父View
下滑:滑不动
父View半展开的时候:
上滑:优先滑动父View,滑到父View完全折叠后开始滑动子View
下滑:优先滑动父View,滑到父View完全展开后开始滑动子View
父View折叠的时候:
上滑:滑动子View
下滑:优先滑动子View,滑到子View顶部后开始滑动父View
滑动嵌套解决方案: 自定义滑动策略(父View,子View谁来消费滑动事件)
-
实现:
大多数场景下SDk就能解决:ScrollView嵌套问题,换成NestedScrollView
自己实现:实现 NestedScrollingChild2 接口来实现自定义的嵌套滑动逻辑