ViewDragHelper實現view拖拽

在瞭解View拖拽之前,應該瞭解android的事件傳遞機制

ViewDragHelper 可以方便我們快速實現View拖拽功能。主要步驟如下

  1. 創建ViewDragHelper實例
  2. 實現ViewDragHelper的CallBack編寫
  3. 處理ViewGroup觸摸事件

下面就是一個可以實現View拖拽實現的ViewGroup

public class VDHLinearLayout extends LinearLayout {
  ViewDragHelper dragHelper;

  public VDHLinearLayout(Context context, AttributeSet attrs) {
      super(context, attrs);
      dragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
          @Override
          public boolean tryCaptureView(View child, int pointerId) {
              return true;
          }

          @Override
          public int clampViewPositionVertical(View child, int top, int dy) {
              return top;
          }

          @Override
          public int clampViewPositionHorizontal(View child, int left, int dx) {
              return left;
          }
      });
  }

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
      return dragHelper.shouldInterceptTouchEvent(ev);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
      dragHelper.processTouchEvent(event);
      return true;
  }
}
  1. 創建ViewDragHelper實例
dragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {});
/*
函數原型
@param forParent 當前的ViewGroup
@param sensitivity 設置拖拽靈敏度,數字越大越靈敏。默認給1就行(即系統的默認值)
@param cb 觸摸過程的回調加函數
*/
public static ViewDragHelper create(@NonNull ViewGroup forParent, float sensitivity,
            @NonNull Callback cb) {

  1. 實現ViewDragHelper.Callback相關方法
new ViewDragHelper.Callback() {
	/*
		返回true表示捕獲當前view,如果不想使某個view移動,可以在內部判斷當前View飯後返回false
	*/
   @Override
   public boolean tryCaptureView(View child, int pointerId) {
       return true;
   }
	
	/*
	child垂直移動的距離,top 表示y軸座標,相對於ViewGroup而言。dy表示偏移的距離
	返回值確定view最終的y軸座標,如果不想垂直移動return 0
	*/
   @Override
   public int clampViewPositionVertical(View child, int top, int dy) {
       return top;
   }
	
	/*
	child垂直移動的距離,left表示x軸座標,相對於ViewGroup而言。dx表示偏移的距離
	返回值確定view最終的x軸座標,如果不想水平移動return 0
	*/
   @Override
   public int clampViewPositionHorizontal(View child, int left, int dx) {
       return left;
   }
}
  1. 處理ViewGroup觸摸事件(原理參考)android的事件傳遞機制

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
   return dragHelper.shouldInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
   dragHelper.processTouchEvent(event);
   return true;
}

在這裏插入圖片描述

佈局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<com.example.qwe.VDHLinearLayout 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"
    android:orientation="vertical"
    tools:context=".SecoundActivity">

    <TextView
        android:id="@+id/text_1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="20dp"
        android:text="not clickable"
        android:background="@mipmap/ic_launcher_round"
        />

    <TextView
        android:id="@+id/text_2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="20dp"
        android:text="clickable"
        android:clickable="true"
        android:background="@mipmap/ic_launcher_round"
        />



</com.example.qwe.VDHLinearLayout>

此時我們把text_2 指定clickable屬性,此時我們發現控件沒辦法移動了。這是因爲text_2 控件消耗了ACTION_DOWN事件,導致ViewGroup沒有調用onTouchEvent。
我們看看onTouchEvent中的dragHelper.processTouchEvent做了一些什麼

 public void processTouchEvent(@NonNull MotionEvent ev) {
      	...
        mVelocityTracker.addMovement(ev);

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
               ...
                tryCaptureViewForDrag(toCapture, pointerId);
                ...
            }
		...
	}

    boolean tryCaptureViewForDrag(View toCapture, int pointerId) {
        ...
            //這裏設置窗口狀態爲STATE_DRAGGING
            captureChildView(toCapture, pointerId);
       ...
    }

 public void captureChildView(@NonNull View childView, int activePointerId) {
     ...
        setDragState(STATE_DRAGGING);
     ...
    }

所以在ViewGroup的onTouchEvent裏面將狀態設置爲STATE_DRAGGING,此時我們如果移動窗口ACTION_MOVE,dragHelper.processTouchEvent 將返回true,攔截Move事件。

 public boolean shouldInterceptTouchEvent(@NonNull MotionEvent ev) {
 	return mDragState == STATE_DRAGGING;
 }

因爲clickable消耗了ACTION_DOWN導致ViewGroup的onTouchEvent無法被調用 ,從而導致tryCaptureViewForDrag沒有調用,mDragState 狀態不爲STATE_DRAGGING。所以後面當觸發ACTION_MOVE的時候返回false(mDragState == STATE_DRAGGING)故無法移動。此時看看processTouchEvent的ACTION_MOVE事件處理。

 public boolean shouldInterceptTouchEvent(@NonNull MotionEvent ev) {
 ...
 case MotionEvent.ACTION_MOVE: {
                if (mInitialMotionX == null || mInitialMotionY == null) break;

                ...
                        final int hDragRange = mCallback.getViewHorizontalDragRange(toCapture);
                        final int vDragRange = mCallback.getViewVerticalDragRange(toCapture);
                        if ((hDragRange == 0 || (hDragRange > 0 && newLeft == oldLeft))
                                && (vDragRange == 0 || (vDragRange > 0 && newTop == oldTop))) {
                            break;
                        }
                    }
                  ...
                    if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
                        break;
                    }
                }
                saveLastMotion(ev);
                break;
            }
 ...
 }

從上述代碼我們發現如果mCallback.getViewHorizontalDragRange = 0 && mCallback.getViewVerticalDragRange = 0那麼程序將Break,過濾掉下面tryCaptureViewForDrag的執行。從而導致mDragState 沒有辦法設置爲STATE_DRAGGING,從而返回false(mDragState == STATE_DRAGGING)。所以爲了使tryCaptureViewForDrag執行,那麼我們需要重寫mCallback.getViewHorizontalDragRange 或者 mCallback.getViewVerticalDragRange 返回非0就可以了。

            @Override
            public int getViewHorizontalDragRange(@NonNull View child) {
                return getMeasuredWidth();
            }

            @Override
            public int getViewVerticalDragRange(@NonNull View child) {
                return getMeasuredHeight();
            }

此時在拖拽具有clickable 的view就OK了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章