在Android中避免不了自定义ViewGroup,来实现我们原生控件所不能满足的需求。尤其是复杂的ViewGroup实现,手势的处理是避免不了的。我们要针对不同的ViewGroup来实现不同的onInterceptTouchEvent
与onTouchEvent
事件等。
ViewDragHelper可以帮助我们解决负责的手势操作。它是官方所提供的一个专门为自定义ViewGroup处理拖拽的手势类。下面是官方的原文引用说明
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.
Purpose
通过这篇文章你将会掌握以下几个知识点:
- ViewDragHelper的简单入门
- ViewDragHelper的关键API用途
- 使用ViewDragHelper实现view的拖拽
- 子view交换位置
use
首先需要构建ViewDragHelper的实例,通过它的静态create
方法生成
mDragger = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
// return child == mDragView ;
return true;
}
主要参数为ViewGroup
与ViewDragHelper.Callback
。Callback是对view操作的回调,绝对多数手势操作都是在这个回调中完成。tryCaptureView
方法是它唯一的抽象方法,默认需要实现。根据参数child判断用户触摸的view是否可以进行后续操作。
为了让ViewDragHelper帮助我们简化手势操作,所以还需为它传入相关的MotionEvent
。
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
return mViewDragHelper.shouldInterceptTouchEvent(ev)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
mViewDragHelper.processTouchEvent(event)
return true
}
如果要处理惯性滑动,再重写computeScroll
方法
override fun computeScroll() {
if (mViewDragHelper.continueSettling(true)) {
invalidate()
}
}
Demo
完整代码
package com.example.tt;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
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;
public class ViewDragLayout extends LinearLayout {
private static final String TAG = "ViewDragLayout";
private ViewDragHelper mDragger;
private OnLongClickListener onLongClickListenner = new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
scaleCapture(true, v);
return true;
}
};
public ViewDragLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mDragger = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
// return child == mDragView ;
return true;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
int paddingleft = getPaddingLeft();
if (left < paddingleft) {
return paddingleft;
}
int pos = getWidth() - child.getWidth() - getPaddingRight();
if (left > pos) {
return pos;
}
return left;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
int paddingTop = getPaddingTop();
if (top < paddingTop) {
return paddingTop;
}
int pos = getHeight() - child.getHeight() - getPaddingBottom();
if (top > pos) {
return pos;
}
return top;
}
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
if (state == ViewDragHelper.STATE_IDLE) {
} else if (state == ViewDragHelper.STATE_DRAGGING) {
} else if (state == ViewDragHelper.STATE_SETTLING) {
}
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
}
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
Log.d(TAG, "onViewCaptured:" + capturedChild.getId());
// scaleCapture(true);
}
@Override
public void onEdgeTouched(int edgeFlags, int pointerId) {
super.onEdgeTouched(edgeFlags, pointerId);
}
@Override
public boolean onEdgeLock(int edgeFlags) {
return super.onEdgeLock(edgeFlags);
}
@Override
public int getOrderedChildIndex(int index) {
return super.getOrderedChildIndex(index);
}
//手指释放的时候回调
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
scaleCapture(false);
//交换位置
int[] p = new int[2];
releasedChild.getLocationInWindow(p);
DragView target = getCoverView(releasedChild, p[0], p[1]);
swipeViewIfNeed((DragView) releasedChild, target);
}
/**
* 交换视图
* @param releasedChild
* @param target
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private void swipeViewIfNeed(DragView releasedChild, final DragView target) {
if (null != releasedChild && null != target) {
//place capturedView
mDragger.settleCapturedViewAt(target.getLeft(), target.getTop());
// place targetView
PropertyValuesHolder x = PropertyValuesHolder.ofFloat("x", target.getLeft(), releasedChild.getStartX());
PropertyValuesHolder y = PropertyValuesHolder.ofFloat("y", target.getTop(), releasedChild.getStartY());
ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(x, y).setDuration(300);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int x = ((Float) animation.getAnimatedValue("x")).intValue();
int y = ((Float) animation.getAnimatedValue("y")).intValue();
Log.d(TAG, "x," + x + ",y:" + y);
dragTo(target, x, y);
}
});
animator.start();
//延时
// mDragger.smoothSlideViewTo(target,releasedChild.getStartX(),releasedChild.getStartY());
invalidate();
}
}
//在边界拖动时回调
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
// mDragger.captureChildView(mEdgeTrackerView, pointerId);
}
@Override
public int getViewHorizontalDragRange(View child) {
int ragnetH = getMeasuredWidth() - child.getMeasuredWidth();
Log.d(TAG, "ragnetH:" + ragnetH + " child" + child.getMeasuredHeight());
return ragnetH;
}
@Override
public int getViewVerticalDragRange(View child) {
int rangeV = getMeasuredHeight() - child.getMeasuredHeight();
Log.d(TAG, "rangeV:" + rangeV);
return rangeV;
}
});
mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
}
private void dragTo(View mCapturedView, int left, int top) {
int clampedX = left;
int clampedY = top;
final int oldLeft = mCapturedView.getLeft();
final int oldTop = mCapturedView.getTop();
ViewCompat.offsetLeftAndRight(mCapturedView, clampedX - oldLeft);
ViewCompat.offsetTopAndBottom(mCapturedView, clampedY - oldTop);
}
/**
* 简单写了下
* @param releasedChild
* @param xvel
* @param yvel
* @return
*/
private DragView getCoverView(View releasedChild, float xvel, float yvel) {
Rect r = new Rect();
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view != releasedChild) {
if (view.getGlobalVisibleRect(r)) {
if (r.contains((int) xvel, (int) yvel))
return (DragView) view;
}
}
}
return null;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void scaleCapture(boolean scale) {
View capturedView = mDragger.getCapturedView();
if (null != capturedView) {
Log.d(TAG, "scaleCapture:" + scale);
capturedView.setScaleX(scale ? 0.8f : 1);
capturedView.setScaleY(scale ? 0.8f : 1);
capturedView.invalidate();
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void scaleCapture(boolean scale, View capturedView) {
if (null != capturedView) {
Log.d(TAG, "scaleCapture:" + scale);
capturedView.setScaleX(scale ? 0.8f : 1);
capturedView.setScaleY(scale ? 0.8f : 1);
capturedView.invalidate();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (null == mDragger)
return false;
boolean intercept = mDragger.shouldInterceptTouchEvent(event);
Log.d(TAG, "onInterceptTouchEvent:" + intercept + " action:" + event.getAction());
return intercept;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent parent:" + " action:" + event.getAction());
mDragger.processTouchEvent(event);
return true;
}
@Override
public void computeScroll() {
if (null != mDragger)
if (mDragger.continueSettling(true)) {
invalidate();
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
Log.d(TAG, "onLayout:");
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
view.setOnLongClickListener(onLongClickListenner);
}
}
}