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是能夠拖動的。
四、處理滑動邊緣(分爲滑動左邊緣仍是右邊緣: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#