ViewDragHelper 的学习一
ViewDragHelper是一个在自定义viewgroup的时候比较神奇的一个类,可以帮我们的实现拖拽等的一些功能,比如实现拖动的效果等。在拖过的时候我们可以限制其可拖拽的范围等一些参数。通过学习这个我们可以自己手动实现一个简单的drawerlayout。
ViewDragHelper的介绍
SlidingPaneLayout和DrawerLayout,现在这俩个类被广泛的运用,其实研究他们的源码你会发现这两个类都运用了ViewDragHelper来处理拖动。是属于在v4包中的一个类。
ViewDragHelper的常见的用法
1 创建,初始化ViewDragHelper和ViewDragHelper.Callback,其中的ViewDragHelper是用来帮助拖拽的类,而ViewDragHelper.Callback的用来联系ViewDragHelper与viewgroup之间的桥梁,主要ViewDragHelper.Callback的监听方法实现我们需要的一系列操作。
//初始化ViewDragHelper
ViewDragHelper create(ViewGroup forParent, Callback cb);
//初始化Callback
private Callback mCallback = new Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return mView1 == child || mView2 == child;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (releasedChild == mView2) {
mHelper.settleCapturedViewAt(mView2Point.x, mView2Point.y);
invalidate();
}
if (releasedChild == mView1) {
mHelper.flingCapturedView(0, 0, 10000, 10000);
invalidate();
}
}
};
2 在viewGroup()中onTouchEvent();return true,为了能够消费事件
public boolean onTouchEvent(MotionEvent event) {
mHelper.processTouchEvent(event);
return true;
}
最后贴上整个完整的源码
public class VDHViewGroup extends RelativeLayout {
private ViewDragHelper mHelper;
private Point mView2Point;
private View mView1;
private View mView2;
private Callback mCallback = new Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return mView1 == child || mView2 == child;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (releasedChild == mView2) {
mHelper.settleCapturedViewAt(mView2Point.x, mView2Point.y);
invalidate();
}
if (releasedChild == mView1) {
mHelper.flingCapturedView(0, 0, 10000, 10000);
invalidate();
}
}
};
public VDHViewGroup(Context context) {
this(context, null);
}
public VDHViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mHelper = ViewDragHelper.create(this, mCallback);
mView2Point = new Point();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mHelper.processTouchEvent(event);
return true;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mView2Point.x = mView2.getLeft();
mView2Point.y = mView2.getTop();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mView1 = getChildAt(0);
mView2 = getChildAt(1);
}
//这里用来处理手离开的CaptureView的view回调的,fling的手势和回弹的模式,因为viewdraghelper内部时借助Scroller实现的。
@Override
public void computeScroll() {
if (mHelper.continueSettling(true)) {
invalidate();
}
}
}
这里第一view是可以处理fling的手势,第二个view的可以的回到原来的位置的。
其中大象的是具有滑动的惯性的view1,而小狗是手指离开的回返回到原来的位置。
3,viewgroup中有消费的事件的view (clickable = true 或者longclickable = true)关于消费事件可以去看下我以前的博客ViewGroup和View的事件传递原理,我们都是知道事件传递是会传递子view去消费的,这个时候viewgroup的是的onTouchEvent是不会触发的,这个时候我们就需要拦截了,可以写成的下面的这样子的
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}
同样也就可以出现的上面效果但是呢?我们这样真的好吗?这个意味着我们子view的没有办法消费事件,比如点击事件,长按事件的。对吧,但是ViewDragHelper的帮我实现这样的功能,我们需要在
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mHelper.shouldInterceptTouchEvent(ev);
}
但是这样是不够的,我们还需要Callback中的重新重写下面的方法
@Override
public int getViewHorizontalDragRange(View child) {
//写个大于0的值
return 1;
}
@Override
public int getViewVerticalDragRange(View child) {
//写个大于0的值
return 1;
}
这样就可以又可以实现了刚上来的效果,并且能够让子view实现消费事件
常见api的说明
ViewDragHelper
//创建ViewDragHelper的对象
ViewDragHelper create(ViewGroup forParent, Callback cb)
//ViewDragHelper处理onTouchEvent的,注意没有返回值的
public void processTouchEvent(MotionEvent ev)
//判断是否需要拦截孩子的事件的
public boolean shouldInterceptTouchEvent(MotionEvent ev)
//判断滑动是不是还在继续,和Scroller的public boolean computeScrollOffset() 相似,其实内部也是这么实现的。
public boolean continueSettling(boolean deferCallbacks);
//将CaptureView的Scroll到制定的位置
public boolean settleCapturedViewAt(int finalLeft, int finalTop)
//将CaptureView的fling到制定的位置,前提是CaptureView能够fling的
public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop)
//设置边缘触发,边缘出发方向
//EDGE_LEFT,EDGE_RIGHT,EDGE_TOP,EDGE_BOTTOM
public void setEdgeTrackingEnabled(int edgeFlags)
ViewDragHelper.Callback
1
//判断viewgroup中那些子view可以被拖拽
public boolean tryCaptureView(View child, int pointerId)
2
public int clampViewPositionHorizontal(View child, int left, int dx)
计算的实现的拖债过程的子view的left,有点难理解,我先说下参数吧,第一个child位拖拽的view,第二个left,是child拖拽前的位置left+move的事件的distancex,也就是实际手指拖拽的距离加之前的left的,第三个dx也就是distancex;所以这个方法为了处理一些边缘条件,比如我们需要让子view不能画出屏幕外(左右),就可以加上下面的条件
public int clampViewPositionHorizontal(View child, int left, int dx) {
if (left < getLeft()) {
left = getLeft();
}
if (left > getRight() - (child.getRight() - child.getLeft())) {
left = getRight() - (child.getRight() - child.getLeft());
}
return left;
}
3
public int clampViewPositionVertical(View child, int top, int dy)
和上面的同理,如果想限制上下的屏幕的可加以下的代码
public int clampViewPositionVertical(View child, int top, int dy) {
if (top < getTop()) {
top = getTop();
}
if (top > getBottom() - (child.getBottom() - child.getTop())) {
top = getBottom();
}
return top;
}
4
//判断水平方向能否拖拽,
public int getViewHorizontalDragRange(View child)
5
//判断垂直方向能否拖拽,
public int getViewVerticalDragRange(View child)
6
//手指离开拖拽的view的回调
public void onViewReleased(View releasedChild, float xvel, float yvel)
7
//手指刚接触的拖拽的view的回调
public void onViewCaptured(View capturedChild, int activePointerId)
8
//拖拽的状态发生改变的回调,三种状态
//STATE_IDLE
//STATE_DRAGGING
//STATE_SETTLING fling or predefined non-interactive motion.
public void onViewDragStateChanged(int state)
9
//边缘开始拖拽的时候的回调(ViewGroup)
public void onEdgeDragStarted(int edgeFlags, int pointerId)
10
//边缘开始触摸的时候的回调(ViewGroup),注意与上面的区别
public void onEdgeTouched(int edgeFlags, int pointerId)
11
//子view的位置发生改变的时候回调
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy)
完整的例子
public class VDHViewGroup extends RelativeLayout {
private ViewDragHelper mHelper;
private Point mView2Point;
private View mView1;
private View mView2;
private Callback mCallback = new Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return mView1 == child || mView2 == child;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
@Override
public int getViewHorizontalDragRange(View child) {
return 1;
}
@Override
public int getViewVerticalDragRange(View child) {
return 1;
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (releasedChild == mView2) {
mHelper.settleCapturedViewAt(mView2Point.x, mView2Point.y);
invalidate();
}
if (releasedChild == mView1) {
mHelper.flingCapturedView(0, 0, 10000, 10000);
invalidate();
}
}
};
public VDHViewGroup(Context context) {
this(context, null);
}
public VDHViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mHelper = ViewDragHelper.create(this, mCallback);
mView2Point = new Point();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mHelper.processTouchEvent(event);
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mHelper.shouldInterceptTouchEvent(ev);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mView2Point.x = mView2.getLeft();
mView2Point.y = mView2.getTop();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mView1 = getChildAt(0);
mView2 = getChildAt(1);
}
@Override
public void computeScroll() {
if (mHelper.continueSettling(true)) {
invalidate();
}
}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<com.idreamo.rrtoyewx.studyviewdraghelper.VDHViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/animal_1"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@drawable/animal_2"/>
</com.idreamo.rrtoyewx.studyviewdraghelper.VDHViewGroup>