使用viewdraghelper 来处理一些复杂的滑动事件
1.首先 自定义一个viewgroup
这里我们选择继承LinearLayout(可以自行选择)
2.调用ViewDragHelper.create(this, 1f, new DragHelper());来创建一个ViewDragHelper对象
DragHelper是继承了ViewDragHelper.Callback 的内部类
public class DragHelper extends ViewDragHelper.Callback {} 在这里重写一些方法来对事件进行处理
3.重写onInterceptTouchEvent (可选) onTouchEvent方法
<span style="font-size:14px;">@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mDragHelper.cancel();
return false;
}
return mDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
mDragHelper.processTouchEvent(ev);//必须加上这句话调用<span style="font-size:14px;">ViewDragHelper对象来处理触摸事件 否则不响应</span>
<span style="white-space:pre"> </span>
return true;
}</span>
完整代码
<span style="font-size:14px;">package com.flavienlaurent.vdh;
import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
/**
* Created by Flavien Laurent (flavienlaurent.com) on 23/08/13.
*/
public class DragLayout extends LinearLayout {
private final ViewDragHelper mDragHelper;
private View mDragView1;
private View mDragView2;
private boolean mDragEdge;
private boolean mDragHorizontal;
private boolean mDragCapture;
private boolean mDragVertical;
public DragLayout(Context context) {
this(context, null);
}
public DragLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DragLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mDragHelper = ViewDragHelper.create(this, 1f, new DragHelper());
}
@Override
protected void onFinishInflate() {
mDragView1 = findViewById(R.id.drag1);
mDragView2 = findViewById(R.id.drag2);
}
/**
* 设置只能横向移动
* @param dragHorizontal
*/
public void setDragHorizontal(boolean dragHorizontal) {
mDragHorizontal = dragHorizontal;
mDragView2.setVisibility(View.GONE);
}
/**
* 设置只能纵向移动
* @param dragHorizontal
*/
public void setDragVertical(boolean dragVertical) {
mDragVertical = dragVertical;
mDragView2.setVisibility(View.GONE);
}
public void setDragEdge(boolean dragEdge) {
//滑动左边缘(以左边缘的坐标为基准)
mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
mDragEdge = dragEdge;
mDragView2.setVisibility(View.GONE);
}
public void setDragCapture(boolean dragCapture) {
mDragCapture = dragCapture;
}
public class DragHelper extends ViewDragHelper.Callback {
/**
* <span style="color:#ff0000;">判断当前点击的view是否可以移动</span>
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
if (mDragCapture) {
return child == mDragView1;
}
return true;
}
/**
* 位置移动时触发
*/
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
invalidate();
}
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
}
@Override
public void onEdgeTouched(int edgeFlags, int pointerId) {
super.onEdgeTouched(edgeFlags, pointerId);
}
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
if (mDragEdge) {
mDragHelper.captureChildView(mDragView1, pointerId);
}
}
/**
* 处理纵向移动 </span><span style="color:#ff0000;font-size:14px;">必须重写</span><span style="font-size:14px;">
* @param top 当前操纵的view 的应该到达的top值
* @param dy 距离的绝对值
*/
@Override
public int </span><span style="color:#ff0000;font-size:14px;">clampViewPositionVertical</span><span style="font-size:14px;">(View child, int top, int dy) {
if (mDragVertical) {
//当前视图离顶部的距离
final int topBound = getPaddingTop();
//手机高度-操纵的view的高度
final int bottomBound = getHeight() - mDragView1.getHeight();
//移动到的新高度
final int newTop = Math.min(Math.max(top, topBound), bottomBound);
return newTop;
}
return super.clampViewPositionVertical(child, top, dy);
// return top;
}
/**
* 处理横向移动 </span><span style="font-size:12px;color:#ff0000;"><span style="font-family: Arial, Helvetica, sans-serif;">必须重写</span></span><span style="font-size:14px;">
* @param left 当前操纵的view 的应该到达的left值
* @param dx 距离的绝对值
*/
@Override
public int </span><span style="color:#ff0000;font-size:14px;">clampViewPositionHorizontal</span><span style="font-size:14px;">(View child, int left, int dx) {
if (mDragHorizontal || mDragCapture || mDragEdge) {
final int leftBound = getPaddingLeft();
final int rightBound = getWidth() - mDragView1.getWidth();
final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
return newLeft;
}
return super.clampViewPositionHorizontal(child, left, dx);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mDragHelper.cancel();
return false;
}
return mDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
<span style="color:#ff0000;">//!!!重要</span>
mDragHelper.</span><span style="color:#ff0000;font-size:14px;">processTouchEvent</span><span style="font-size:14px;">(ev);
return true;
}
}
</span>
布局:
<com.flavienlaurent.vdh.DragLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dragLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<View
android:id="@+id/drag1"
android:layout_width="96dp"
android:layout_height="96dp"
android:background="#AD78CC" />
<View
android:id="@+id/drag2"
android:layout_width="96dp"
android:layout_height="96dp"
android:background="#FF00FF" />
</com.flavienlaurent.vdh.DragLayout>
实现如下效果:
代码中的关键点:
1.tryCaptureView返回了唯一可以被拖动的header view;
2.拖动范围drag range的计算是在onLayout中完成的;
3.注意在onInterceptTouchEvent和onTouchEvent中使用的ViewDragHelper的若干方法;
4.在computeScroll中使用continueSettling方法(因为ViewDragHelper使用了scroller)
5.smoothSlideViewTo方法来完成拖动结束后的惯性操作。
package com.flavienlaurent.vdh;
import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by Flavien Laurent (flavienlaurent.com) on 23/08/13.
*/
public class YoutubeLayout extends ViewGroup {
private final ViewDragHelper mDragHelper;
private View mHeaderView;
private View mDescView;
private float mInitialMotionX;
private float mInitialMotionY;
private int mDragRange;
private int mTop;
private float mDragOffset;
public YoutubeLayout(Context context) {
this(context, null);
}
public YoutubeLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public YoutubeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mDragHelper = ViewDragHelper.create(this, 1f, new DragHelperCallback());
}
@Override
protected void onFinishInflate() {
mHeaderView = findViewById(R.id.header);
mDescView = findViewById(R.id.desc);
}
public void maximize() {
smoothSlideTo(0f);
}
public void minimize() {
smoothSlideTo(1f);
}
/**
* 进行平滑的滑动效果
* @param slideOffset
* @return
*/
boolean smoothSlideTo(float slideOffset) {
final int topBound = getPaddingTop();
int y = (int) (topBound + slideOffset * mDragRange);
// 滑动到指定位置
if (<span style="color:#ff0000;">mDragHelper.smoothSlideViewTo(mHeaderView, mHeaderView.getLeft(), y)</span>) {
//内部调用scroller来进行滑动的效果
<span style="color:#ff0000;">ViewCompat.postInvalidateOnAnimation(this);</span>
return true;
}
return false;
}
private class DragHelperCallback extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == mHeaderView;
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
mTop = top;
mDragOffset = (float) top / mDragRange;
//设置轴心 设置缩放比例
mHeaderView.setPivotX(mHeaderView.getWidth());
mHeaderView.setPivotY(mHeaderView.getHeight());
mHeaderView.setScaleX(1 - mDragOffset / 2);
mHeaderView.setScaleY(1 - mDragOffset / 2);
mDescView.setAlpha(1 - mDragOffset);
//两者效果相同 requestLayout最终也是调用layout方法进行滑动效果的
// mDescView.<span style="color:#ff0000;">layout</span>(left, mTop + mHeaderView.getMeasuredHeight(),
// left + mDescView.getWidth(),
// mTop + mDescView.getHeight() + mHeaderView.getHeight());
<span style="color:#ff0000;">requestLayout();</span>
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
int top = getPaddingTop();
if (yvel > 0 || (yvel == 0 && mDragOffset > 0.5f)) {
top += mDragRange;
}
mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);
invalidate();
}
@Override
public int getViewVerticalDragRange(View child) {
return mDragRange;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
final int topBound = getPaddingTop();
final int bottomBound = getHeight() - mHeaderView.getHeight()
- mHeaderView.getPaddingBottom();
final int newTop = Math.min(Math.max(top, topBound), bottomBound);
return newTop;
}
}
<span style="color:#ff0000;">@Override
public void computeScroll() {
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}</span>
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
if ((action != MotionEvent.ACTION_DOWN)) {
mDragHelper.cancel();
return super.onInterceptTouchEvent(ev);
}
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mDragHelper.cancel();
return false;
}
final float x = ev.getX();
final float y = ev.getY();
boolean interceptTap = false;
switch (action) {
case MotionEvent.ACTION_DOWN: {
mInitialMotionX = x;
mInitialMotionY = y;
interceptTap = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);
break;
}
case MotionEvent.ACTION_MOVE: {
final float adx = Math.abs(x - mInitialMotionX);
final float ady = Math.abs(y - mInitialMotionY);
final int slop = mDragHelper.getTouchSlop();
/* useless */
if (ady > slop && adx > ady) {
mDragHelper.cancel();
return false;
}
}
}
return mDragHelper.shouldInterceptTouchEvent(ev)|| interceptTap;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
<span style="color:#ff0000;">mDragHelper.processTouchEvent(ev);</span>
final int action = ev.getAction();
final float x = ev.getX();
final float y = ev.getY();
boolean isHeaderViewUnder = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);
switch (action & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
mInitialMotionX = x;
mInitialMotionY = y;
break;
}
case MotionEvent.ACTION_UP: {
final float dx = x - mInitialMotionX;
final float dy = y - mInitialMotionY;
final int slop = mDragHelper.getTouchSlop();
if (dx * dx + dy * dy < slop * slop && isHeaderViewUnder) {
if (mDragOffset == 0) {
smoothSlideTo(1f);
} else {
smoothSlideTo(0f);
}
}
break;
}
}
return isHeaderViewUnder && isViewHit(mHeaderView, (int) x, (int) y)
|| isViewHit(mDescView, (int) x, (int) y);
}
private boolean isViewHit(View view, int x, int y) {
int[] viewLocation = new int[2];
view.getLocationOnScreen(viewLocation);
int[] parentLocation = new int[2];
this.getLocationOnScreen(parentLocation);
int screenX = parentLocation[0] + x;
int screenY = parentLocation[1] + y;
return screenX >= viewLocation[0] && screenX < viewLocation[0] + view.getWidth()
&& screenY >= viewLocation[1] && screenY < viewLocation[1] + view.getHeight();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mDragRange = getHeight() - mHeaderView.getHeight();
mHeaderView.layout(0, mTop, r, mTop + mHeaderView.getMeasuredHeight());
mDescView.layout(0, mTop + mHeaderView.getMeasuredHeight(), r, mTop + b);
}
}<span style="color:#c00000;">
</span>