Android拖放

时间:2020-02-23 14:28:54  来源:igfitidea点击:

在本教程中,我们将在应用程序中实现Android拖放功能。
Android框架具有用于在应用程序中实现拖放功能的内置机制。

Android拖放

  • 要拖动视图,我们需要先向onTouchListener或者onLongClickListener注册。

  • 我们还需要向该视图添加一个侦听器,以将拖放的视图放置到该视图。
    我们将在其上注册一个" onDragListener"。

  • onDragListener接口包含方法onDrag,只要有任何DragEvent事件就会被调用。

以下是在拖放操作期间触发的事件。

  • ACTION_DRAG_STARTED:一旦用户触摸/单击要拖动的视图,就会在onTouch/onLongClick方法内部调用startDrag方法,从而指示拖动已经开始。
    最终在" onDrag"方法内调用" ACTION_DRAG_STARTED"。

  • ACTION_DRAG_ENTERED:当拖动的视图进入拖放视图的边界时,将触发此事件。

  • ACTION_DRAG_LOCATION:此事件在触发" ACTION_DRAG_ENTERED"事件后触发,并用于通过" getX()"和" getY()"方法获取拖动视图的当前位置。

  • ACTION_DRAG_EXITED:当拖动的视图离开放置的视图的边界时触发。

  • ACTION_DROP:当用户释放拖动的视图时触发。

  • ACTION_DRAG_ENDED:得出的结论是,拖放操作已结束。

注意:在拖放过程中,被拖动的视图是原始视图的阴影。
原始视图保持原样,并且保持不变。
相反,它的实例是使用DragShadow类创建的。
因此,我们上面提到的拖动视图实际上是一个拖动阴影。

要将数据从拖动的视图传递到放置的视图,我们使用ClipData。

让我们进入本教程的业务端,在此我们将在应用程序中开发拖放功能。

Android拖放代码

下面给出了" activity_main.xml"布局文件的代码。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:tools="https://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="com.theitroad.draganddrop.MainActivity">

  <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Are you happy with the way drag and drop functionality is taught in this tutorial?"
      android:textColor="#FFF"
      android:gravity="center"
      android:layout_margin="16dp"
      android:textSize="20sp"
      android:layout_above="@+id/btnNo"
      android:layout_centerHorizontal="true" 

  <Button
      android:id="@+id/btnNo"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentEnd="true"
      android:layout_alignParentRight="true"
      android:layout_centerVertical="true"
      android:layout_margin="8dp"
      android:tag="NO"
      android:text="NO" 

  <Button
      android:id="@+id/btnYes"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentLeft="true"
      android:layout_alignParentStart="true"
      android:layout_centerVertical="true"
      android:layout_margin="8dp"
      android:tag="YES"
      android:text="YES" 

  <ImageView
      android:id="@+id/imgDestination"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_centerHorizontal="true"
      android:layout_above="@+id/textView"
      android:src="@drawable/circle_border"
      android:tag="Destination" 

  <TextView
      android:id="@+id/textView"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentBottom="true"
      android:layout_centerHorizontal="true"
      android:text="DROP ABOVE"
      android:layout_margin="16dp"
      android:textColor="#FFF"

</RelativeLayout>

布局包含两个按钮,这些按钮将用于在ImageView内部拖动。
ImageView显示位于图形" circle_border.xml"中的可绘制形状。

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="https://schemas.android.com/apk/res/android" android:shape="oval">
  <solid android:color="@android:color/transparent"
  <stroke android:width="2dp" android:color="#fff" 
  <size android:width="100dp" android:height="100dp"
</shape>

下面给出了MainActivity.java类的代码。

package com.theitroad.draganddrop;

import android.content.ClipData;
import android.content.ClipDescription;
import android.graphics.Color;
import android.os.Build;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnTouchListener, View.OnDragListener {

  Button btnYes, btnNo;
  ImageView imgDestination;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      btnYes = findViewById(R.id.btnYes);
      btnNo = findViewById(R.id.btnNo);
      imgDestination = findViewById(R.id.imgDestination);

      btnYes.setOnTouchListener(this);
      btnNo.setOnTouchListener(this);
      imgDestination.setOnDragListener(this);
  }

  @Override
  public boolean onTouch(View v, MotionEvent event) {

      View.DragShadowBuilder mShadow = new View.DragShadowBuilder(v);
      ClipData.Item item = new ClipData.Item(v.getTag().toString());
      String[] mimeTypes = {ClipDescription.MIMETYPE_TEXT_PLAIN};
      ClipData data = new ClipData(v.getTag().toString(), mimeTypes, item);

      switch (v.getId()) {
          case R.id.btnYes:

              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                  v.startDragAndDrop(data, mShadow, null, 0);
              } else {
                  v.startDrag(data, mShadow, null, 0);
              }

              break;
          case R.id.btnNo:

              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                  v.startDragAndDrop(data, mShadow, null, 0);
              } else {
                  v.startDrag(data, mShadow, null, 0);
              }
              break;

      }

      return false;
  }

  @Override
  public boolean onDrag(View v, DragEvent event) {
      switch (event.getAction()) {
          case DragEvent.ACTION_DRAG_STARTED:

              ((ImageView) v).setColorFilter(Color.YELLOW);

              v.invalidate();
              return true;

          case DragEvent.ACTION_DRAG_ENTERED:

              String clipData = event.getClipDescription().getLabel().toString();
              switch (clipData) {
                  case "YES":
                      ((ImageView) v).setColorFilter(ContextCompat.getColor(MainActivity.this, R.color.green), android.graphics.PorterDuff.Mode.MULTIPLY);
                      break;
                  case "NO":
                      ((ImageView) v).setColorFilter(ContextCompat.getColor(MainActivity.this, R.color.colorAccent), android.graphics.PorterDuff.Mode.MULTIPLY);
                      break;
              }

              v.invalidate();
              return true;

          case DragEvent.ACTION_DRAG_LOCATION:
              return true;

          case DragEvent.ACTION_DRAG_EXITED:

              ((ImageView) v).clearColorFilter();
              ((ImageView) v).setColorFilter(Color.YELLOW);

              v.invalidate();
              return true;

          case DragEvent.ACTION_DROP:

              clipData = event.getClipDescription().getLabel().toString();
              Toast.makeText(getApplicationContext(),clipData, Toast.LENGTH_SHORT).show();

              v.invalidate();
              return true;

          case DragEvent.ACTION_DRAG_ENDED:

              ((ImageView) v).clearColorFilter();
              if (event.getResult()) {
                  Toast.makeText(MainActivity.this, "Awesome!", Toast.LENGTH_SHORT).show();

              } else {
                  Toast.makeText(MainActivity.this, "Aw Snap! Try dropping it again", Toast.LENGTH_SHORT).show();
              }
              return true;

          default:
              return false;
      }
  }
}

让我们分析一下我们是如何产生以上代码的。

  • 如前所述,我们已经在onTouchListener上注册了要拖动(按钮)的视图,而ImageView是应将其拖放到的视图。
    因此,onDragListener和随后的DragEvent被注册到其上。

  • " onTouch"方法是我们传递" ClipData"并创建" DragShadowBuilder"视图的实例的地方(最终将其拖到第一位)。

  • 随着Android Nougat(API 24)的引入,不赞成使用startDrag()方法。
    因此,我们对API> = 24使用startDragAndDrop()方法。

  • startDrag/startDragAndDrop方法需要ClipData以及DragShadow实例。

  • 当拖动开始时,我们使用setColorFilter方法在onDrag方法中的ACTION_DRAG_STARTED案例中的拖放视图上添加背景色。

  • 每个开关案例都"返回true"。
    返回false表示不想为该特定DragEvent触发onDrag方法。

  • 在" ACTION_DRAG_ENTERED"情况下,我们基于哪个Button进入放置视图范围来更改放置视图的背景色。
    按钮的类型由ClipData确定。

  • 要检索ClipData,我们将以下方法链接在一起:" event.getClipDescription().getLabel().toString()"。

  • " event.getResult()"返回一个布尔值,该布尔值确定DragShadow是否被放到了应该放置的范围之内。

  • v.invalidate()用于强制重绘ImageView。