前言
以前我们想实现一个可拖动的悬浮窗,一般来说需要在ViewGroup的onTouch事件里面处理各种DOWN、MOVE、UP事件,写的很恶心,而且让代码也很难维护,好在现在有一个神器ViewDragHelper来帮我们处理这些情况,感谢Google~~~
ViewDragHelper的基础使用
ViewDragHelper的基本使用如下。首先需要覆写ViewGroup的onInterceptTouchEvent以及onTouchEvent,然后将其交给ViewDragHelper进行处理,然后在ViewDragHelper.Callback中进行各种回调处理。一般来说,需要覆写tryCaptureView,用来告诉ViewDragHelper哪一个View可以被拖动。
public class ViewDragHelperDemo extends LinearLayout {
private ViewDragHelper mViewDragHelper;
private ViewDragHelper.Callback mDragCallback;
private void init() {
initDragCallBack();
mViewDragHelper = ViewDragHelper.create(this, 1.0f, mDragCallback);
// 设置为可以捕获屏幕左边的滑动
mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
}
private void initDragCallBack() {
mDragCallback = new ViewDragHelper.Callback() {
/**
* 返回true,表示传入的View可以被拖动
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
// 只允许第一个被拖动
return child.equals(getChildAt(0));
}
/**
* 传入View即将到达的位置(left),返回值为真正到达的位置
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
/**
* 传入View即将到达的位置(top),返回值为真正到达的位置
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
};
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//让ViewDragHelper决定是否拦截事件
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//表示处理Touch事件
mViewDragHelper.processTouchEvent(event);
return true;
}
}
拖动效果可以查看Demo.apk。
限制拖动范围
上面的代码能让ViewGroup中第一个子View任意拖动,如果我们向让其左右不能出边界,上下可以出边界,就需要改写下clampViewPositionHorizontal、clampViewPositionVertical方法,这两个方法主要是用来修正可被拖动的View即将到达的位置,通过修改这两个函数的返回值,即可限制拖动范围。
/**
* 传入View即将到达的位置(left),返回值为真正到达的位置
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
LinearLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
int leftBorder = getPaddingLeft() + lp.leftMargin;
int rightBorder = getMeasuredWidth() - getPaddingRight() - child.getMeasuredWidth() - lp.rightMargin;
return Math.min(Math.max(leftBorder, left), rightBorder);
}
/**
* 传入View即将到达的位置(top),返回值为真正到达的位置
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
加入自动回到起点
首先我们需要知道什么时候用户停止了拖动,这一事件可以在ViewDragHelper.Callback的onViewReleased方法中知道,然后我们ViewDragHelper.settleCapturedViewAt设置View要回到的位置,那么View就会以当前拖动的速度,自动回到指定地点。并且惯性滑动ViewDragHelper使用的ScrollerCompat实现,所以我们也得覆写computeScroll方法才行,大致逻辑如下(省略部分代码)
public class ViewDragHelperDemo extends LinearLayout {
private ViewDragHelper mViewDragHelper;
private ViewDragHelper.Callback mDragCallback;
private void init() {
initDragCallBack();
mViewDragHelper = ViewDragHelper.create(this, 1.0f, mDragCallback);
// 设置为可以捕获屏幕左边的滑动
mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
}
private void initDragCallBack() {
mDragCallback = new ViewDragHelper.Callback() {
/**
* 当手指离开以后的回调
*
* @param releasedChild 子View
* @param xvel X轴的速度
* @param yvel Y轴的速度
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (releasedChild.equals(getChildAt(0))) {
Log.i(TAG, xvel = + xvel + ,yvel= + yvel);
// 手指松开以后自动回到原始位置
mViewDragHelper.settleCapturedViewAt(100, 100);
invalidate();
}
}
};
}
@Override
public void computeScroll() {
if (mViewDragHelper != null && mViewDragHelper.continueSettling(true)) {
invalidate();
}
}
}
解决点击冲突
如果你将可移动的View添加一个点击事件,如果不进行特殊的处理,你会发现点击事件是无效的,所以我们需要如下覆写部分方法
mDragCallback = new ViewDragHelper.Callback() {
/**
* 返回横向能拖动的长度,默认返回0,如果被拖动的View设置了点击事件,返回0会不响应点击事件
*/
@Override
public int getViewHorizontalDragRange(View child) {
return getMeasuredWidth() - child.getMeasuredWidth();
}
/**
* 返回纵向能拖动的长度,默认返回0,如果被拖动的View设置了点击事件,返回0会不响应点击事件
*/
@Override
public int getViewVerticalDragRange(View child) {
return getMeasuredHeight() - child.getMeasuredHeight();
}
};
添加屏幕边缘滑动监听
如果我们的View在屏幕中间,我们想让用户滑动屏幕的某一个边缘也行将其滑动,那么就需要添加屏幕边缘滑动监听。
mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
上面的代码就是当用户在屏幕左边滑动的时候,ViewDragHelper会自动回调ViewDragHelper.Callback的onEdgeDragStarted方法,然后我们就可以进行移动处理了。
private void init() {
initDragCallBack();
mViewDragHelper = ViewDragHelper.create(this, 1.0f, mDragCallback);
// 设置为可以捕获屏幕左边的滑动
mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
}
private void initDragCallBack() {
mDragCallback = new ViewDragHelper.Callback() {
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
Log.i(TAG, onEdgeDragStarted; + edgeFlags);
// 当从屏幕左边开始滑动的时候,开始滑动第一个子控件
mViewDragHelper.captureChildView(getChildAt(0), pointerId);
}
};
}
移动联合动画
如果需要在移动某一个View的时候,另一个view也随之进行某种移动,那么仅仅需要在View移动的时候进行监听,然后同步移动另一个即可,至于如何监听View的移动,ViewDragHelper.CallBack中也有回调。
mDragCallback = new ViewDragHelper.Callback() {
/**
* 当某一个View在动的时候的回调,不管是用户手动滑动,还是使用settleCapturedViewAt或者smoothSlideViewTo,都会回调这里
*/
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
Log.i(TAG, left= + left + ,top= + top + ,dx= + dx + ,dy= + dy);
}
};
其他
其实查看ViewDragHelper的源码可以发现,ViewDragHelper移动View是使用的View自带的offsetLeftAndRight以及offsetTopAndBottom,惯性移动使用的ScrollerCompat,主要是将手指的复杂处理给封装起来,便于开发者调用。顺带贴出能让View进行位移的一些方法。
View.setLeft/Top/Right/Bottom
View.layout(l,t,r,b);
View.setTranslationX/Y
参考链接:希尔瓦娜斯女神
Demo地址:Github
挖槽, 沙发了.
@夕凉 奖励蒙古海景房一套,海自己挖