關于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。
   */
}
效果圖:
效果如圖
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容