下面記錄下自己對(duì)ViewDragHelper的理解.
一锯厢、什么是ViewDragHelper
首先,ViewDragHelper實(shí)在官方supportV4包里面叭爱,為了方便開(kāi)發(fā)者自定義自己的控件而提出來(lái)的勤婚,其中官方的DrawLayout的基礎(chǔ)實(shí)現(xiàn)類是通過(guò)ViewDragHelper實(shí)現(xiàn)的。我相信讀者應(yīng)該都對(duì)DrawLayout比較熟悉涤伐,其實(shí)自定義一個(gè)類似DrawLayout的功能馒胆,并不是一件難事!好了凝果,現(xiàn)在直接進(jìn)入正文祝迂。
二、入門小Demo
首先簡(jiǎn)單說(shuō)明下使用ViewDragHelper的步驟(這里筆者使用典型的View拖動(dòng)小Demo器净,盡可能加入注釋描述ViewDragHelper應(yīng)用)型雳。
1.創(chuàng)建一個(gè)ViewGroup
2.實(shí)例化ViewDragHelper對(duì)象
3.重新部分ViewDragHelper對(duì)象回調(diào)接口方法
(一)利用ViewDragHelper,實(shí)現(xiàn)子View可以拖動(dòng)效果
首先我們直接看代碼:
/**
* 簡(jiǎn)單的ViewDrawHleper應(yīng)用 案例
* Created by wsy on 2016/2/29.
*/
public class BasicMoveView extends LinearLayout {
private final String TAG = "Wusy BasicMoveView";
private ViewDragHelper dragHelper;
private Context mContext;
private View mDragView1;
private View mDragView2;
private View mDragView3;
public BasicMoveView(Context context) {
super(context);
this.mContext = context;
init();
}
public BasicMoveView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
init();
}
private void init() {
dragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View view, int i) {
return mDragView1 == view || mDragView2 == view || mDragView3 == view;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
int lefPad = getPaddingLeft();
int realWidth = getWidth() - child.getWidth();
int newLeft = Math.min(Math.max(left, lefPad), realWidth);
return newLeft;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
int topPad = getPaddingTop();
int realHeight = getHeight() - child.getHeight();
int newTop = Math.min(Math.max(top, topPad), realHeight);
return newTop;
}
});
}
/**
* 事件從父-》子控件 事件傳遞
**/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
dragHelper.cancel();
return false;
}
return dragHelper.shouldInterceptTouchEvent(ev);
}
/**
* 事件從父《-子控件 事件傳遞
**/
@Override
public boolean onTouchEvent(MotionEvent event) {
dragHelper.processTouchEvent(event);
return true;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mDragView1 = getChildAt(0);
mDragView2 = getChildAt(1);
mDragView3 = getChildAt(2);
}
}
說(shuō)明:
1.首先創(chuàng)建ViewDragHelper實(shí)例山害,是通過(guò)靜態(tài)工程提供實(shí)例化方法:
dragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {... });
關(guān)于這里三個(gè)形參的解釋:1.自身拖動(dòng)對(duì)象(必須為ViewGroup) 2.敏感度(越大纠俭,觸發(fā)越敏感) 3.操作回調(diào)。
2.觸摸事件傳遞給ViewDragHelper必須重寫(xiě)的兩個(gè)方法
/**
* 事件從父-》子控件 事件傳遞 決定當(dāng)前事件是否要被截?cái)? **/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return dragHelper.shouldInterceptTouchEvent(ev);
}
/**
* 事件從父《-子控件 事件傳遞 決定當(dāng)前事件的處理
**/
@Override
public boolean onTouchEvent(MotionEvent event) {
dragHelper.processTouchEvent(event);
return true;
}
3.一切觸摸移動(dòng)事件取決于ViewDragHelper.CallCack回調(diào)重寫(xiě)浪慌。
這里我們看下如果實(shí)現(xiàn)子View需要重寫(xiě)的三個(gè)方法
@Override
public boolean tryCaptureView(View childView, int i) {
return mDragView1 == childView || mDragView2 == childView|| mDragView3 == childView;
}
/** 水平控制子View位置 left為x坐標(biāo) childView橫向的移動(dòng)的范圍**/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
int lefPad = getPaddingLeft();
int realWidth = getWidth() - child.getWidth();
int newLeft = Math.min(Math.max(left, lefPad), realWidth);
return newLeft;
}
/** 垂直控制子View位置 top y坐標(biāo) childView縱向的移動(dòng)的范圍**/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
int topPad = getPaddingTop();
int realHeight = getHeight() - child.getHeight();
int newTop = Math.min(Math.max(top, topPad), realHeight);
return newTop;
}
(1)tryCaptureView()方法主要決定受控制子View冤荆,當(dāng)判斷滿足條件的childView,即返回true后即可回調(diào)下面出發(fā)事件
(3)clampViewPositionHorizontal() 控制子View移動(dòng)位置权纤,返回值為橫向移動(dòng)范圍
(3)clampViewPositionVertical()控制子View移動(dòng)位置钓简,返回值為縱向移動(dòng)范圍
4.具體應(yīng)用
一下是效果與布局代碼
<cn.wsy.viewdraghelper.Views.BasicMoveView 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"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/text1"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#000000" />
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#ccc" />
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#44ff0000" />
</cn.wsy.viewdraghelper.Views.BasicMoveView>
(二)通過(guò)ViewDragHelper實(shí)現(xiàn)邊緣控制與運(yùn)動(dòng)回放
/** 邊緣控制 **/
@Override
public void onEdgeTouched(int edgeFlags, int pointerId) {
super.onEdgeTouched(edgeFlags, pointerId);
Toast.makeText(mContext, "在最左邊", Toast.LENGTH_SHORT).show();
}
/** 邊緣動(dòng)態(tài)控制view **/
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
super.onEdgeDragStarted(edgeFlags, pointerId);
dragHelper.captureChildView(mDragView1, pointerId);
}
/** 拖動(dòng)回放 **/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if (releasedChild == mDragView3) {
Log.i(TAG,"指定View被釋放了,位置在:x-> "+mDragView3.getX()+" y-> "+mDragView3.getY());
dragHelper.settleCapturedViewAt(backViewPoint.x, backViewPoint.y);//源碼內(nèi)部mScroller.startScroll,配合computeScroll才能實(shí)現(xiàn)invalidate()
invalidate();
}
}
onEdgeTouched() 前提指定邊緣方向監(jiān)聽(tīng)后汹想,如果觸摸坐標(biāo)達(dá)到邊緣即出發(fā)事件
onEdgeDragStarted() 前提指定邊緣方向監(jiān)聽(tīng)后外邓,觸摸坐標(biāo)達(dá)到邊緣后,當(dāng)觸摸坐標(biāo)移動(dòng)的時(shí)候會(huì)回調(diào)這個(gè)方法古掏。dragHelper.captureChildView(mDragView1, pointerId) 這里是當(dāng)坐標(biāo)在屏幕邊緣的時(shí)候损话,坐標(biāo)移動(dòng)回調(diào)返回的pointerId,直接將制定捕獲的View槽唾,進(jìn)行位移操作丧枪。
onViewReleased() 當(dāng)其中一個(gè)捕獲到的子View光涂,釋放的時(shí)候回調(diào)這個(gè)方法。這里View移動(dòng)后回滾到原來(lái)位置的原理是豪诲,初始化的Onlayout的時(shí)候,先臨時(shí)存儲(chǔ)了原來(lái)的x挂绰,y坐標(biāo)屎篱,當(dāng)捕獲的View釋放的時(shí)候,重新讓它繪制到這個(gè)位置葵蒂。注意:這里需要配合computeScroll方法(我們看settleCapturedViewAt源碼得知通過(guò)mScroller.startScroll滾動(dòng)改變位移)交播。
//設(shè)置使能邊緣
dragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
指定邊緣方向監(jiān)聽(tīng)。
@Override
public void computeScroll() {
if(dragHelper.continueSettling(true)){
invalidate();
} }
(三)注意
如果其中子View有監(jiān)聽(tīng)事件践付,必須重寫(xiě)以下兩個(gè)方法:
/**添加了clickable = true 秦士,都記得重寫(xiě)下面這兩個(gè)方法**/
@Override
public int getViewHorizontalDragRange(View child) {
return getMeasuredWidth()-child.getMeasuredWidth();
}
@Override
public int getViewVerticalDragRange(View child) {
return getMeasuredHeight()-child.getMeasuredHeight();
}
三、簡(jiǎn)單描述未使用接口作用
到此永高,我們列一下所有的Callback方法隧土,看看還有哪些沒(méi)用過(guò)的:
onViewDragStateChanged
當(dāng)ViewDragHelper狀態(tài)發(fā)生變化時(shí)回調(diào)(IDLE,DRAGGING,SETTING[自動(dòng)滾動(dòng)時(shí)])
onViewPositionChanged
當(dāng)captureview的位置發(fā)生改變時(shí)回調(diào)
onViewCaptured
當(dāng)captureview被捕獲時(shí)回調(diào)
onViewReleased 已用
onEdgeTouched
當(dāng)觸摸到邊界時(shí)回調(diào)。
onEdgeLock
true的時(shí)候會(huì)鎖住當(dāng)前的邊界命爬,false則unLock曹傀。
onEdgeDragStarted 已用
getOrderedChildIndex
改變同一個(gè)坐標(biāo)(x,y)去尋找captureView位置的方法。(具體在:findTopChildUnder方法中)
getViewHorizontalDragRange 已用
getViewVerticalDragRange 已用
tryCaptureView 已用
clampViewPositionHorizontal 已用
clampViewPositionVertical 已用
ok饲宛,至此所有的回調(diào)方法都有了一定的認(rèn)識(shí)皆愉。
總結(jié)下,方法的大致的回調(diào)順序:
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
ok艇抠,上述是正常情況下大致的流程幕庐,當(dāng)然整個(gè)過(guò)程可能會(huì)存在很多判斷不成立的情況。
四家淤、總結(jié)
http://blog.csdn.net/pi9nc/article/details/39583377
http://blog.csdn.net/lmj623565791/article/details/46858663
傻小孩b mark