效果
DragBackLayout.gif
自定义属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DragBackLayout">
<attr name="dbl_style" format="enum">
<!-- 菜单和内容,一起移动 -->
<enum name="scroll" value="1" />
<!-- 菜单固定,只有内容移动 -->
<enum name="fixed" value="2" />
</attr>
</declare-styleable>
</resources>
代码实现
/**
* 全局侧滑返回
*/
public class DragBackLayout extends FrameLayout {
/**
* 风格:菜单和内容,一起移动
*/
public static final int STYLE_SCROLL = 1;
/**
* 风格:菜单固定,只有内容移动
*/
public static final int STYLE_FIXED = 2;
/**
* 拽托帮助类
*/
private ViewDragHelper mViewDragHelper;
/**
* 风格
*/
private int mStyle;
/**
* 菜单View
*/
private View mMenuView;
/**
* 内容子View
*/
private View mContentView;
/**
* 当前内容子View的Left值,由于子View引起重绘或addView引起重绘会导致requestLayout(),会重新onLayout()子View
* 而layout子View,要在onLayout中处理。如果在onViewPositionChanged()中单独onLayout,并且又没有保存left值为成员变量,就会因为其他原因引起的requestLayout()
* 导致子View的left值默认值为0,会突然抖回左边
*/
private int mCurrentLeft = 0;
/**
* 拽托状态回调
*/
private OnDragStateChangeListener mOnDragStateChangeListener;
public DragBackLayout(Context context) {
this(context, null);
}
public DragBackLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DragBackLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
mViewDragHelper = ViewDragHelper.create(this, 0.5f, new ViewDragHelperCallBack());
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DragBackLayout, defStyleAttr, 0);
mStyle = typedArray.getInt(R.styleable.DragBackLayout_dbl_style, STYLE_SCROLL);
typedArray.recycle();
}
private class ViewDragHelperCallBack extends ViewDragHelper.Callback {
/**
* 开始拽托时的X坐标
*/
private int mDownX;
/**
* 开始拽托时的Y坐标
*/
private int mDownY;
@Override
public boolean tryCaptureView(@NonNull View child, int pointerId) {
//返回ture则表示可以捕获该view,手指摸上一瞬间调用
return child == mMenuView || child == mContentView;
}
@Override
public void onViewCaptured(@NonNull View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
mDownX = capturedChild.getLeft();
mDownY = capturedChild.getTop();
}
@Override
public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
//手指触摸移动时实时回调
//限制只能向右滑
if (left < 0) {
return 0;
}
//最多只能滑动到菜单View的宽度
int maxLeft = mMenuView.getWidth();
return Math.min(left, maxLeft);
}
@Override
public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
//手指释放时回调
final int currentLeft = releasedChild.getLeft();
final int currentTop = releasedChild.getTop();
//计算移动距离
int distanceX = currentLeft - mDownX;
int distanceY = currentTop - mDownY;
float halfMenuWidth = mMenuView.getWidth() / 2f;
//滚动到左边
Runnable scrollToLeft = new Runnable() {
@Override
public void run() {
mViewDragHelper.settleCapturedViewAt(0, 0);
}
};
//滚动到右边
Runnable scrollToRight = new Runnable() {
@Override
public void run() {
int finalLeft = mMenuView.getWidth();
mViewDragHelper.settleCapturedViewAt(finalLeft, 0);
}
};
//根据移动距离占总大小的百分比,决定滚动到左边还是右边
Runnable scrollByDistancePercentage = new Runnable() {
@Override
public void run() {
//小于一半菜单的宽度,回弹回左边
if (releasedChild.getLeft() < halfMenuWidth) {
scrollToLeft.run();
} else {
//大于一半,则算右边
scrollToRight.run();
}
}
};
//判断是否是左右滑,上下滑不需要动
if (Math.abs(distanceX) > Math.abs(distanceY)) {
//上下滑,速度很快,则为fling操作
if (Math.abs(xvel) > Math.abs(mViewDragHelper.getMinVelocity())) {
//距离相减为正数,并且惯性速度为正数,滚动到右边
if (distanceX > 0 && xvel > 0) {
scrollToRight.run();
} else {
//否则回弹回左边
scrollToLeft.run();
}
} else {
//不是fling操作,根据移动距离占总大小的百分比,决定滚动到左边还是右边
scrollByDistancePercentage.run();
}
}
invalidate();
}
@Override
public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) {
mCurrentLeft = left;
//统一在onLayout中,重新布局子View位置,来移动子View
requestLayout();
}
@Override
public int getViewHorizontalDragRange(@NonNull View child) {
if (mContentView == null) {
return 0;
}
if (mContentView == child) {
return mContentView.getWidth();
} else {
return 0;
}
}
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
DragBackLayout layout = DragBackLayout.this;
if (state == ViewDragHelper.STATE_DRAGGING) {
if (mOnDragStateChangeListener != null) {
mOnDragStateChangeListener.onStartDrag(layout);
}
} else if (state == ViewDragHelper.STATE_IDLE) {
boolean isScroll2Right = mContentView.getLeft() == mMenuView.getWidth();
if (mOnDragStateChangeListener != null) {
mOnDragStateChangeListener.onStopDrag(layout);
}
if (isScroll2Right) {
if (mOnDragStateChangeListener != null) {
mOnDragStateChangeListener.onDragToBack(layout);
}
}
}
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//移动内容子View
//菜单View
MarginLayoutParams menuViewLp = (MarginLayoutParams) mMenuView.getLayoutParams();
if (mStyle == STYLE_SCROLL) {
int menuLeft = -mMenuView.getMeasuredWidth() + menuViewLp.leftMargin;
mMenuView.layout(
mCurrentLeft + menuLeft,
menuViewLp.topMargin,
mCurrentLeft + menuLeft + mMenuView.getMeasuredWidth() + menuViewLp.leftMargin,
mMenuView.getMeasuredHeight() + menuViewLp.topMargin
);
} else if (mStyle == STYLE_FIXED) {
int menuLeft = menuViewLp.leftMargin;
mMenuView.layout(
menuLeft,
menuViewLp.topMargin,
menuLeft + mMenuView.getMeasuredWidth() + menuViewLp.leftMargin,
mMenuView.getMeasuredHeight() + menuViewLp.topMargin
);
}
//内容View
MarginLayoutParams contentViewLp = (MarginLayoutParams) mContentView.getLayoutParams();
mContentView.layout(
mCurrentLeft + contentViewLp.leftMargin,
contentViewLp.topMargin,
mCurrentLeft + mContentView.getMeasuredWidth() + contentViewLp.leftMargin,
mContentView.getMeasuredHeight() + contentViewLp.topMargin
);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
int childCount = getChildCount();
if (childCount != 2) {
throw new RuntimeException("子View必须只有2个,分别是菜单View和内容View");
}
mMenuView = getChildAt(0);
mContentView = getChildAt(1);
}
@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//将onInterceptTouchEvent委托给ViewDragHelper
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//将onTouchEvent委托给ViewDragHelper
mViewDragHelper.processTouchEvent(event);
return true;
}
/**
* 滚性滚动关闭
*/
public void smoothClose() {
mViewDragHelper.smoothSlideViewTo(mContentView, 0, 0);
}
/**
* 无动画滚动关闭
*/
public void quickClose() {
mCurrentLeft = 0;
requestLayout();
}
/**
* 结束回调
*/
public interface OnDragStateChangeListener {
void onStartDrag(DragBackLayout layout);
void onStopDrag(DragBackLayout layout);
void onDragToBack(DragBackLayout layout);
}
public void setOnDragStateChangeListener(OnDragStateChangeListener listener) {
this.mOnDragStateChangeListener = listener;
}
}
使用
- 布局xml
<?xml version="1.0" encoding="utf-8"?>
<com.zh.dragbacklayout.DragBackLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drag_back_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:dbl_style="scroll">
<!-- 左边的返回提示菜单 -->
<FrameLayout
android:id="@+id/back_tip"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@color/black"
android:padding="15dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_back_icon" />
</FrameLayout>
<!-- 内容View -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/purple_200">
</FrameLayout>
</com.zh.dragbacklayout.DragBackLayout>
- Java代码
DragBackLayout dragBackLayout = findViewById(R.id.drag_back_layout);
//设置拽托监听
dragBackLayout.setOnDragStateChangeListener(new DragBackLayout.OnDragStateChangeListener() {
@Override
public void onStartDrag(DragBackLayout layout) {
//开始拽托
}
@Override
public void onStopDrag(DragBackLayout layout) {
//停止拽托
}
@Override
public void onDragToBack(DragBackLayout layout) {
//拽托关闭
finish();
}
});