OnDragListener

  • Start the drag with startDrag()
  • Use setOnDragListener() to listen
    • OnDragListener has only one method inside :onDrag()
    • The onDragEvent() method also receives a drag callback (as does every View in the interface)

ViewDragHelper

  • You need to create a ViewDragHelper and Callback()
  • We need to put it in ViewGroup, override onIntercept() and onTouchevent()

Why these two things instead of one?

  • OnDragListener
    • Utility classes added to API 11 for drag and drop operations.
    • Usage scenario: Users “drag -> drop” operation, focus on content movement. Drag and drop data can be attached
    • Instead of writing a custom View, use startDrag()/startDragAndDrop() to manually enable drag
    • The principle of drag and drop is to create an image on the top of the screen, and the user’s finger drags the image around
  • ViewDragHelper
    • Toolclasses added to the support V4 package in 2015 for drag-and-drop operations.
    • Usage scenario: A user drags a child View in a ViewGroup
    • Need to apply custom ViewGroup invokes the ViewDragHelper. ShouldInterceptTouchEvent () and processTouchEvent (), the program will automatically open the drag and drop
    • The principle of drag is to change the mLeft, mTop, mRight and mBottom values of the child View being dragged in real time

Several implementations of drag and drop

1.DragHelperGridView, using ViewDragHelper to handle drag logic

  • Remeasure in onMeasure
  • Put child Views in onLayout
  • The various event logic is handled through ViewDragHelper

private const val COLUMNS = 2 private const val ROWS = 3 class DragHelperGridView(context: Context? , attrs: AttributeSet?) : ViewGroup(context, attrs) { private var dragHelper = ViewDragHelper.create(this, DragCallback()) override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { val specWidth = MeasureSpec.getSize(widthMeasureSpec) val specHeight = MeasureSpec.getSize(heightMeasureSpec) val  childWidth = specWidth / COLUMNS val childHeight = specHeight / ROWS measureChildren(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)) setMeasuredDimension(specWidth, specHeight) } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { var childLeft: Int var childTop: Int val childWidth = width / COLUMNS val childHeight = height / ROWS for ((index, child) in children.withIndex()) { childLeft = index % 2 * childWidth childTop = index / 2 * childHeight child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight) } } override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { return dragHelper.shouldInterceptTouchEvent(ev) } override fun onTouchEvent(event: MotionEvent): Boolean { dragHelper.processTouchEvent(event) return true } override fun computeScroll() { if (dragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this) } } private inner class DragCallback : ViewDragHelper.Callback() { var capturedLeft = 0f var capturedTop = 0f 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 } override fun onViewCaptured(capturedChild: View, activePointerId: Int) { capturedChild.elevation = elevation + 1 capturedLeft = capturedChild.left.toFloat() capturedTop = capturedChild.top.toFloat() } override fun onViewPositionChanged(changedView: View, left: Int, top: Int, dx: Int, dy: Int) { } override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) { dragHelper.settleCapturedViewAt(capturedLeft.toInt(), capturedTop.toInt()) postInvalidateOnAnimation() } } }Copy the code

xml

<com.dsh.txlessons.viewtouchdrag.drag.view.DragHelperGridView xmlns:android="http://schemas.android.com/apk/res/android"  xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"  tools:context=".MainActivity"> <View android:id="@+id/draggedView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#EF5350" /> <View android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#9C27B0" /> <View android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#1E88E5" /> <View android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#00695C" /> <View android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#FDD835" /> <View android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#546E7A" /> </com.dsh.txlessons.viewtouchdrag.drag.view.DragHelperGridView>Copy the code

2.DragListenerGridView, which handles the drag logic via OnDragListener

  • Handle the drag logic through OnDragListener
  • Drag and rearrange the child view

private const val COLUMNS = 2 private const val ROWS = 3 class DragListenerGridView(context: Context? , attrs: AttributeSet?) : ViewGroup(context, attrs) { private var dragListener: OnDragListener = DshDragListener() private var draggedView: View? = null private var orderedChildren: MutableList<View> = ArrayList() init { isChildrenDrawingOrderEnabled = true } override fun onFinishInflate() { super.onFinishInflate() val count: Int = getChildCount() for (index in 0 until count) {val Child = getChildAt(index) orderedChildren.add(child) // Initialize the position child.setOnLongClickListener { v -> draggedView = v v.startDrag(null, DragShadowBuilder(v), v, 0) false } child.setOnDragListener(dragListener) } } override fun onDragEvent(event: DragEvent?) : Boolean { return super.onDragEvent(event) } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { val specWidth = MeasureSpec.getSize(widthMeasureSpec) val specHeight = MeasureSpec.getSize(heightMeasureSpec) val  childWidth = specWidth / COLUMNS val childHeight = specHeight / ROWS measureChildren(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)) setMeasuredDimension(specWidth, specHeight) } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { var childLeft: Int var childTop: Int val childWidth = width / COLUMNS val childHeight = height / ROWS val count: Int = getChildCount() for (index in 0 until count) { val child = getChildAt(index) childLeft = index % 2 * childWidth childTop = index / 2 * childHeight child.layout(0, 0, childWidth, childHeight) child.translationX = childLeft.toFloat() child.translationY = childTop.toFloat() } } private inner class DshDragListener : OnDragListener { override fun onDrag(v: View, event: DragEvent): Boolean { when (event.action) { DragEvent.ACTION_DRAG_STARTED -> if (event.localState === v) { v.visibility = View.INVISIBLE } DragEvent.ACTION_DRAG_ENTERED -> if (event.localState ! == v) { sort(v) } DragEvent.ACTION_DRAG_EXITED -> { } DragEvent.ACTION_DRAG_ENDED -> if (event.localState === v) { v.visibility = View.VISIBLE } } return true } } private fun sort(targetView: View) { var draggedIndex = -1 var targetIndex = -1 for ((index, child) in orderedChildren.withIndex()) { if (targetView === child) { targetIndex = index } else if (draggedView === child) { draggedIndex = index } } orderedChildren.removeAt(draggedIndex) orderedChildren.add(targetIndex, draggedView!!) var childLeft: Int var childTop: Int val childWidth = width / COLUMNS val childHeight = height / ROWS for ((index, child) in orderedChildren.withIndex()) { childLeft = index % 2 * childWidth childTop = index / 2 * childHeight child.animate() .translationX(childLeft.toFloat()) .translationY(childTop.toFloat()) .setDuration(150) } } }Copy the code

3. DragToCollectLayout, drag favorites/Add shopping cart effect

  • OnFinishInflate adds click and drag events
  • Long press the trigger drag events, call ViewCompat. StartDragAndDrop (…). methods
  • OnDragListener listens for events and adds content to the bottom layout

class DragToCollectLayout(context: Context, attrs: AttributeSet) : ConstraintLayout(context, attrs) {
  private var dragStarter = OnLongClickListener { v ->
    val imageData = ClipData.newPlainText("name", v.contentDescription)
    ViewCompat.startDragAndDrop(v, imageData, DragShadowBuilder(v), null, 0)
  }
  private var dragListener: OnDragListener = CollectListener()

  override fun onFinishInflate() {
    super.onFinishInflate()
    avatarView.setOnLongClickListener(dragStarter)
    logoView.setOnLongClickListener(dragStarter)
    collectorLayout.setOnDragListener(dragListener)
  }

  inner class CollectListener : OnDragListener {
    override fun onDrag(v: View, event: DragEvent): Boolean {
      when (event.action) {
        DragEvent.ACTION_DROP -> if (v is LinearLayout) {
          val textView = TextView(context)
          textView.textSize = 16f
          textView.text = event.clipData.getItemAt(0).text
          v.addView(textView)
        }
      }
      return true
    }
  }
}
Copy the code

xml

<? The XML version = "1.0" encoding = "utf-8"? > <com.dsh.txlessons.viewtouchdrag.drag.view.DragToCollectLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/avatarView" android:layout_width="0dp" android:layout_height="120dp" android:layout_weight="1" android:contentDescription="Avatar" android:src="@mipmap/slmh" app:layout_constraintEnd_toStartOf="@id/logoView" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ImageView android:id="@+id/logoView" android:layout_width="0dp" android:layout_height="120dp" android:layout_weight="1" android:contentDescription="Logo" android:src="@drawable/google_logo" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/avatarView" app:layout_constraintTop_toTopOf="parent" /> <LinearLayout android:id="@+id/collectorLayout" android:layout_width="match_parent" android:layout_height="80dp" android:layout_alignParentBottom="true" android:background="#78909C" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> </com.dsh.txlessons.viewtouchdrag.drag.view.DragToCollectLayout>Copy the code

4. DragUpDownLayout, drag automatic top and bottom effect

  • The logic is handled primarily in viewDragHelper.callback ()

class DragUpDownLayout(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) { private var dragListener: ViewDragHelper.Callback = DragCallback() private var dragHelper: ViewDragHelper = ViewDragHelper.create(this, dragListener) private var viewConfiguration: ViewConfiguration = ViewConfiguration.get(context) override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { return dragHelper.shouldInterceptTouchEvent(ev) } override fun onTouchEvent(event: MotionEvent): Boolean { dragHelper.processTouchEvent(event) return true } override fun computeScroll() { if (dragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this) } } internal inner class DragCallback :  ViewDragHelper.Callback() { override fun tryCaptureView(child: View, pointerId: Int): Boolean { return child === draggedView } override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int { return top } override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) { if (Math.abs(yvel) > viewConfiguration.scaledMinimumFlingVelocity) { if (yvel > 0) { dragHelper.settleCapturedViewAt(0, height - releasedChild.height) } else { dragHelper.settleCapturedViewAt(0, 0) } } else { if (releasedChild.top < height - releasedChild.bottom) { dragHelper.settleCapturedViewAt(0, 0) } else { dragHelper.settleCapturedViewAt(0, height - releasedChild.height) } } postInvalidateOnAnimation() } } }Copy the code

xml

<? The XML version = "1.0" encoding = "utf-8"? > <com.dsh.txlessons.viewtouchdrag.drag.view.DragUpDownLayout xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent" android:layout_height="match_parent"> <View android:id="@+id/draggedView" android:layout_width="match_parent" android:layout_height="200dp" android:background="#388E3C" /> </com.dsh.txlessons.viewtouchdrag.drag.view.DragUpDownLayout>Copy the code