ViewDragHelper
使用ViewDragHelper可以轻松的实现拖动效果
简单Demo
XML中定义
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--这里考虑了Padding的情况-->
<com.jacob.viewdraghelper.tianrui.Practice1View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp">
<TextView
android:layout_width="60dp"
android:layout_height="60dp"
android:background="#00ff00"/>
</com.jacob.viewdraghelper.tianrui.Practice1View>
</RelativeLayout>
ViewDragHelper的编写
在onInterceptTouchEvent()和onTouchEvent()中使用ViewDragHelper接受事件
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// 取消拖动
mViewDragHelper.cancel();
break;
}
// 由ViewDragHelper决定是否拦截事件
return mViewDragHelper.shouldInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true;
}
这些都是模板代码,要使用ViewDragHelper的话必须这么做.
编写CallBack代码
ViewDragHelper将MotionEvent帮我们处理成CallBack,我们只需处理CallBack中的逻辑就可以了,不用编写重复的拖动代码了.
最基本的有三个回调方法,使用这三个回调方法,能够处理大多数拖拽逻辑了
/**
* ViewDragHelper必备的回调方法
*/
private class ViewDragCallBack extends ViewDragHelper.Callback {
/**
* 判断当前需要拖动哪个View
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
Log.d(TAG, "tryCaptureView: " + child.getClass().getSimpleName());
return child == mTvTarget;
}
/**
* 在水平方向上,对拖动的位置进行限制
*
* @param child 进行拖动的View
* @param left 水平方向上的移动距离
* @param dx 水平方向上的移动增量
* @return 实际需要移动的值
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
final int leftBound = getLeft() + getPaddingLeft();
if (left < leftBound) {
return leftBound;
}
final int rightBound = getRight() - mTvTarget.getWidth() - getPaddingRight();
if (left > rightBound) {
return rightBound;
}
return left;
}
/**
* 处理竖直方向上的拖动
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
final int topBound = getTop() + getPaddingTop();
if (top < topBound) {
return topBound;
}
final int bottomBound = getBottom() - mTvTarget.getHeight() - getPaddingBottom();
if (top > bottomBound) {
return bottomBound;
}
return top;
}
}
注意事项:
- 如果第一子View不是TextView而是一个Button,那么就会出现无法拖动的情况,原因就是子View默认属性带有Clickable,会消耗掉这次事件序列,所以必须在OnInterceptTouchEvent()中进行拦截,使用ViewDragHelper中已经带了默认方法帮我们进行处理
/**
* 该方法用来描述View需要被拖动的距离,用此来适配子View具有Clickable属性的情况
*
* @param child 要拖动的View
* @return View在该方向上拖动的距离
*/
@Override
public int getViewHorizontalDragRange(View child) {
Log.d(TAG, "getViewHorizontalDragRange: ");
// 这里返回整个水平区域
return getMeasuredWidth() - child.getMeasuredWidth();
}
@Override
public int getViewVerticalDragRange(View child) {
return getMeasuredHeight() - child.getMeasuredHeight();
}
Demo1完整代码
/**
* Created by yangtianrui on 17-8-3.
* ViewDragHelper的基本使用
*/
public class Practice1View extends LinearLayout {
private static final String TAG = "tianrui";
// 需要拖动的目标View
private TextView mTvTarget;
private ViewDragHelper mViewDragHelper;
public Practice1View(Context context) {
this(context, null);
}
public Practice1View(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public Practice1View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mViewDragHelper = ViewDragHelper.create(this, 1F, new ViewDragCallBack());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
Log.d(TAG, "onFinishInflate: ");
mTvTarget = (TextView) getChildAt(0);
if (mTvTarget == null) {
throw new IllegalStateException("first child must be TextView.");
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// 取消拖动
mViewDragHelper.cancel();
break;
}
// 由ViewDragHelper决定是否拦截事件
return mViewDragHelper.shouldInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true;
}
/**
* ViewDragHelper必备的回调方法
*/
private class ViewDragCallBack extends ViewDragHelper.Callback {
/**
* 判断当前需要拖动哪个View
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
Log.d(TAG, "tryCaptureView: " + child.getClass().getSimpleName());
return child == mTvTarget;
}
/**
* 在水平方向上,对拖动的位置进行限制
*
* @param child 进行拖动的View
* @param left 水平方向上的移动距离
* @param dx 水平方向上的移动增量
* @return 实际需要移动的值
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
final int leftBound = getLeft() + getPaddingLeft();
if (left < leftBound) {
return leftBound;
}
final int rightBound = getRight() - mTvTarget.getWidth() - getPaddingRight();
if (left > rightBound) {
return rightBound;
}
return left;
}
/**
* 处理竖直方向上的拖动
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
final int topBound = getTop() + getPaddingTop();
if (top < topBound) {
return topBound;
}
final int bottomBound = getBottom() - mTvTarget.getHeight() - getPaddingBottom();
if (top > bottomBound) {
return bottomBound;
}
return top;
}
/**
* 该方法用来描述View需要被拖动的距离,用此来适配子View具有Clickable属性的情况
*
* @param child 要拖动的View
* @return View在该方向上拖动的距离
*/
@Override
public int getViewHorizontalDragRange(View child) {
Log.d(TAG, "getViewHorizontalDragRange: ");
// 这里返回整个水平区域
return getMeasuredWidth() - child.getMeasuredWidth();
}
@Override
public int getViewVerticalDragRange(View child) {
return getMeasuredHeight() - child.getMeasuredHeight();
}
}
@Override
public void computeScroll() {
super.computeScroll();
// 如果需要ViewDragHelper进行惯性滑动的话需要使用
// 下面代码仍然是模板代码
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
}
Demo2 使用ViewDragHelper实现惯性滑动
ViewDragHelper内部使用Scroller实现滑动,所以需要重写View的ComputeScroll()方法,
该方法的重写方式如下:
@Override
public void computeScroll() {
super.computeScroll();
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
在onViewRelease()中实现惯性滑动逻辑
使用 mViewDragHelper.settleCapturedViewAt( left, top)将一个View进行惯性滑动,同时记得调用invalidate()进行重绘
/**
* 手指抬起时回调,一般用于实现惯性滑动
*
* @param releasedChild 拖动的View
* @param xvel x方向的速度
* @param yvel y方向的速度
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
// 定位到top/middle/bottom三个位置
final int top = releasedChild.getTop();
Log.d(TAG, "onViewReleased: " + String.format("mTop=%d, mMiddle=%d, mBottom=%d,yVel=%f, height=%d", mTop, mMiddle, mBottom, yvel, releasedChild.getHeight()));
// 向下滑动
if (yvel > 0) {
if (top < mMiddle) {
mViewDragHelper.settleCapturedViewAt(0, mMiddle);
} else if (top < mBottom) {
mViewDragHelper.settleCapturedViewAt(0, mBottom);
}
} else if (yvel < 0) {
// 向上滑动
if (top < mMiddle) {
mViewDragHelper.settleCapturedViewAt(0, mTop);
} else if (top < mBottom) {
mViewDragHelper.settleCapturedViewAt(0, mMiddle);
}
}
invalidate();
}
}
Demo2完整代码
/**
* Created by yangtianrui on 17-8-5.
* ViewDragHelper实现惯性滑动
*/
public class Practice2View extends LinearLayout {
private static final String TAG = "tianrui";
private ViewDragHelper mViewDragHelper;
private View mTarget;
private int mTop;
private int mMiddle;
private int mBottom;
public Practice2View(Context context) {
this(context, null);
}
public Practice2View(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public Practice2View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mViewDragHelper = ViewDragHelper.create(this, 1F, new ViewDragHelperCallBack());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTarget = getChildAt(0);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mTop = getTop();
mMiddle = (getBottom() - getTop()) / 2 - mTarget.getMeasuredHeight();
mBottom = getBottom() - mTarget.getMeasuredHeight();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mViewDragHelper.cancel();
break;
}
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true;
}
@Override
public void computeScroll() {
super.computeScroll();
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
private class ViewDragHelperCallBack extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == mTarget;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
if (top < getTop()) {
return getTop();
}
if (top > (getBottom() - child.getHeight())) {
return getBottom() - child.getHeight();
}
return top;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if (left < getLeft()) {
return getLeft();
}
if (left > (getRight() - child.getWidth())) {
return getRight() - child.getWidth();
}
return left;
}
/**
* 手指抬起时回调,一般用于实现惯性滑动
*
* @param releasedChild 拖动的View
* @param xvel x方向的速度
* @param yvel y方向的速度
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
// 定位到top/middle/bottom三个位置
final int top = releasedChild.getTop();
Log.d(TAG, "onViewReleased: " + String.format("mTop=%d, mMiddle=%d, mBottom=%d,yVel=%f, height=%d", mTop, mMiddle, mBottom, yvel, releasedChild.getHeight()));
// 向下滑动
if (yvel > 0) {
if (top < mMiddle) {
mViewDragHelper.settleCapturedViewAt(0, mMiddle);
} else if (top < mBottom) {
mViewDragHelper.settleCapturedViewAt(0, mBottom);
}
} else if (yvel < 0) {
// 向上滑动
if (top < mMiddle) {
mViewDragHelper.settleCapturedViewAt(0, mTop);
} else if (top < mBottom) {
mViewDragHelper.settleCapturedViewAt(0, mMiddle);
}
}
invalidate();
}
}
}
参考http://blog.csdn.net/lmj623565791/article/details/46858663