ViewDragHelper 详解(一)
一、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是一个实用的类,用来开发自定义ViewGroup。它定义了一组有用的操作和状态追踪,允许用户在父ViewGroup中拖动并且重新定位子View(child view)。
从官方的介绍中我们了解到了几个关键点:
- 自定义ViewGroup
- 拖动以及重新定位
- 操作和状态追踪
二、ViewHelper的看的见的地方有哪些?
当我们看到一个未知的东西,我们的心里总是充满疑惑,想要探究一下可以看得见和摸得着的感知。 在我的系列文章中也将会给大家奉献看得见的GIF,将相关函数和具体表现形象的展示出来。
- DrawerLayout
- QQ5.0以上的侧滑效果
三、 从实例来讲解ViewDragHelper
我们将会看到常用函数的用法
步骤一: 创建我们自定义的LinearLayout
public class VDHLinearLayout extends LinearLayout {
private ViewDragHelper mViewDragHelper = null;
public VDHLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
createVDH(null);
}
public void createVDH(ViewDragHelper.Callback callback) {
if (callback == null) {
mViewDragHelper = ViewDragHelper.create(this, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
});
} else {
mViewDragHelper = ViewDragHelper.create(this, 1.0f, callback);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mViewDragHelper == null) {
return super.dispatchTouchEvent(event);
}
mViewDragHelper.processTouchEvent(event);
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mViewDragHelper == null) {
return super.onInterceptTouchEvent(ev);
}
int keyCode = ev.getAction();
if (keyCode == MotionEvent.ACTION_CANCEL) {
mViewDragHelper.cancel();
return false;
}
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
public ViewDragHelper getViewDragHelper() {
return mViewDragHelper;
}
}
步骤二:创建布局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<com.demonli.vdh.view.VDHLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context="com.demonli.vdh.MainActivity"
android:id="@+id/root_vdh"
android:paddingTop="10dp"
android:paddingBottom="30dp"
android:paddingLeft="15dp"
android:paddingRight="20dp"
>
<TextView
android:id="@+id/child1"
android:layout_margin="10dp"
android:gravity="center"
android:layout_gravity="center"
android:background="@color/colorPrimary"
android:text="CommonDragView"
android:layout_width="100dp"
android:layout_height="100dp"/>
<TextView
android:id="@+id/child2"
android:layout_margin="10dp"
android:layout_gravity="center"
android:gravity="center"
android:background="@color/colorAccent"
android:text="AutobackView"
android:layout_width="100dp"
android:layout_height="100dp"/>
<TextView
android:id="@+id/child3"
android:layout_margin="10dp"
android:layout_gravity="center"
android:gravity="center"
android:background="@android:color/holo_red_dark"
android:text="EdgeTrackerView"
android:layout_width="100dp"
android:layout_height="100dp"/>
</com.demonli.vdh.view.VDHLinearLayout>
步骤三:创建MainActivity
public class MainActivity extends AppCompatActivity {
private VDHLinearLayout mVDHLiearLayout;
private TextView mDragView;
private TextView mAutobackView;
private TextView mEdgeView;
Point mAutobackPoint = new Point();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDragView = (TextView) findViewById(R.id.child1);
mAutobackView = (TextView) findViewById(R.id.child2);
mEdgeView = (TextView) findViewById(R.id.child3);
mVDHLiearLayout = (VDHLinearLayout) findViewById(R.id.root_vdh);
init();
}
protected void init() {
mVDHLiearLayout.createVDH(new VDHCallback());
mAutobackView.postDelayed(new Runnable() {
@Override
public void run() {
mAutobackPoint.set(mAutobackView.getLeft(), mAutobackView.getTop());
}
}, 0);
}
class VDHCallback extends ViewDragHelper.Callback {
/**
* 此例中 mEdgeView将无法被拖动
* 此函数必须实现
*
* @param child
* @param pointerId
* @return if false,那么child view将无法拖动(Drag)
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
if (child == mEdgeView) {//
return false;
}
return true;
}
/**
* 实现对child view x 轴方向的控制
*
* @param child
* @param left
* @param dx
* @return
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
int leftBound = mVDHLiearLayout.getPaddingLeft();
int rightBound = mVDHLiearLayout.getWidth() - mVDHLiearLayout.getPaddingRight() - child.getWidth();
// if(left<leftBound){
// left = leftBound;
// }else if(left>rightBound){
// left = rightBound;
// }
//更加简洁的写法
left = Math.min(Math.max(left, leftBound), rightBound);
return left;
}
/**
* 对child view y 轴方向的控制
*
* @param child
* @param top
* @param dy
* @return
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
int topBound = mVDHLiearLayout.getPaddingTop();
int bottomBound = mVDHLiearLayout.getHeight() - child.getHeight() - mVDHLiearLayout.getPaddingBottom();
if (top < topBound) {
top = topBound;
} else if (top > bottomBound) {
top = bottomBound;
}
return top;
}
}
}
经过步骤一、二、三之后我们可以看到以下的效果:
这里写图片描述
我们会发现:
1、mDragView和mAutobackView可以Drag,而mEdgeView没有移动。
2、同时我们会主要到mDragView和mAutobackView无法到达屏幕的边界。
1、tryCaptureView( ) :试图捕获child view的drag 事件,如果指定子view 返回false,则会出现像mEdgeView那样无法Drag的情况。
2、布局文件activity_main.xml中我们定义根布局中的paddingLeft等,并且在MainActivity中实现了clampViewPositionVertical()与clampViewPositionHorizontal()两个函数。函数的具体意义见代码中的注释。
步骤四:实现mAutobackView的拖动后自动返回到原来的位置:
在VDHLinearLayout中添加:
@Override
public void computeScroll() {
super.computeScroll();
if(mViewDragHelper.continueSettling(true)){
invalidate();
}
}
在MainActivty中添加:
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if(releasedChild==mAutobackView){
mVDHLiearLayout.getViewDragHelper().settleCapturedViewAt(mAutobackPoint.x,mAutobackPoint.y);
mVDHLiearLayout.invalidate();
}
}
接下来我们会看到这样的效果:
这里写图片描述
我们可以看出来mAutobackView自动的回到原来的位置。
1、首先我们保存了mAutobackView的初始坐标
mAutobackView.postDelayed(new Runnable() {
@Override
public void run() {
mAutobackPoint.set(mAutobackView.getLeft(), mAutobackView.getTop());
}
}, 0);
然后我们在VDHCallback实现了:
/**
* 此函数用于捕捉用户的ACTION_UP
* @param releasedChild
* @param xvel
* @param yvel
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if(releasedChild==mAutobackView){
mVDHLiearLayout.getViewDragHelper().settleCapturedViewAt(mAutobackPoint.x,mAutobackPoint.y);
mVDHLiearLayout.invalidate();
}
}
步骤五:实现mEdgeView从屏幕边界滑动时的移动效果
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
// super.onEdgeDragStarted(edgeFlags, pointerId);
mVDHView.getViewDragHelper().captureChildView(mEageView, pointerId);
}
mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);
这里写图片描述
这样我们的mEdgeView便可拖动了。
对于ViewDragHelper.Callback中没有讲到的方法如下:
onViewDragStateChanged()
当ViewDragHelper状态发生变化时回调(IDLE,DRAGGING,SETTING[自动滚动时])
onViewPositionChanged
当captureview的位置发生改变时回调
onViewCaptured()
当captureview被捕获时回调
onEdgeTouched()
当触摸到边界时回调。
onEdgeLock()
true的时候会锁住当前的边界,false则unLock。
getOrderedChildIndex()
改变同一个坐标(x,y)去寻找captureView位置的方法。(具体在:findTopChildUnder方法中)