ViewDragHelper詳解

2013谷歌i/o大會上介紹了兩個新的layout: SlidingPaneLayout和DrawerLayout,如今這倆個類被普遍的運用,其實研究他們的源碼你會發現這兩個類都運用了ViewDragHelper來處理拖動。ViewDragHelper是framework中鮮爲人知卻很是有用的一個工具 html

ViewDragHelper用來簡化view拖拽操做的幫助類。須要注意的是:ViewDragHelper是做用在一個ViewGroup上,也就是說他不能直接做用到被拖拽的view, 其實這也很好理解,由於view在佈局中的位置是父ViewGroup決定的。 android

用ViewGroup實現一個能夠拖動的view開發步驟: ide

 

一、獲取ViewDragHelper的實例,注意,這裏不能直接new,而是使用ViewDragHelper的一個靜態方法: 工具

ViewDragHelper.create(ViewGroup forParent, float sensitivity, ViewDragHelper.Callback cb); 佈局

參數1: 一個ViewGroup, 也就是ViewDragHelper將要用來拖拽誰下面的子view 動畫

參數2:靈敏度,通常設置爲1.0f就行,參數越大,靈敏度越高 this

參數3:一個回調,用來處理拖動到位置 spa

二、繼承ViewDragHelper.Callback類,該類有個抽象方法:tryCaptureView(View view, int pointerId) 表示嘗試捕獲子view,返回值能夠決定一個parentview中哪一個子view能夠拖動。 .net

三、重寫兩個方法int clampViewPositionHorizontal(View child, int left, int dx)和int clampViewPositionHorizontal(View child, int left, int dx) 這兩個方法分別用來處理x方向和y方向的拖動的,返回值該child如今的位置。在這倆個方法裏續作處理,以防止view超出了邊界。 xml

四、重寫ViewGroup的onInterceptTouchEvent(MotionEvent ev)用來攔截事件

五、重寫ViewGroup的onTouchEvent(MotionEvent event) 在這裏面只要作兩件事:mDragHelper.processTouchEvent(event);處理攔截到的事件,這個方法會在返回前分發事件;return true 表示消費了事件。

簡單的5步就能夠實現一個能夠任意拖動到view了。

 

詳細代碼:

 

public class CustomView extends LinearLayout {
 private ViewDragHelper mDragHelper; //ViewDragHelper通常用在一個自定義ViewGroup的內部

 public CustomView(Context context) {
  super(context);
  init();
 }
 

 public CustomView(Context context, AttributeSet attrs) {
  super(context, attrs);
  init();
 }

 public CustomView(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  init();
 }

 private void init() {
  /**
   * @params ViewGroup forParent 必須是一個ViewGroup
   * @params float sensitivity 靈敏度
   * @params Callback cb 回調
   */
  mDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragCallback());
 }
 
 private class ViewDragCallback extends ViewDragHelper.Callback {
  /**
   * 嘗試捕獲子view,必定要返回true
   * @param View child 嘗試捕獲的view
   * @param int pointerId 指示器id?
   * 這裏能夠決定哪一個子view能夠拖動
   */
  @Override
  public boolean tryCaptureView(View view, int pointerId) {
//   return mCanDragView == view;
   return true;
  }
  
  /**
   *返回滑動範圍,用來設置拖拽的範圍。
   *大於0便可,不會真正限制child的移動範圍。
   *內部用來計算是否此方向是否能夠拖拽,以及釋放時動畫執行時間
   */
  @Override
  public int getViewHorizontalDragRange(View child) {
   return getMeasuredWidth();
  };
  
  /**
   * releasedChild : 釋放的對象
   * xvel:x軸上的每秒速率
   * yvel:y軸上的每秒速率
   */
  @Override
  public void onViewReleased(View releasedChild, float xvel, float yvel) {
   // 當釋放滑動的view時,處理最後的事情。(執行開啓或關閉的動畫)
   super.onViewReleased(releasedChild, xvel, yvel);
  };
  
  /**
   * 處理水平方向上的拖動
   * @param View child 被拖動到view
   * @param int left 移動的x軸的距離
   * @param int dx 所產生的偏移量
   */
  @Override
  public int clampViewPositionHorizontal(View child, int left, int dx) {
   System.out.println("left = " + left + ", dx = " + dx);
   
   // 兩個if主要是爲了讓viewViewGroup裏
   if(getPaddingLeft() > left) {
    return getPaddingLeft();
   }
   
   if(getWidth() - child.getWidth() < left) {
    return getWidth() - child.getWidth();
   }
   
   return left;
  }
  
  /**
   *  處理豎直方向上的拖動
   * @param View child 被拖動到view
   * @param int top 移動y軸的距離
   * @param int dy 所產生的偏移量
   */
  @Override
  public int clampViewPositionVertical(View child, int top, int dy) {
   // 兩個if主要是爲了讓viewViewGroup裏
   if(getPaddingTop() > top) {
    return getPaddingTop();
   }
   
   if(getHeight() - child.getHeight() < top) {
    return getHeight() - child.getHeight();
   }
   
   return top;
  }
  
  /**
   * 當拖拽到狀態改變時回調
   * @params 新的狀態
   */
  @Override
  public void onViewDragStateChanged(int state) {
   switch (state) {
   case ViewDragHelper.STATE_DRAGGING:  // 正在被拖動
    break;
   case ViewDragHelper.STATE_IDLE:  // view沒有被拖拽或者 正在進行fling/snap
    break;
   case ViewDragHelper.STATE_SETTLING: // fling完畢後被放置到一個位置
    break;
   }
   super.onViewDragStateChanged(state);
  }
 }
 
 /**
  * 要讓ViewDragHelper可以處理拖動須要將觸摸事件傳遞給ViewDragHelper
  */
 @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
  switch (ev.getAction()) {
  case MotionEvent.ACTION_CANCEL:
  case MotionEvent.ACTION_DOWN:
   mDragHelper.cancel(); // 至關於調用 processTouchEvent收到ACTION_CANCEL
   break;
  }
  
  /**
   * 檢查是否能夠攔截touch事件
   * 若是onInterceptTouchEvent能夠return true 則這裏return true
   */
  return mDragHelper.shouldInterceptTouchEvent(ev);
 }
 
 @Override
 public boolean onTouchEvent(MotionEvent event) {
  /**
   * 處理攔截到的事件
   * 這個方法會在返回前分發事件
   */
  mDragHelper.processTouchEvent(event);
  return true;
 }


}

在xml中使用:

 

<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"
    tools:context=".MainActivity" >

    <com.example.viewdragerhelpertest.CustomView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <View
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="#FFFF0000" />
    </com.example.viewdragerhelpertest.CustomView>

</RelativeLayout>

 

看一下效果吧:


 

下面是拖動行爲的處理幾種效果

一、處理橫向的拖動:

 @Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
  Log.d("DragLayout", "clampViewPositionHorizontal " + left + "," + dx);
  final int leftBound = getPaddingLeft();
  final int rightBound = getWidth() - mDragView.getWidth();
  final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
  return newLeft;
}


二、處理縱向的拖動:

@Override
public int clampViewPositionVertical(View child, int top, int dy) {
  final int topBound = getPaddingTop();
  final int bottomBound = getHeight() - mDragView.getHeight();
  final int newTop = Math.min(Math.max(top, topBound), bottomBound);
  return newTop;
}

 

三、指定那個子View能夠拖動:

 

如今假設有兩個子views (mDragView1和mDragView2)  ,以下實現tryCaptureView以後,則只有mDragView1是能夠拖動的。

 

@Override
public boolean tryCaptureView(View child, int pointerId) {
  returnchild == mDragView1;
}

 

 四、處理滑動邊緣(分爲滑動左邊緣仍是右邊緣:EDGE_LEFT和EDGE_RIGHT):

 下面的代碼設置了能夠處理滑動左邊緣:

mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);

假如如上設置,onEdgeTouched方法會在左邊緣滑動的時候被調用,這種狀況下通常都是沒有和子view接觸的狀況。

 @Override
public void onEdgeTouched(int edgeFlags, int pointerId) {
    super.onEdgeTouched(edgeFlags, pointerId);
    Toast.makeText(getContext(), "edgeTouched", Toast.LENGTH_SHORT).show();
}

若是你想在邊緣滑動的時候根據滑動距離移動一個子view,能夠經過實現onEdgeDragStarted方法,並在onEdgeDragStarted方法中手動指定要移動的子View

@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
    mDragHelper.captureChildView(mDragView2, pointerId);
}

 


 

 

參考文章:http://www.xuebuyuan.com/2225442.html

http://blog.csdn.net/jianghejie123/article/details/39315319#
相關文章
相關標籤/搜索