package com.wuxiaolong.androidsamples.viewdraghelper; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ViewCompat; import android.support.v4.widget.ViewDragHelper; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; /** * Created by WuXiaolong * on 2015/12/4. */ public class ViewDragLayout extends LinearLayout { /** * 内容界面 */ private ViewGroup mContentLayout; /** * 遮盖界面 */ private ViewGroup mBehindLayout; private ViewDragHelper mViewDragHelper; /** * 手势事件类 */ private GestureDetectorCompat mGestureDetectorCompat; /** * 滑动距离 */ private int mViewDragRange; /** * 左滑最大距离 */ private int mBehindLayoutWidth; /** * 宽度 */ private int mContentLayoutWidth; private ViewDragListener mViewDragListener; private boolean isOpen = false; public ViewDragLayout(Context context) { super(context); init(); } public ViewDragLayout(Context context, AttributeSet attrs) { super(context, attrs); init(); } public ViewDragLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public ViewDragLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(); } /** * View 中所有的子控件均被映射成xml后触发 */ @Override protected void onFinishInflate() { mContentLayout = (ViewGroup) getChildAt(0); mBehindLayout = (ViewGroup) getChildAt(1); mBehindLayout.setClickable(true); mContentLayout.setClickable(true); super.onFinishInflate(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mContentLayoutWidth = mContentLayout.getMeasuredWidth(); mBehindLayoutWidth = mBehindLayout.getMeasuredWidth(); } /** * 设置监听 */ public void setOnViewDragListener(ViewDragListener viewDragListener) { this.mViewDragListener = viewDragListener; } public interface ViewDragListener { void onOpen(); void onClose(); void onDrag(float percent); } /** * 滑动时松手后以一定速率继续自动滑动下去并逐渐停止, * 类似于扔东西或者松手后自动滑动到指定位置 */ @Override public void computeScroll() { if (mViewDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } } /** * 初始化 */ private void init() { //创建ViewDragHelper的实例,第一个参数是ViewGroup,传自己, // 第二个参数就是滑动灵敏度的意思,可以随意设置,第三个是回调 mViewDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback()); //手势操作,第二参数什么意思看下面 mGestureDetectorCompat = new GestureDetectorCompat(getContext(), new XScrollDetector()); } /** * 手势监听回调, * SimpleOnGestureListener为了不用重写那么多监听的帮助类 */ private class XScrollDetector extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { //判断是否是滑动的x距离>y距离 return Math.abs(distanceY) <= Math.abs(distanceX); } } class DragHelperCallback extends ViewDragHelper.Callback { /** * 根据返回结果决定当前child是否可以拖拽 * * @param child 当前被拖拽的view * @param pointerId 区分多点触摸的id * @return */ @Override public boolean tryCaptureView(View child, int pointerId) { return mContentLayout == child; } /** * 返回拖拽的范围,不对拖拽进行真正的限制,仅仅决定了动画执行速度 * * @param child * @return */ @Override public int getViewHorizontalDragRange(View child) { return mContentLayoutWidth; } /** * @param child * @param left 代表你将要移动到的位置的坐标,返回值就是最终确定的移动的位置, * 判断如果这个坐标在layout之内,那我们就返回这个坐标值, * 不能让他超出这个范围, 除此之外就是如果你的layout设置了padding的话, * 让子view的活动范围在padding之内的 * @param dx * @return */ @Override public int clampViewPositionHorizontal(View child, int left, int dx) { //Log.d("wxl", "clampViewPositionHorizontal left=" + left); if (child == mContentLayout) { int newLeft = Math.min( Math.max((-getPaddingLeft() - mBehindLayoutWidth), left), 0); return newLeft; } else { int newLeft = Math.min( Math.max(left, getPaddingLeft() + mContentLayoutWidth - mBehindLayoutWidth), (getPaddingLeft() + mContentLayoutWidth + getPaddingRight())); return newLeft; } } /** * 位置改变时回调,常用于滑动是更改scale进行缩放等效果 * * @param changedView * @param left * @param top * @param dx 横向滑动的加速度 * @param dy */ @Override public void onViewPositionChanged( View changedView, int left, int top, int dx, int dy) { //Log.d("wxl", "onViewPositionChanged left=" + left); mViewDragRange = left; float percent = Math.abs((float) left / (float) mContentLayoutWidth); if (null != mViewDragListener) mViewDragListener.onDrag(percent); if (changedView == mContentLayout) { mBehindLayout.offsetLeftAndRight(dx); } else { mContentLayout.offsetLeftAndRight(dx); } invalidate(); } /** * 拖动结束后调用 * * @param releasedChild * @param xvel 水平方向的速度 向右为正 * @param yvel 竖直方向的速度 向下为正 */ @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { if (releasedChild == mContentLayout) { if (xvel <= 0) {//向左滑动 if (-mViewDragRange >= mBehindLayoutWidth / 2 && -mViewDragRange <= mBehindLayoutWidth) { open(); } else { close(); } } else {//向右滑动 if (-mViewDragRange >= 0 && -mViewDragRange <= mBehindLayoutWidth) { close(); } else { open(); } } } } } /** * 打开 */ public void open() { if (mViewDragHelper.smoothSlideViewTo( mContentLayout, -mBehindLayoutWidth, 0)) { ViewCompat.postInvalidateOnAnimation(this); } if (null != mViewDragListener) mViewDragListener.onOpen(); isOpen = true; } /** * 关闭 */ public void close() { if (mViewDragHelper.smoothSlideViewTo(mContentLayout, 0, 0)) { ViewCompat.postInvalidateOnAnimation(this); } if (null != mViewDragListener) mViewDragListener.onClose(); isOpen = false; } public boolean isOpen() { return isOpen; } /** * 事件拦截下来,相当于把自定义控件的事件交给ViewDragHelper去处理 * * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return mViewDragHelper.shouldInterceptTouchEvent(ev) && mGestureDetectorCompat.onTouchEvent(ev); } /** * 事件监听,交给ViewDragHelper处理 * * @param e * @return */ @Override public boolean onTouchEvent(MotionEvent e) { try { mViewDragHelper.processTouchEvent(e); } catch (Exception ex) { ex.printStackTrace(); } return false; } }