一、简介
对于编写一个自定义的viewgroup ViewDragHelper是一个实用的类。它提供了很多有用的操作和状态,实现监听用户在父ViewGroup拖拽和重新定位子view。
二、详解
1. 使用步骤
(1)、自定义一个View
(2)、创建一个ViewDragHelper的实例
dragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
});
其中1.0f是敏感度参数参数越大越敏感。this为当前的ViewGroup,他是ViewDragHelper的拖动处理对象,必须为ViewGroup。
(3)、触摸相关的方法的调用
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return dragHelper.shouldInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
dragHelper.processTouchEvent(event);
return true;
}
将Tonch事件交给ViewDragHelper处理
(4)、实现ViewDragHelper.CallCack相关方法
2. 类介绍
(1)、ViewDragHelper常用方法介绍
dragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT | ViewDragHelper.EDGE_TOP | ViewDragHelper.EDGE_RIGHT | ViewDragHelper.EDGE_BOTTOM);
设置边界触摸回调是否被激活,分别为上下左右边界的设置
dragHelper.smoothSlideViewTo(view, getPaddingLeft(), getPaddingTop())
使用动画平滑的移动到指定位置,但是一定要添加下边代码
@Override
public void computeScroll() {
if (dragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
还有一些其他功能方法,如(取消、状态跟踪)的方法,比较好理解这里不做介绍。
(2)、ViewDragHelper.Callback的类介绍,在下边示例注释中已经非常完善,这里不做重复介绍了。
3. 相关问题
(1)、如果ViewGroup的子控件会消耗点击事件,例如按钮,在触摸屏幕的时候就会先走onInterceptTouchEvent方法,判断是否可以捕获,而在判断的过程中会去判断另外两个回调的方法:getViewHorizontalDragRange和getViewVerticalDragRange,只有这两个方法返回大于0的值才能正常的捕获,否则会出现子控件不能拖动的问题。
(2)、如果需要使用边界检测需要添加上
dragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT | ViewDragHelper.EDGE_TOP | ViewDragHelper.EDGE_RIGHT | ViewDragHelper.EDGE_BOTTOM);
(3)、如果你当前布局的根布局不是DragLayout,可能会遇到DragLayout遮挡其他布局Touch事件的问题。这时你可以考虑onTouchEvent(),onInterceptTouchEvent()方法,根据需求动态返回true,false。
三、示例
1. 示例效果
2. 代码分析
DragLayout.java
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
public class DragLayout extends LinearLayout {
private static final String TAG = "DragLayout";
private ViewDragHelper dragHelper;
private TextView mDragView1;
private View mDragView2;
private View mDragView3;
public DragLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
dragHelper = ViewDragHelper.create(this, callback);
// 设置边界触摸回调被激活
dragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT | ViewDragHelper.EDGE_TOP | ViewDragHelper.EDGE_RIGHT | ViewDragHelper.EDGE_BOTTOM);
}
private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
/**
* 指定View是否可拖拽
* @param child 用户捕获的view
* @param pointerId
* @return 捕获的view是否可拖动
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == mDragView1 || child == mDragView3;
}
/**
* @param state 拖拽的状态
*
* @see # STATE_IDLE 拖拽结束
* @see # STATE_DRAGGING 正在拖拽
* @see # STATE_SETTLING 正在被放置,这个状态不会出现
*/
@Override
public void onViewDragStateChanged(int state) {
if (state == ViewDragHelper.STATE_DRAGGING) {
mDragView1.setText("正在拖拽");
} else if (state == ViewDragHelper.STATE_IDLE) {
mDragView1.setText("拖拽结束");
}
}
/**
* 当View的位置发生改变是回调
* @param changedView 当前发生位置改变的View
* @param left view左上角新X坐标
* @param top view左上角新Y坐标
* @param dx 俩次回调X轴方向移动的距离
* @param dy 俩次回调Y轴方向移动的距离
*/
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
Log.d(TAG, "onViewPositionChanged left" + left);
Log.d(TAG, "onViewPositionChanged top" + top);
Log.d(TAG, "onViewPositionChanged dx" + dx);
Log.d(TAG, "onViewPositionChanged dy" + dy);
Log.d(TAG, "----------------------");
}
/**
* 当view被捕获时回调
*
* @param capturedChild 被捕获的view
* @param activePointerId
*/
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
}
/**
* 手指释放时候回调该方法
* @param releasedChild 被释放的子view
* @param xvel 手指的X轴速度,以像素每秒钟离开屏幕
* @param yvel 手指的Y轴速度,以像素每秒钟离开屏幕
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
// 向下,向右为正,否则为负
if (yvel > 0) {
if (releasedChild == mDragView3) {
smoothToBottom(releasedChild);
}
Toast.makeText(getContext(), "释放时,速度向下大于0", Toast.LENGTH_SHORT).show();
} else if (yvel < 0) {
if (releasedChild == mDragView3) {
smoothToTop(releasedChild);
}
Toast.makeText(getContext(), "释放时,速度向上大于0", Toast.LENGTH_SHORT).show();
}
}
/**
* 当触摸到父布局边界时回调(必须调用ViewDragHelper.setEdgeTrackingEnabled才会生效)
*
* @param edgeFlags
* @param pointerId
*/
@Override
public void onEdgeTouched(int edgeFlags, int pointerId) {
if (edgeFlags == ViewDragHelper.EDGE_TOP) {
Toast.makeText(getContext(), "到上边界了", Toast.LENGTH_SHORT).show();
} else if (edgeFlags == ViewDragHelper.EDGE_BOTTOM) {
Toast.makeText(getContext(), "到下边界了", Toast.LENGTH_SHORT).show();
} else if (edgeFlags == ViewDragHelper.EDGE_LEFT) {
Toast.makeText(getContext(), "到左边界了", Toast.LENGTH_SHORT).show();
} else if (edgeFlags == ViewDragHelper.EDGE_RIGHT) {
Toast.makeText(getContext(), "到右边界了", Toast.LENGTH_SHORT).show();
}
}
@Override
public boolean onEdgeLock(int edgeFlags) {
return false;
}
/**
* 当没有捕获到子view时,通过触摸边界拖拽子view(场景:当view在屏幕外,无法捕获view,只能通过触摸屏幕边界先让他滚回来)
* @param edgeFlags
* @param pointerId
*/
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
dragHelper.captureChildView(mDragView2, pointerId);
// dragHelper.captureChildView(mDragView3, pointerId);
}
/**
* 返回捕获的view Z轴的坐标(即当前view在布局的第几层)
* @param index
* @return
*/
@Override
public int getOrderedChildIndex(int index) {
Log.d(TAG, "getOrderedChildIndex index" + index);
return index;
}
/**
* 该view水平方向拖动的范围(子控件消耗点击事件时候才回调(例如:按钮))
* @param child
* @return
*/
@Override
public int getViewHorizontalDragRange(View child) {
return getMeasuredWidth() - child.getMeasuredWidth();
}
/**
* 该view垂直方向拖动的范围(子控件消耗点击事件时候才回调(例如:按钮))
* @param child
* @return
*/
@Override
public int getViewVerticalDragRange(View child) {
return getMeasuredHeight() - child.getMeasuredHeight();
}
/**
* 垂直方向限制View的拖动范围
* @param child 被拖动的子View
* @param top 移动过程中Y轴的坐标
* @param dy
* @return view的新位置
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
// 限定view上下边界(以view左上角坐标点为准)
final int topBound = getPaddingTop();
final int bottomBound = getHeight() - child.getHeight();
final int newTop = Math.min(Math.max(top, topBound), bottomBound);
return newTop;
}
/**
* 水平方向限制View的拖动范围
* @param child 被拖动的子View
* @param left 移动过程中X轴的坐标
* @param dx
* @return view的新位置
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
};
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return dragHelper.shouldInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
dragHelper.processTouchEvent(event);
return true;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mDragView1 = (TextView) getChildAt(0);
mDragView2 = getChildAt(1);
mDragView3 = getChildAt(2);
}
public void smoothToTop(View view) {
// 动画平滑的移动到指定位置
if (dragHelper.smoothSlideViewTo(view, getPaddingLeft(), getPaddingTop())) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
public void smoothToBottom(View view) {
// 动画平滑的移动到指定位置
if (dragHelper.smoothSlideViewTo(view, getPaddingLeft(), getHeight() - view.getHeight())) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
@Override
public void computeScroll() {
if (dragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
}
XML文件
<com.wyj.viewdraglayout.DragLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textview1"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:layout_margin="10dp"
android:background="#FFDCD665"
android:gravity="center"
android:text="可直接拖动的View" />
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:layout_margin="10dp"
android:background="#FFDCD665"
android:gravity="center"
android:text="触摸边界可拖动的View" />
<Button
android:id="@+id/button"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_margin="10dp"
android:background="#FFDCD665"
android:gravity="center"
android:text="Button" />
</com.wyj.viewdraglayout.DragLayout>