出处:ViewDragHelper是V4包下的一个文件。
我们在自定义ViewGroup的时候,有时候觉得很头疼,其中很大一部分原因就是因为事件处理太麻烦,需要记录大量的成员变量,还有各种判断等等。
Google也感觉到了这个麻烦,所以ViewDragHelper就出现了,ViewDragHelper功能到底是什么呢?从字面意思上看是View拖拽的帮助类,简而言之就是,在简化View拖拽的时候的代码量。我们先来看一看到底这个类的帮助有多大?
先来看一个测拉菜单效果
先来分析一下,如果我们不借助这个帮助类实现情况:
1、重写一个RelativeLayout;
2、重写其中的onInterceptTouchEvent(做相应的事件拦截操作)
2、重写其中的onTouchEvent方法(这里面做大量的代码)
3、定义一个Scroller变量,用来控制手指松开以后的操作
这里我就不去写代码了,代码量肯定很大!
再来看看借助ViewDragHelper类实现的代码
/**
* Created by gyzhong on 15/4/8.
*/
public class VdhLayout01 extends RelativeLayout {
private ViewDragHelper mViewDragHelper;
private View mCaptureView;
private float mInitialMotionX;
private float mInitialMotionY;
private boolean mIsUnableToDrag;
private int mSlideRange;
private float mSlideOffset;
public VdhLayout01(Context context) {
this(context, null);
}
public VdhLayout01(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public VdhLayout01(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
mViewDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCall());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mCaptureView = findViewById(R.id.id_capture_view);
TextView textView = (TextView) findViewById(R.id.id_text);
textView.setText(Shakespeare.DIALOGUE[0]);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mCaptureView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mCaptureView.getViewTreeObserver().removeOnPreDrawListener(this);
mSlideRange = mCaptureView.getMeasuredWidth();
return false;
}
});
}
private class DragHelperCall extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == mCaptureView;
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
mSlideOffset = left * 1.0f / mSlideRange*2;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return clamp(left, 0, mSlideRange / 2);
}
@Override
public int getViewHorizontalDragRange(View child) {
return mSlideRange/2;
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
int finalLeft;
if (xvel > 0 || xvel == 0 && mSlideOffset > .5f) {
finalLeft = mSlideRange/2 ;
}else {
finalLeft = 0 ;
}
mViewDragHelper.settleCapturedViewAt( finalLeft, mCaptureView.getTop());
invalidate();
}
}
@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)){
ViewCompat.postInvalidateOnAnimation(this);
}
}
private int clamp(int value, int min, int max) {
return Math.min(max, Math.max(min, value));
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mViewDragHelper.cancel();
return false;
}
if (!isEnabled() || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
mViewDragHelper.cancel();
return super.onInterceptTouchEvent(ev);
}
int index = MotionEventCompat.getActionIndex(ev) ;
switch (action) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
mInitialMotionX = x;
mInitialMotionY = y;
mIsUnableToDrag = false;
break;
}
case MotionEvent.ACTION_MOVE: {
final float x = ev.getX();
final float y = ev.getY();
final float adx = Math.abs(x - mInitialMotionX);
final float ady = Math.abs(y - mInitialMotionY);
int slop = mViewDragHelper.getTouchSlop();
if (adx > slop && adx < ady) {
mIsUnableToDrag = true;
mViewDragHelper.cancel();
return false;
}
break;
}
}
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true;
}
}
可以看到这里我们只是处理了事件拦截的操作,因为这里涉及到了ScrollView,如果没有涉及到事件拦截的话,代码量更简单!而最最复杂的onTouchEvent方法,我们什么都没做,只是让他交给ViewDragHelper类去处理。
如果看了DrawerLayout和SlidingPaneLayout源码的朋友应该知道,这两个控件就使用了这个帮助类。
看了ViewDragHelper的使用效果,我们再来看看它的用法,
/**
* ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number
* of useful operations and state tracking for allowing a user to drag and reposition
* views within their parent ViewGroup.
*/
上面那段话是ViewDragHelper类的一个说明。大致意思实说ViewDragHelper是自定义ViewGroup的一个工具类,在我们对子View拖拽或者复位的时候它提供了一系列有用的操作。
从这段话中我们可以知道一个信息,这个类大部分用于自定义ViewGroup中,当然像上面的例子,重写RelativeLayout其实也相当于自定义ViewGroup。
实例化ViewDragHelper,ViewDragHelper的构造方法私有化了,所以我们不能直接new,需要通过
public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {
//...
return helper;
}
或
public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
return new ViewDragHelper(forParent.getContext(), forParent, cb);
}
来实例化,这列有三个参数,分别代表什么意思呢?
ViewGroup forParent 就是我们自定义的ViewGroup
float sensitivity 是一个拖拽的灵敏度
Callback cb 是ViewDragHelper中定义的一个抽象类,需要我们在自定义的ViewGroup中重写它,而核心的操作也就在在各类中,了解了这个类中的方法,就基本掌握这个Helper的运用。
public static abstract class Callback {
//但拖拽状态改变的时候会触发这个方法,比如:从一开始不能拖拽,到拖拽
public void onViewDragStateChanged(int state) {}
//关键方法:顾名思义,这里就是记录了一些值得变化,可用于我们处理其他的操作,比如,改变背景颜色
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {}
//这个方法很少用到,意义不大,可忽略
public void onViewCaptured(View capturedChild, int activePointerId) {}
//关键方法,手指松开会触发这个方法,做复位操作就在此方法中实现
public void onViewReleased(View releasedChild, float xvel, float yvel) {}
//当边缘被触摸的时候调用
public void onEdgeTouched(int edgeFlags, int pointerId) {}
//边缘设置不可用,
public boolean onEdgeLock(int edgeFlags) {
return false;
}
//这个方法也很少用到
public void onEdgeDragStarted(int edgeFlags, int pointerId) {}
//给自定义的ViewGroup中的字View 从新排序
public int getOrderedChildIndex(int index) {
return index;
}
//关键方法:设置水平拖动的距离
public int getViewHorizontalDragRange(View child) {
return 0;
}
//关键方法:设置垂直拖动的距离 0 表示不可拖动
public int getViewVerticalDragRange(View child) {
return 0;
}
//关键方法:返回true表示可以拖动
public abstract boolean tryCaptureView(View child, int pointerId);
//关键方法:重新定位水平移动的位置,返回left表示不受限制
public int clampViewPositionHorizontal(View child, int left, int dx) {
return 0;
}
//关键方法:重新定位垂直移动的位置,返回top表示不受限制
public int clampViewPositionVertical(View child, int top, int dy) {
return 0;
}
}
看了以上注释,再回过头来看上面的例子是不是觉得很简单。
总结:
1、ViewDragHelper的作用是一个简化View拖动的帮助类
2、ViewDragHelper大部分用在自定义ViewGroup中
3、ViewDragHelper的实例化通过
create(ViewGroup forParent, float sensitivity, Callback cb)
方法创建
4、在自定义的ViewGroup中的onInterceptTouchEvent方法中别忘记调用ViewDragHelper中的shouldInterceptTouchEvent(ev),
同理onTouchEvent(MotionEvent event)中需要调用
ViewDragHelper.processTouchEvent(event);