關于ViewDragHelper常用操作整理

ViewDragHelper 簡單實現類:
public class VGHLayout extends LinearLayout {

  private ViewDragHelper mHelper;
  private View mFreeView;
  private View mEdgeView;
  private View mReleaseView;

  private Point autoBackPoint = new Point();

  public VGHLayout(Context context) {
    this(context, null);
  }

  public VGHLayout(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public VGHLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    /**
     * 定義構造函數
     * this-指定當前使用的容器
     * 1.0f-敏感度,越大越敏感
     * Callback-拖拽方法回掉
     *
     * ps:helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
     */
    mHelper = ViewDragHelper.create(this, 1.0f, new DemoDragHelperCallback());
    mHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
  }

  class DemoDragHelperCallback extends ViewDragHelper.Callback {
       /**
       * 如果返回true,則表示可以捕獲該view,你可以根據傳入的第一個view參數
       * 決定哪些可以捕獲
       */
      @Override
      public boolean tryCaptureView(View child, int pointerId) {
        return child != mEdgeView;
      }

      /**
       * 可以在該方法中,對child移動的邊界進行控制
       * left和top就是當前view即將移動的位置,需要做相關的邊界控制
       */
      @Override
      public int clampViewPositionHorizontal(View child, int left, int dx) {
        final int leftBound = getPaddingLeft();
        final int rightBound = getWidth() - getPaddingRight() - child.getWidth();

        return Math.min(Math.max(left, leftBound), rightBound);
      }

      @Override
      public int clampViewPositionVertical(View child, int top, int dy) {
        final int topBound = getPaddingTop();
        final int bottomBound = getHeight() - getPaddingBottom() - child.getHeight();

        return Math.min(Math.max(top, topBound), bottomBound);
      }

      /**
       * 我們把我們的TextView全部加上clickable=true,意思就是子View可以消耗事件。再次運行,
       * 你會發現本來可以拖動的View不動了,主要是因為,如果子View不消耗事件,
       * 那么整個手勢(DOWN-MOVE*-UP)都是直接進入onTouchEvent,在onTouchEvent的DOWN的時候就確定了captureView。
       * 如果消耗事件,那么就會先走onInterceptTouchEvent方法,判斷是否可以捕獲,
       * 而在判斷的過程中會去判斷另外兩個回調的方法:getViewHorizontalDragRange和getViewVerticalDragRange,
       * 只有這兩個方法返回大于0的值才能正常的捕獲,方法的返回值為當前view的移動范圍,如果只是移動一個方向
       * 則只重寫其中的方法即可
       */
      @Override
      public int getViewHorizontalDragRange(View child) {
        return getMeasuredWidth() - child.getMeasuredWidth();
      }

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


      /**
       * 手指釋放時回掉的方法
       */
      @Override
      public void onViewReleased(View releasedChild, float xvel, float yvel) {
        if (releasedChild == mReleaseView) {
            /**
             * 使用下沉的回掉效果
             */
            /**
             * smoothSlideViewTo,將子控件平滑移動到指定位置。它調用了forceSettleCapturedViewAt,在forceSettleCapturedViewAt中會調用Scroller.startScroll方法。
             * settleCapturedViewAt,將子控件移動到指定位置。與smoothSlideViewTo相似,它也調用了forceSettleCapturedViewAt方法。與smoothSlideViewTo不同的是,它以手指離開時的速度為初速度,將子控件移動到指定位置。
             * captureChildView方法,將指定的子控件移動到指定位置。與上面兩個方法不同的是,它直接移動到指定位置,不會有時間上的等待,也就是說不會有那種平滑的感覺。
             * lingCapturedView方法,與settleCapturedViewAt類似,都使用了手指離開時的速度作為計算的當前位置的依據。
             */
            mHelper.settleCapturedViewAt(autoBackPoint.x, autoBackPoint.y);
            /**
             * 重新進行繪制
             */
            invalidate();
        }
    }

    /**
     * 在邊界拖動時候進行回掉
     */
    @Override
    public void onEdgeDragStarted(int edgeFlags, int pointerId) {

        mHelper.captureChildView(mEdgeView, pointerId);
    }

    /**
     * 當ViewDragHelper狀態發生變化時回調(IDLE,DRAGGING,SETTING[自動滾動時])
     */
    @Override
    public void onViewDragStateChanged(int state) {
        super.onViewDragStateChanged(state);
    }

    /**
     * 當captureview的位置發生改變時回調
     */
    @Override
    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
        super.onViewPositionChanged(changedView, left, top, dx, dy);
    }

    /**
     * 當captureview被捕獲時回調
     */
    @Override
    public void onViewCaptured(View capturedChild, int activePointerId) {
        super.onViewCaptured(capturedChild, activePointerId);
    }

    /**
     * true的時候會鎖住當前的邊界,false則unLock。
     */
    @Override
    public boolean onEdgeLock(int edgeFlags) {
        return super.onEdgeLock(edgeFlags);
    }

    /**
     * 當觸摸到邊界時回調。
     */
    @Override
    public void onEdgeTouched(int edgeFlags, int pointerId) {
        super.onEdgeTouched(edgeFlags, pointerId);
    }

    /**
     * 改變同一個坐標(x,y)去尋找captureView位置的方法。(具體在:findTopChildUnder方法中)
     */
    @Override
    public int getOrderedChildIndex(int index) {
        return super.getOrderedChildIndex(index);
    }

  }


  /**
   * 將touch事件的攔截和處理交由DragHelper進行處理
   */
  @Override
  public boolean onInterceptHoverEvent(MotionEvent event) {
    return mHelper.shouldInterceptTouchEvent(event);
  }

  @Override
 public boolean onTouchEvent(MotionEvent event) {
    mHelper.processTouchEvent(event);

    return true;
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);

    autoBackPoint.x = mReleaseView.getLeft();
    autoBackPoint.y = mReleaseView.getTop();
  }

  /**
   * 填充完畢后調用此方法
   */
  @Override
  protected void onFinishInflate() {
    super.onFinishInflate();

    mFreeView = getChildAt(0);
    mEdgeView = getChildAt(1);
    mReleaseView = getChildAt(2);
  }

  /**
   * 重寫computeScroll方法,實現fling或者松手后的滑動效果
   * computeScroll也不是來讓ViewGroup滑動的,真正讓ViewGroup滑動的是scrollTo,scrollBy.
   * computeScroll的作用是計算ViewGroup如何滑動
   * 調用startScroll()是不會有滾動效果的,只有在computeScroll()獲取滾動情況,做出滾動的響應
   * computeScroll在父控件執行drawChild時,會調用這個方法
   * 并在ViewDragHelper.Callback的onViewReleased()方法里調用
   * settleCapturedViewAt()、flingCapturedView(),或在任意地方調用smoothSlideViewTo()方法。
   */
  @Override
  public void computeScroll() {
    /**
     * continueSettling,當ViewDragHelper的狀態是STATE_SETTLING(自動滾動)時,
     * 該方法會將子控件自動進行移動(使用Scroller實現測量空前當前應該處于的位置,
     * 然后調用View.offsetLeftAndRight和View.offsetTopAndBottom方法進行移動)。
     * 在自定義控件中的computeScroll方法中調用。computeScroll方法用于處理自動移動的狀況,
     * 通常是在MONTIONEVENT.ACTION_UP的時候,調用Scroller.startScroll方法。
     */
    /**
     * 解析:
     * continueSettling方法調用的是View.offsetLeftAndRight和offsetTopAndBottom方法來實現滑動效果。
     * ViewDragHelper中另一個方法dragTo,同樣使用的是offsetLeftAndRight實現滑動,
     * 而dragTo只在processTouchEvent的ACTION_MOVE中調用。
     */
    if (mHelper.continueSettling(true)) {
        invalidate();
    }
  }

  /**
   * 方法正常流程的回掉順序:
   * shouldInterceptTouchEvent:
   *DOWN:
   * getOrderedChildIndex(findTopChildUnder)
   * ->onEdgeTouched
   *
   * MOVE:
   * getOrderedChildIndex(findTopChildUnder)
   * ->getViewHorizontalDragRange &
   * getViewVerticalDragRange(checkTouchSlop)(MOVE中可能不止一次)
   * ->clampViewPositionHorizontal&
   * clampViewPositionVertical
   * ->onEdgeDragStarted
   * ->tryCaptureView
   * ->onViewCaptured
   * ->onViewDragStateChanged
   *
   * processTouchEvent:
   *
   * DOWN:
   * getOrderedChildIndex(findTopChildUnder)
   * ->tryCaptureView
   * ->onViewCaptured
   * ->onViewDragStateChanged
   * ->onEdgeTouched
   * MOVE:
   * ->STATE==DRAGGING:dragTo
   * ->STATE!=DRAGGING:
   * onEdgeDragStarted
   * ->getOrderedChildIndex(findTopChildUnder)
   * ->getViewHorizontalDragRange&
   * getViewVerticalDragRange(checkTouchSlop)
   * ->tryCaptureView
   * ->onViewCaptured
   * ->onViewDragStateChanged
   * 從上面也可以解釋,我們在之前TextView(clickable=false)的情況下,
   * 沒有編寫getViewHorizontalDragRange方法時,是可以移動的。
   * 因為直接進入processTouchEvent的DOWN,然后就onViewCaptured、onViewDragStateChanged(進入DRAGGING狀態),
   * 接下來MOVE就直接dragTo了。
   * 而當子View消耗事件的時候,就需要走shouldInterceptTouchEvent,MOVE的時候經過一系列的判斷(getViewHorizontalDragRange,clampViewPositionVertical等),
   * 才能夠去tryCaptureView。
   */
}
效果圖:
效果如圖
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容