package com.adnonstop.draghelper.viewgroups;
import android.content.Context;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
/**
* Created by gzq on 2017/1/19.
*/
public class DragViewGroup extends FrameLayout {
private ViewDragHelper dragHelper;
private View mDragView;
private View mAutoBackView;
private View mEdgeTrackerView;
private int mAutoBackViewOriginalPos_x;
private int mAutoBackViewOriginalPos_y;
public DragViewGroup(Context context) {
this(context, null);
}
public DragViewGroup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DragViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
dragHelper = ViewDragHelper.create(this, new DragCallback());
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
initChild();
}
});
//拖拽左边界回调
dragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
}
/**
* 在viewGroup绘制初始化过程中,要一个一个添加子view,有多少个子view,就会调用多少次onViewAdded;
* 可以在次做一些view类型判断的工作。
*
* @param child
*/
@Override
public void onViewAdded(View child) {
super.onViewAdded(child);
// if (!(child instanceof ViewGroup))
// throw new RuntimeException("the child most be a viewgroup!");
}
/**
* 获取到子view。
*/
private void initChild() {
for (int index = 0; index < getChildCount(); index++) {
View child = getChildAt(index);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return dragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
dragHelper.processTouchEvent(event);
return true;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mAutoBackViewOriginalPos_x = mAutoBackView.getLeft();
mAutoBackViewOriginalPos_y = mAutoBackView.getTop();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mDragView = getChildAt(0);
mAutoBackView = getChildAt(1);
mEdgeTrackerView = getChildAt(2);
}
@Override
public void computeScroll() {
if (dragHelper.continueSettling(true)) {
invalidate();
}
}
private class DragCallback extends ViewDragHelper.Callback {
/**
* tryCaptureView 返回ture则表示可以捕获该view,你可以根据传入的第一个参数view决定哪些可以捕获
*
* @param child
* @param pointerId
* @return
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
/**
* @param child
* @param left 对移动的边界进行控制
* @param dx
* @return
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
int newLeft = Math.max(0, Math.min(left, getWidth() - child.getWidth()));
return newLeft;
}
/**
* @param child
* @param top 控制移动的边界
* @param dy
* @return
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
int newTop = Math.max(0, Math.min(top, getHeight() - child.getHeight()));
return newTop;
}
/**
* @param releasedChild
* @param xvel 松手时沿X轴的速度
* @param yvel 松手时沿Y轴的速度
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if (releasedChild == mAutoBackView) {
dragHelper.settleCapturedViewAt(mAutoBackViewOriginalPos_x, mAutoBackViewOriginalPos_y);
invalidate();
}
}
/**
* @param changedView
* @param left new left
* @param top new top
* @param dx
* @param dy
*/
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
}
// See also:
// STATE_IDLE 静止
// STATE_DRAGGING 拖拽中
// STATE_SETTLING 自动滚动
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
}
/*
* 边界拖动回调:
* 在viewGroup的左边界拖动时的回调。
* */
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
super.onEdgeDragStarted(edgeFlags, pointerId);
System.out.println("******边界拖动回调**********");
}
}
}
第一个View基本没做任何修改。
第二个View,我们在onLayout之后保存了最开启的位置信息,最主要还是重写了Callback中的onViewReleased
,我们在onViewReleased中判断如果是mAutoBackView则调用settleCapturedViewAt
回到初始的位置。大家可以看到紧随其后的代码是invalidate();因为其内部使用的是mScroller.startScroll,所以别忘了需要invalidate()以及结合computeScroll
方法一起。
第三个View,我们在onEdgeDragStarted
回调方法中,主动通过captureChildView
对其进行捕获,该方法可以绕过tryCaptureView,所以我们的tryCaptureView虽然并为返回true,但却不影响。注意如果需要使用边界检测需要添加上mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
。
到此,我们已经介绍了Callback中常用的回调方法了,当然还有一些方法没有介绍,接下来我们修改下我们的布局文件,我们把我们的TextView全部加上clickable=true,意思就是子View可以消耗事件。再次运行,你会发现本来可以拖动的View不动了,(如果有拿Button测试的兄弟应该已经发现这个问题了,我希望你看到这了,而不是已经提问了,哈~)。
原因是什么呢?主要是因为,如果子View不消耗事件,那么整个手势(DOWN-MOVE*-UP)都是直接进入onTouchEvent,在onTouchEvent的DOWN的时候就确定了captureView。如果消耗事件,那么就会先走onInterceptTouchEvent
方法,判断是否可以捕获,而在判断的过程中会去判断另外两个回调的方法:getViewHorizontalDragRange
和getViewVerticalDragRange
,只有这两个方法返回大于0的值才能正常的捕获。
所以,如果你用Button测试,或者给TextView添加了clickable = true ,都记得重写下面这两个方法:
@Override
public int getViewHorizontalDragRange(View child)
{
return 1;
}
@Override
public int getViewVerticalDragRange(View child)
{
return 1;
}