Android 拖拽怎么实现?Drag and Drop 操作详解

文章导读
上一个 测验 下一个 Android 拖放框架允许用户使用图形化的拖放手势,将数据从当前布局中的一个 View 移动到另一个 View。从 API 11 开始,支持将 view 拖放到其他 view 或 view group 上。该框架包含以下三个重要组件来支持拖放功能 −
📋 目录
  1. 壹 拖放过程
  2. 贰 DragEvent 类
  3. 叁 监听拖放事件
  4. 肆 启动拖放事件
  5. 伍 示例
A A

Android - 拖放



上一个
测验
下一个

Android 拖放框架允许用户使用图形化的拖放手势,将数据从当前布局中的一个 View 移动到另一个 View。从 API 11 开始,支持将 view 拖放到其他 view 或 view group 上。该框架包含以下三个重要组件来支持拖放功能 −

  • 拖放事件类。

  • 拖放监听器。

  • 辅助方法和类。

拖放过程

拖放过程基本上有四个步骤或状态 −

  • 开始 − 当您在布局中开始拖动项目时发生此事件,您的应用调用 startDrag() 方法通知系统开始拖放操作。startDrag() 方法中的参数提供要拖放的数据、该数据的元数据,以及用于绘制拖放阴影的回调。

    系统首先回调到您的应用以获取拖放阴影。然后在设备上显示拖放阴影。

    接下来,系统向当前布局中所有 View 对象的已注册拖放事件监听器发送带有动作类型 ACTION_DRAG_STARTED 的拖放事件。

    要继续接收拖放事件(包括可能的 drop 事件),拖放事件监听器必须返回 true。如果拖放事件监听器返回 false,则在系统发送带有动作类型 ACTION_DRAG_ENDED 的拖放事件之前,它不会接收当前操作的拖放事件。

  • 继续 − 用户继续拖动。系统向拖动点进入的 View 的已注册拖放事件监听器发送 ACTION_DRAG_ENTERED 动作,随后发送 ACTION_DRAG_LOCATION 动作。监听器可以选择更改其 View 对象的外观以响应事件,或者通过高亮显示其 View 来反应。

    当用户将拖放阴影移出 View 的边界框后,拖放事件监听器会接收到 ACTION_DRAG_EXITED 动作。

  • 放置 − 用户在某个 View 的边界框内释放拖动的项目。系统向该 View 对象的监听器发送带有动作类型 ACTION_DROP 的拖放事件。

  • 结束 − 就在动作类型 ACTION_DROP 之后,系统发送带有动作类型 ACTION_DRAG_ENDED 的拖放事件,以指示拖放操作已结束。

DragEvent 类

DragEvent 表示系统在拖放操作的各个阶段发送的事件。此类提供了少量常量和我们在拖放过程中使用的关键方法。

常量

以下是作为 DragEvent 类一部分的所有常量整数。

序号 常量及描述
1

ACTION_DRAG_STARTED

表示拖放操作开始的信号。

2

ACTION_DRAG_ENTERED

向 View 发送信号,表示拖放点已进入 View 的边界框。

3

ACTION_DRAG_LOCATION

在 ACTION_DRAG_ENTERED 之后发送给 View,如果拖放阴影仍位于 View 对象的边界框内。

4

ACTION_DRAG_EXITED

表示用户将拖放阴影移出 View 的边界框。

5

ACTION_DROP

向 View 发送信号,表示用户释放了拖放阴影,且拖放点位于 View 的边界框内。

6

ACTION_DRAG_ENDED

向 View 发送信号,表示拖放操作已结束。

方法

以下是 DragEvent 类中几个重要且最常用的方法。

序号 常量及描述
1

int getAction()

检查此事件的动作值。

2

ClipData getClipData()

返回在调用 startDrag() 时发送给系统的 ClipData 对象。

3

ClipDescription getClipDescription()

返回 ClipData 中包含的 ClipDescription 对象。

4

boolean getResult()

返回拖放操作结果的指示。

5

float getX()

获取拖放点的 X 坐标。

6

float getY()

获取拖放点的 Y 坐标。

7

String toString()

返回此 DragEvent 对象的字符串表示。

监听拖放事件

如果您希望 Layout 中的某个 view 响应拖放事件,则该 view 要么实现 View.OnDragListener,要么设置 onDragEvent(DragEvent) 回调方法。当系统调用该方法或监听器时,会向其传递上面介绍的 DragEvent 对象。您可以为 View 对象同时设置监听器和回调方法。如果这样做,只要监听器返回 true,系统会先调用监听器,然后再调用定义的回调。

onDragEvent(DragEvent) 方法与 View.OnDragListener 的组合类似于旧版 Android 中用于触摸事件的 onTouchEvent() 与 View.OnTouchListener 的组合。

启动拖放事件

首先为要移动的数据创建 ClipData 和 ClipData.Item。作为 ClipData 对象的一部分,提供存储在 ClipData 内 ClipDescription 对象中的元数据。对于不表示数据移动的拖放操作,您可能希望使用 null 而非实际对象。

接下来,您可以扩展 View.DragShadowBuilder 来为拖动 view 创建拖放阴影,或者简单地使用 View.DragShadowBuilder(View) 来创建默认拖放阴影,该阴影大小与传递给它的 View 参数相同,触摸点位于拖放阴影的中心。

示例

以下示例展示了使用 View.setOnLongClickListener()、View.setOnTouchListener() 和 View.OnDragEventListener() 实现简单拖放功能的代码。

步骤 描述
1 您将使用 Android Studio IDE 创建一个 Android 应用程序,并将其命名为 My Application,包名为 com.example.saira_000.myapplication。
2 修改 src/MainActivity.java 文件,添加代码以定义事件监听器以及示例中使用的 logo 图像的回调方法。
3 将图像 abc.png 复制到 res/drawable-* 文件夹中。如果您想为不同设备提供不同分辨率的图像,可以使用不同分辨率的图像。
4 修改布局 XML 文件 res/layout/activity_main.xml,以定义 logo 图像的默认视图。
5 运行应用程序以启动 Android 模拟器,并验证应用程序中更改的结果。

以下是修改后的主活动文件 src/MainActivity.java 的内容。该文件可以包含所有基本生命周期方法。

package com.example.saira_000.myapplication;

import android.app.Activity;

import android.content.ClipData;
import android.content.ClipDescription;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;

import android.view.DragEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;

import android.widget.ImageView;
import android.widget.RelativeLayout;


public class MainActivity extends Activity {
   ImageView img;
   String msg;
   private android.widget.RelativeLayout.LayoutParams layoutParams;
   
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      img=(ImageView)findViewById(R.id.imageView);
      
      img.setOnLongClickListener(new View.OnLongClickListener() {
         @Override
         public boolean onLongClick(View v) {
            ClipData.Item item = new ClipData.Item((CharSequence)v.getTag());
            String[] mimeTypes = {ClipDescription.MIMETYPE_TEXT_PLAIN};
            
            ClipData dragData = new ClipData(v.getTag().toString(),mimeTypes, item);
            View.DragShadowBuilder myShadow = new View.DragShadowBuilder(img);
            
            v.startDrag(dragData,myShadow,null,0);
            return true;
         }
      });
      
      img.setOnDragListener(new View.OnDragListener() {
         @Override
         public boolean onDrag(View v, DragEvent event) {
            switch(event.getAction()) {
               case DragEvent.ACTION_DRAG_STARTED:
               layoutParams = (RelativeLayout.LayoutParams)v.getLayoutParams();
               Log.d(msg, "Action is DragEvent.ACTION_DRAG_STARTED");
               
               // 无需执行任何操作
               break;
               
               case DragEvent.ACTION_DRAG_ENTERED:
               Log.d(msg, "Action is DragEvent.ACTION_DRAG_ENTERED");
               int x_cord = (int) event.getX();
               int y_cord = (int) event.getY();
               break;
               
               case DragEvent.ACTION_DRAG_EXITED :
               Log.d(msg, "Action is DragEvent.ACTION_DRAG_EXITED");
               x_cord = (int) event.getX();
               y_cord = (int) event.getY();
               layoutParams.leftMargin = x_cord;
               layoutParams.topMargin = y_cord;
               v.setLayoutParams(layoutParams);
               break;
               
               case DragEvent.ACTION_DRAG_LOCATION  :
               Log.d(msg, "Action is DragEvent.ACTION_DRAG_LOCATION");
               x_cord = (int) event.getX();
               y_cord = (int) event.getY();
               break;
               
               case DragEvent.ACTION_DRAG_ENDED   :
               Log.d(msg, "Action is DragEvent.ACTION_DRAG_ENDED");
               
               // 无需执行任何操作
               break;
               
               case DragEvent.ACTION_DROP:
               Log.d(msg, "ACTION_DROP event");
               
               // 无需执行任何操作
               break;
               default: break;
            }
            return true;
         }
      });
      
      img.setOnTouchListener(new View.OnTouchListener() {
         @Override
         public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
               ClipData data = ClipData.newPlainText("", "");
               View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(img);
               
               img.startDrag(data, shadowBuilder, img, 0);
               img.setVisibility(View.INVISIBLE);
               return true;
            } else {
               return false;
            }
         }
      });
   }
}

以下是 res/layout/activity_main.xml 文件的内容 −

在以下代码中,abc 表示 example.com 的 logo。
<RelativeLayout 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" 
   android:paddingLeft="@dimen/activity_horizontal_margin"
   android:paddingRight="@dimen/activity_horizontal_margin"
   android:paddingTop="@dimen/activity_vertical_margin"
   android:paddingBottom="@dimen/activity_vertical_margin" 
   tools:context=".MainActivity">
   
   <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Drag and Drop Example"
      android:id="@+id/textView"
      android:layout_alignParentTop="true"
      android:layout_centerHorizontal="true"
      android:textSize="30dp" />
      
   <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text=""
      android:id="@+id/textView2"
      android:layout_below="@+id/textView"
      android:layout_centerHorizontal="true"
      android:textSize="30dp"
      android:textColor="#ff14be3c" />>
      
   <ImageView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:id="@+id/imageView"
      android:src="@drawable/abc"
      android:layout_below="@+id/textView2"
      android:layout_alignRight="@+id/textView2"
      android:layout_alignEnd="@+id/textView2"
      android:layout_alignLeft="@+id/textView2"
      android:layout_alignStart="@+id/textView2" />

</RelativeLayout>

以下是 res/values/strings.xml 的内容,用于定义两个新常量 −

<?xml version="1.0" encoding="utf-8"?>
<resources>
   <string name="app_name">My Application</string>
</resources>

以下是 AndroidManifest.xml 的默认内容 −

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.example.saira_000.myapplication" >
      
   <application
      android:allowBackup="true"
      android:icon="@drawable/ic_launcher"
      android:label="@string/app_name"
      android:theme="@style/AppTheme" >
      
      <activity
         android:name=".MainActivity"
         android:label="@string/app_name" >
      
         <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
         </intent-filter>
      
      </activity>
      
   </application>
</manifest>

让我们尝试运行您的 My Application 应用程序。我假设您在设置环境时已经创建了 AVD。要在 Android Studio 中运行应用,请打开项目的一个 activity 文件,然后点击工具栏中的运行 Eclipse Run Icon 图标。Android Studio 将在您的 AVD 上安装应用并启动它,如果您的设置和应用程序一切正常,它将显示以下模拟器窗口 −

Android Drag and Drop

现在对显示的 logo 进行长按,您会看到 logo 图像在长按 1 秒后从原位置稍微移动,这是您开始拖动图像的时机。您可以在屏幕上拖动它并将其放置到新位置。

Android Drop to New Location