ViewDragHelper作为官方推出的手势滑动辅助工具,极大的简化了我们对手势滑动的处理逻辑,v4包中的SlidingPaneLayout和DrawerLayout内部都有ViewDragHelper的身影,这里对这个强大的辅助工具类使用以及相关方法做个系统性的总结。
全文思路:
一、用ViewDragHelper实现一个简单的效果,对其有个初步的认识
用个在项目中实现的简单效果来看下吧:
这个实现思路也很简单,我们看下代码:
public class MyDragViewLayout extends ViewGroup{
public ViewDragHelper mViewDragHelper;
private boolean isOpen = true;
private View mMenuView;
private View mContentView;
private int mCurrentTop = 0;
public MyDragViewLayout(Context context) {
super(context);
init();
}
public MyDragViewLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyDragViewLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//ViewDragHelper静态方法传入ViewDragHelperCallBack创建
mViewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelperCallBack());
// mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_TOP);
}
//实现ViewDragHelper.Callback相关方法
private class ViewDragHelperCallBack extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
//返回ture则表示可以捕获该view
return child == mContentView;
}
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
//setEdgeTrackingEnabled设置的边界滑动时触发
//通过captureChildView对其进行捕获,该方法可以绕过tryCaptureView
//mViewDragHelper.captureChildView(mContentView, pointerId);
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
//手指触摸移动时回调, left表示要到的x坐标
return super.clampViewPositionHorizontal(child, left, dx);//
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
//手指触摸移动时回调 top表示要到达的y坐标
return Math.max(Math.min(top, mMenuView.getHeight()), 0);
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
//手指抬起释放时回调
int finalTop = mMenuView.getHeight();
if(yvel <= 0){
if(releasedChild.getTop()< mMenuView.getHeight()/2){
finalTop = 0;
}else{
finalTop = mMenuView.getHeight();
}
}else{
if(releasedChild.getTop() > mMenuView.getHeight()/2){
finalTop = mMenuView.getHeight();
}else{
finalTop = 0;
}
}
mViewDragHelper.settleCapturedViewAt(releasedChild.getLeft(), finalTop);
invalidate();
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
//mDrawerView完全覆盖屏幕则防止过度绘制
mMenuView.setVisibility((changedView.getHeight() - top == getHeight()) ? View.GONE : View.VISIBLE);
mCurrentTop +=dy;
requestLayout();
}
@Override
public int getViewVerticalDragRange(View child) {
if (mMenuView == null) return 0;
return (mContentView == child) ? mMenuView.getHeight() : 0;
}
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
if (state == ViewDragHelper.STATE_IDLE) {
isOpen = (mContentView.getTop() == mMenuView.getHeight());
}
}
}
@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)) {
invalidate();
}
}
public boolean isDrawerOpened() {
return isOpen;
}
//onInterceptTouchEvent方法调用ViewDragHelper.shouldInterceptTouchEvent
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
//onTouchEvent方法中调用ViewDragHelper.processTouchEvent方法并返回true
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(measureWidth, measureHeight);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMenuView = getChildAt(0);
mContentView = getChildAt(1);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mMenuView.layout(0, 0,
mMenuView.getMeasuredWidth(),
mMenuView.getMeasuredHeight());
mContentView.layout(0, mCurrentTop + mMenuView.getHeight(),
mContentView.getMeasuredWidth(),
mCurrentTop + mContentView.getMeasuredHeight() + mMenuView.getHeight());
}
}
xml:
<?xml version="1.0" encoding="utf-8"?>
<com.mrzk.myapplication.MyDragViewLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="?attr/colorAccent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Menu"
android:layout_centerInParent="true"
android:textSize="22sp"
android:textColor="#fff"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorPrimary">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Content"
android:textSize="22sp"
android:textColor="#fff"/>
</RelativeLayout>
</com.mrzk.myapplication.MyDragViewLayout>
我们缕一下思路,
第一步:在init方法中用ViewDragHelper的静态方法实例化ViewDragHelper对象,其中第一个参数指的当前的ViewGroup,第二个sensitivity参数指的是对滑动检测的敏感度,越大越敏感,默认传1即可。第三个参数为静态回调对象CallBack,我们实现相关CallBack方法来操作拖拽的View。
第二步:实现ViewDragHelper.Callback的相关方法。
第三步:在onInterceptTouchEvent方法中调用mViewDragHelper.shouldInterceptTouchEvent(ev)将事件传给ViewDragHelper。
第四步:在onTouchEvent方法中调用ViewDragHelper.processTouchEvent方法并返回true。
二、对ViewDragHelper相关API进行归纳分析
1、ViewDragHelper
在ViewDragHelper的滑动中共有三个方法可以调用,smoothSlideViewTo、settleCapturedViewAt、flingCapturedView,动画移动会回调continueSettling(boolean)方法,在内部是用的ScrollerCompat来实现滑动的。
在computeScroll方法中判断continueSettling(boolean)的返回值,来动态刷新界面:
@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)) {
invalidate();
}
}
2、ViewDragHelper.CallBack