android 提供了两种用于实现view拖拽的API。
- ViewDragHelper (需要自定义ViewGroup)
- startDrag / startDragAndDrop (配合 setOnDragListener / onDragEvent)
使用场景:
- ViewDragHelper 适用于 “View本身的拖拽”。
- startDrag / startDragAndDrop 适用于 “View携带的数据的拖拽”。
不同点:
- startDrag / startDragAndDrop 产生的拖拽效果,是拖拽一个半透明的view。该view的绘制内容可以自定义绘制。一般默认和被拖拽view相同。该view位于最顶层。
- startDrag / startDragAndDrop 可以在拖拽时携带数据。该数据可以跨进程传输。
- 使用startDrag / startDragAndDrop时,要响应(监听)该view拖拽事件的view都要设置setOnDragListener。
- ViewDragHelper 拖拽的是ViewGroup的内容,针对直接子View。
- ViewDragHelper 拖拽不携带数据。
startDrag / startDragAndDrop 使用:
view.setOnLongClickListener {
// dt:要传递的数据。
val dt = ClipData.newPlainText("name","content")
// DragShadowBuilder 拖拽的图像
it.startDrag(dt, View.DragShadowBuilder(it), it, 0)
}
watcherViews.foreach.setOnDragListener { currentWatcherView, event ->
// 由于每一个设置了监听OnDragListener的view都会接收到回调,
// 所以需要判断当前接收到回调的view是不是正在拖拽的view
when (event.action) {
DragEvent.ACTION_DRAG_STARTED -> {
if (event.localState === currentWatcherView) {
// 如果是当前拖拽的view
currentWatcherView.invisible()
}
}
DragEvent.ACTION_DRAG_ENDED -> {
if (event.localState === currentWatcherView) {
// 如果是当前拖拽的view
currentWatcherView.visible()
}
}
//当拖拽的view进入了currentWatcherView的区域
DragEvent.ACTION_DRAG_ENTERED -> {
if (event.localState !== currentWatcherView) {
// 如果不是当前拖拽的view
}
}
DragEvent.ACTION_DRAG_EXITED -> {}
DragEvent.ACTION_DROP -> {
// 松手时回调。
// clipData参数传入的数据 event.clipData,只有在这里能够接收到。
// event.localState 数据在所有拖拽事件周期都可以接收到
event.clipData.getItemAt(0).text
}
}
true
}
ViewDragHelper 使用:
draglayout.xml
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/needDragView"
android:layout_width="100dp"
android:layout_height="200dp"
android:tag="drag"
android:layout_gravity="center_horizontal"
android:src="@drawable/ic_launcher_background"
app:layout_constraintDimensionRatio="1"
android:scaleType="centerCrop" />
</FrameLayout>
class DragLayout(
context: Context,
attrs: AttributeSet?,
) : LinearLayout(context, attrs) {
var captureViewOldX = 0f
var captureViewOldY = 0f
val callback :ViewDragHelper.Callback= object : ViewDragHelper.Callback(){
// 确定是否要拖拽
override fun tryCaptureView(child: View, pointerId: Int): Boolean {
return true
}
// clamp 夹钳, 钳制view的横向位置
override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
return 0
}
// clamp 夹钳, 钳制view的纵向位置
override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
return top
}
// 开始拖拽
override fun onViewCaptured(capturedChild: View, activePointerId: Int) {
//记录拖拽view的初始位置
captureViewOldX = capturedChild.x
captureViewOldY=capturedChild.y
}
// 拖拽中
override fun onViewPositionChanged(
changedView: View,
left: Int,
top: Int,
dx: Int,
dy: Int,
) {
}
//松手,拖拽结束
override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) {
//返回到该view的初始位置
helper.settleCapturedViewAt(captureViewOldX.toInt(),captureViewOldY.toInt())
invalidate()
// postInvalidateOnAnimation()
// postInvalidate()
}
}
val helper = ViewDragHelper.create(this,callback)
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
return helper.shouldInterceptTouchEvent(ev)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
helper.processTouchEvent(event)
return true
}
override fun computeScroll() {
if(helper.continueSettling(true)){
invalidate()
// postInvalidateOnAnimation()
// postInvalidate()
}
}
}