第十课Android界面事件梳理3:触屏动作事件之Drag事件与拖放手势

4. Drag事件及Drag and drop(拖放手势)

  拖放是在使用手机时经常用到的一种功能,它可以让用户使用图形化拖放手势,将数据从一个视图移至另一个视图。它本质上也是一种单点触控手势,但是它发生在两个视图之间的(甚至可以发生在两个程序之间)。因为它本质也是单点触控,拖放时的手势与前面介绍的单点触屏手势是完全相同的。那么


  同学们想想看要是你是系统设计者,你该如何做?其实答案很简单,就是加一个标志进行区分。当然设置这个标志是由一个方法来完成的,这个方法就是View类的startDragAndDrop。当这个方法被调用之后,系统将手指在屏幕上的单点触屏手势识别为拖放手势,并不再向视图派送Touch事件,改为派送Drag事件。类似于onTouchEvent,View中有一个专门的Drag事件处理方法onDragEvent,它的参数不再是MotionEvent类型,而是DragEvent。同样地有设置监听者对象的方法setOnDragListener,监听者对象的接口为View.OnDragListener,这个接口只定义了一个回调方法onDrag。完整的事件处理流程如下图所示:


  大家可以回忆一下,在Touch事件的处理中,视图类通过监听者接口分化出了3个回调方法,而这里就只有一个onDrag方法,也就是把事件信息进行直接传递。而且Drag事件的处理也没有GestureDetector与ScaleGestureDetector这样的辅助功能类来进行动作类型的组合,再分化为不同的回调方法,只能在重写的onDrag回调方法里对动作类型进行判断再决定如何处理,好在Drag事件的动作类型并不复杂。

  要在代码中启动拖放功能,先要通过视图对象调用startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)方法。注意这个方法通过程序中的任意一个View对象调用都可以,而且调用之后,在程序中所有当前Activity的View对象都会收到Drag事件。调用这个方法时要确保手指当前正与屏幕接触,因为当手指离开屏幕后系统即判断拖放手势结束。所以调用startDragAndDrop方法最合适的地方是重写的onLongClick方法中(当然View的其它回调方法或GestureDetector的几个回调方法中也可以,但都没有这么方便)。startDragAndDrop的参数列表中有4个参数。第1个参数是ClipData类型,通过它可以把数据暂存到系统剪贴板(系统剪贴板同学们应该知道吧,我们在发微信时对文本进行剪切、粘贴操作时,数据就是暂存在系统剪贴板中),在drop的时候就可以从剪贴板把数据取出来。这个参数在跨程序拖放时需要用到。第2个参数View.DragShadowBuilder是拖动显示的阴影图形。第3个参数是在程序内进行拖放时可以传递的数据对象,drop时可以通过DragEvent对象调用getLocalState()方法重新获得该对象从而完成数据传递。第4个参数是标志位,在实现复杂功能如跨程序拖放时需要用到,一般置为0即可。

  Drag事件发生以后,各级回调方法的参数DragEvent对象会将事件动作信息传递过来。类似于MotionEvent,通过调用DragEvent对象的getAction方法也可以获得当前拖放手势所在的动作。动作共分为6种:

  • ACTION_DRAG_STARTED:startDragAndDrop方法被调用后,当前界面上的每个View对象都立即收到该事件动作类型。如果监听器想继续接收拖放手势的事件信息,则必须向系统返回布尔值true。

  • ACTION_DRAG_ENTERED:当拖动阴影刚进入一个视图的边界框时,该视图对象的拖动事件监听器会收到此事件动作类型。这是监听器在拖动阴影进入边界框时收到的第一个事件动作类型。如果监听器想继续接收拖放手势在本视图边界框内的移动动作信息,则必须向系统返回布尔值true。

  • ACTION_DRAG_LOCATION:当收到 ACTION_DRAG_ENTERED 事件且拖动阴影仍在该视图的边界框内时,该视图对象的拖动事件监听器会收到此事件动作类型。

  • ACTION_DRAG_EXITED:当收到 ACTION_DRAG_ENTERED 和至少一个 ACTION_DRAG_LOCATION ,并且用户已将拖动阴影移至视图的边界框以外时,该视图对象的拖动事件监听器会收到此事件动作类型。

  • ACTION_DROP:当用户将拖动阴影释放到视图对象上时,该视图对象的拖动事件监听器会收到此事件动作类型。仅当视图对象的监听器在响应 ACTION_DRAG_STARTED 拖动事件时返回布尔值true,系统才会将该动作类型发送至该监听器。如果用户将拖动阴影释放到未注册监听器的视图上或不属于当前布局的任何视图上,则系统不会发送此动作类型。如果成功处理了drop动作,则监听器方法应该返回布尔值 true。

  • ACTION_DRAG_ENDED:当系统结束拖动手势时,视图对象的拖动事件监听器会收到此事件动作类型。此动作类型不一定在 ACTION_DROP之后,因为可能手指抬起时所在的视图对象并没有注册要接收Drag事件。

  下面我就用一个示例来演示拖放功能如何实现。示例说明如下:

  • 拖放一个ImageView到ScrollView内

  • 当进入ScrollView边界时,ScrollView底色改变。释放或离开边界后,恢复原有底色

  • 在ScrollView内部释放,会添加一个ImageView成为其子组件

  • 添加的ImageView能够自动排列

  我们新建一个Activity名为DragTest,在对应的XML中拖入一个Guidline,orientation设为horizontal,layout_constraintGuide_percent为0.5,即位于垂直方向的50%。再在上方放一个ImageView,id为imgSource,利用Guidline位于上方居中。再在下方放一个ScrollView,id为svTarget,顶部贴着Guidline,并设置它的background颜色,这样在启动时就能够看到。ScrollView内部的LinearLayout也进行设置,id为layoutTarget,layout_width设为300dp,也进行background颜色设置,这样在拖放时就能看出边界区别。XML代码如下:

<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline android:id="@+id/guideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.5" />
<ScrollView android:id="@+id/svTarget" android:layout_width="match_parent" android:layout_height="300dp" android:background="@android:color/holo_green_dark" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline" tools:layout_editor_absoluteX="-40dp">
<LinearLayout android:id="@+id/layoutTarget" android:layout_width="300dp" android:layout_height="wrap_content" android:orientation="vertical" /> </ScrollView>
<ImageView android:id="@+id/imgSource" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toTopOf="@+id/guideline" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/p1" />
</androidx.constraintlayout.widget.ConstraintLayout>

  再敲Java代码,如下:

public class DragTest extends AppCompatActivity {    private final static String TAG = "DragTest";
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drag_test);
final ImageView imgSource = (ImageView)findViewById(R.id.imgSource); imgSource.setOnLongClickListener(new View.OnLongClickListener() { @SuppressLint("NewApi") @Override public boolean onLongClick(View v) { // 创建拖动阴影,即为ImageView本身 View.DragShadowBuilder builder = new View.DragShadowBuilder(v); // 启动拖放功能 v.startDragAndDrop(null, builder, v, 0); return true; } });
final LinearLayout layoutTarget = (LinearLayout)findViewById(R.id.layoutTarget); layoutTarget.setOnDragListener(new View.OnDragListener() { @Override public boolean onDrag(View v, DragEvent event) { switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: Log.i(TAG, "layoutTarget ACTION_DRAG_STARTED"); break; case DragEvent.ACTION_DRAG_ENTERED: // 当拖放手势进入Layout组件边界框时改变背景色 layoutTarget.setBackgroundColor(Color.BLUE); Log.i(TAG, "layoutTarget ACTION_DRAG_ENTERED"); break; case DragEvent.ACTION_DRAG_LOCATION: Log.i(TAG, "layoutTarget ACTION_DRAG_LOCATION"); break; case DragEvent.ACTION_DRAG_EXITED: // 当拖放手势移出Layout组件边界框时恢复背景色 layoutTarget.setBackgroundColor(getResources().getColor(android.R.color.holo_orange_light)); Log.i(TAG, "layoutTarget ACTION_DRAG_EXITED"); break; case DragEvent.ACTION_DRAG_ENDED: // 当拖放手势结束时恢复背景色 layoutTarget.setBackgroundColor(getResources().getColor(android.R.color.holo_orange_light)); Log.i(TAG, "layoutTarget ACTION_DRAG_ENDED"); break; } return true; } });
final ScrollView svTarget = (ScrollView)findViewById(R.id.svTarget); svTarget.setOnDragListener(new View.OnDragListener() { @Override public boolean onDrag(View v, DragEvent event) { switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: Log.i(TAG, "ScrollView ACTION_DRAG_STARTED"); break; case DragEvent.ACTION_DRAG_ENTERED: // 当拖放手势进入Layout组件边界框时改变背景色 svTarget.setBackgroundColor(Color.GREEN); Log.i(TAG, "ScrollView ACTION_DRAG_ENTERED"); break; case DragEvent.ACTION_DRAG_LOCATION: Log.i(TAG, "ScrollView ACTION_DRAG_LOCATION"); break; case DragEvent.ACTION_DROP: // 当手势释放时将获得的图形加入到布局中 Object o = event.getLocalState(); if (o != null && o instanceof ImageView) { ImageView img = new ImageView(v.getContext()); Drawable drawable = ((ImageView)o).getDrawable(); img.setImageDrawable(drawable); layoutTarget.addView(img); } break; case DragEvent.ACTION_DRAG_EXITED: // 当拖放手势移出Layout组件边界框时恢复背景色 svTarget.setBackgroundColor(getResources().getColor(android.R.color.holo_green_dark)); Log.i(TAG, "ScrollView ACTION_DRAG_EXITED"); break; case DragEvent.ACTION_DRAG_ENDED: // 当拖放手势结束时恢复背景色 svTarget.setBackgroundColor(getResources().getColor(android.R.color.holo_green_dark)); Log.i(TAG, "ScrollView ACTION_DRAG_ENDED"); break; } return true; } }); }}

  在上面代码中,通过重写ImageView的OnLongClick启动拖放手势,阴影图形就是ImageView自己,要传递的数据也是ImageView自己。对ScrollView与LinearLayout中都重写onDrag回调,分别在拖放手势进行其边界框时改变背景色,移出边界框或拖放结束时恢复背景色。在ScrollView的onDrag回调中对释放手势进行处理,通过DragEvent对象调用getLocalState方法,获得传递的数据,并将图形加入LinearLayout中。在界面启动时,LinearLayout里面没有东西,它是看不到的,所以要在ScrollView中对释放手势进行处理。示例代码并没有在LinearLayout的onDrag回调中对释放手势进行处理,这样也是为什么了让大家看到区别。运行效果如下:


  到这里就把所有的手势事件类型都讲完了,同学们可以简单回顾一样内容:单点手势、多点手势、单点多视图手势。


跟陶叔学编程第一季 零基础学习Android开发

第一课 第一个Android程序(1)

第一课 第一个Android程序(2)

第二课 Java语言基础1(1)

第二课 Java语言基础1(2)

第三课 Java语言基础2-1

第三课 Java语言基础2-2

第四课 Java语言基础3-1

第四课 Java语言基础3-2

第五课 类与面向对象编程1

第五课 类与面向对象编程1-2

第五课 类与面向对象编程1-3

第六课 类与面向对象编程2-1

第六课 类与面向对象编程2-2

第六课 类与面向对象编程2-3

第七课 JDK的使用1

第七课 JDK的使用2

第七课 JDK的使用3

第七课 JDK的使用4

第八课 Android界面编程1-1:界面设计、XML表示、布局组件

第八课 Android界面编程1-2:代码控制组件、尺寸单位

第八课 Android界面编程1-3:代码生成界面组件并设置位置 使用ConstrainLayout约束布局组件进行组件位置设置

第八课 Android界面编程1-4:完整21点扑克游戏、界面交互、对话框

第八课 Android界面编程1-5:游戏配置、横竖屏切换、生成签名apk文件

第九课 Android界面编程2-1:界面组件总体介绍、Activity

第九课 Android界面编程2-2:Layouts(布局)和Containers(容器)组件:CardView

第九课 Android界面编程2-3:Layouts(布局)和Containers(容器)组件:Spinner

第九课 Android界面编程2-4:Layouts(布局)和Containers(容器)组件:RecyclerView

第九课 Android界面编程2-5:布局与容器的实现原理、输入与输出组件


文章转载自微信公众号:跟陶叔学编程

类似文章