自定义布局
/**
* 左滑退出activity
*/
public class LeftSwipeBackLayout extends ViewGroup {
private static final int FULL_ALPHA = 255;
private static final int MIN_FLING_VELOCITY = 400; // dips per second
private static final int OVERSCROLL_DISTANCE = 10;
private static final int DEFAULT_SCRIM_COLOR = 0x99000000;
/**
* Default threshold of scroll
*/
private static final float DEFAULT_SCROLL_THRESHOLD = 0.3f;
private ViewDragHelper viewDragHelper;
private ViewDragHelperCallBack callBack;
private Drawable leftShadow;
private float scrimOpacity;
private boolean enableSwipeBack;
private float scrollPercent;
private boolean inLayout;
private View contentView; //this view is the child of LeftSwipeBackLayout
private Rect tmpRect;
private int scrimColor;
private Activity activity;
private View scrollChildView;
private int horizontalDragRange;
private int contentLeft;
private int contentTop;
private float scrollThreshold;
private float initialX;
private float initialY;
public LeftSwipeBackLayout(Context context) {
this(context, null);
}
public LeftSwipeBackLayout(Context context, AttributeSet attrs) {
super(context, attrs);
tmpRect = new Rect();
scrollThreshold = DEFAULT_SCROLL_THRESHOLD;
callBack = new ViewDragHelperCallBack();
viewDragHelper = ViewDragHelper.create(this, 1.0f, callBack);
scrimColor = DEFAULT_SCRIM_COLOR;
leftShadow = ContextCompat.getDrawable(context, R.drawable.shadow_left);
final float density = getResources().getDisplayMetrics().density;
viewDragHelper.setMinVelocity(MIN_FLING_VELOCITY * density);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getChildCount() > 1) {
throw new IllegalStateException("SwipeBackLayout must contains only one direct child.");
}
if (getChildCount() > 0) {
int measureWidth = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
int measureHeight = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);
getChildAt(0).measure(measureWidth, measureHeight);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
inLayout = true;
if (contentView != null) {
contentView.layout(contentLeft, contentTop, contentLeft + contentView.getMeasuredWidth(), contentTop + contentView.getMeasuredHeight());
}
inLayout = false;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
horizontalDragRange = w;
}
@Override
public void requestLayout() {
if (!inLayout) {
super.requestLayout();
}
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
final boolean drawContent = (child == contentView);
boolean ret = super.drawChild(canvas, child, drawingTime);
if (scrimOpacity > 0f && drawContent && viewDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE) {
drawShadow(canvas, child);
drawScrim(canvas, child);
}
return ret;
}
private void drawShadow(Canvas canvas, View child) {
final Rect childRect = tmpRect;
child.getHitRect(childRect);
leftShadow.setBounds(childRect.left - leftShadow.getIntrinsicWidth(), childRect.top, childRect.left, childRect.bottom);
leftShadow.setAlpha((int) (FULL_ALPHA * scrimOpacity));
leftShadow.draw(canvas);
}
private void drawScrim(Canvas canvas, View child) {
final int baseAlpha = (scrimColor & 0xff000000) >>> 24;
final int alpha = (int) (baseAlpha * scrimOpacity);
final int color = alpha << 24 | (scrimColor & 0xffffff);
canvas.clipRect(0, 0, child.getLeft(), getHeight());
canvas.drawColor(color);
}
@Override
public void computeScroll() {
scrimOpacity = 1 - scrollPercent;
if (viewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean handled = super.onInterceptTouchEvent(ev);
if (enableSwipeBack) {
viewDragHelper.shouldInterceptTouchEvent(ev);
if (!checkHorizontalScrollToRight()) {
handled = computeGestureStandard(ev);
}
} else {
viewDragHelper.cancel();
}
return handled;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!enableSwipeBack) {
return false;
}
viewDragHelper.processTouchEvent(event);
return true;
}
private boolean computeGestureStandard(MotionEvent ev) {
boolean ret = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
initialX = ev.getX();
initialY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
float deltaX = Math.abs((ev.getX() - initialX));
float deltaY = Math.abs((ev.getY() - initialY));
ret = ev.getX() > initialX && deltaX > deltaY && deltaY < 24;
initialX = ev.getX();
initialY = ev.getY();
break;
case MotionEvent.ACTION_UP:
initialX = ev.getX();
initialY = ev.getY();
break;
}
return ret;
}
private boolean checkHorizontalScrollToRight() { //check the view could move to right direction
return ViewCompat.canScrollHorizontally(scrollChildView, -1);
}
/**
* control swiping back on or off
*
* @param enableSwipeBack
*/
public void setEnableSwipeBack(boolean enableSwipeBack) {
this.enableSwipeBack = enableSwipeBack;
}
/**
* Set scroll threshold, we will close the activity, when scrollPercent over
* this value
*
* @param threshold
*/
public void setScrollThreshold(float threshold) {
if (threshold >= 1.0f || threshold <= 0) {
throw new IllegalArgumentException("Threshold value should be between 0 and 1.0");
}
scrollThreshold = threshold;
}
/**
* Set up contentView which will be moved by user gesture
*
* @param view
*/
private void setContentView(View view) {
contentView = view;
}
public void attachToActivity(Activity activity) {
this.activity = activity;
TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{android.R.attr.windowBackground});
int background = a.getResourceId(0, android.R.color.black);
a.recycle();
// add the swipeLayout outermost layer
ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
final ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
scrollChildView = decorChild;
decor.post(new Runnable() {
@Override
public void run() {
findScrollChildView(decorChild);
}
});
decorChild.setBackgroundResource(background);
decor.removeView(decorChild);
this.addView(decorChild);
this.setContentView(decorChild);
decor.addView(this);
}
private void findScrollChildView(View view) {
if (view instanceof ViewGroup) {
ViewGroup parent = (ViewGroup) view;
if (parent.getChildCount() > 0) {
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
if (ViewCompat.canScrollHorizontally(child, 1) || ViewCompat.canScrollHorizontally(child, -1)) {
scrollChildView = child;
break;
}
findScrollChildView(child);
}
}
}
}
private class ViewDragHelperCallBack extends ViewDragHelper.Callback {
private boolean isScrollOverValid;
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == contentView && enableSwipeBack;
}
@Override
public int getViewHorizontalDragRange(View child) {
return horizontalDragRange;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
int result = 0;
if (!ViewCompat.canScrollHorizontally(scrollChildView, -1) && left > 0) { //left boundary
final int leftBound = getPaddingLeft();
final int rightBound = horizontalDragRange;
result = Math.min(Math.max(leftBound, left), rightBound);
}
return result;
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
scrollPercent = Math.abs((float) left / (contentView.getWidth() + leftShadow.getIntrinsicWidth()));
contentLeft = left;
contentTop = top;
invalidate();
if (scrollPercent < scrollThreshold && !isScrollOverValid) {
Utils.convertActivityToTranslucent(activity);
isScrollOverValid = true;
}
if (viewDragHelper.getViewDragState() == ViewDragHelper.STATE_DRAGGING && scrollPercent >= scrollThreshold && isScrollOverValid) {
isScrollOverValid = false;
}
if (scrollPercent >= 1) {
if (!activity.isFinishing()) {
activity.finish();
activity.overridePendingTransition(0, 0);
}
}
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
final int childWidth = releasedChild.getWidth();
int left;
int top = 0;
left = xvel > 0 || xvel == 0 && scrollPercent > scrollThreshold ? childWidth + leftShadow.getIntrinsicWidth() + OVERSCROLL_DISTANCE : 0;
viewDragHelper.settleCapturedViewAt(left, top); //If you release, it will finish or back to origin
invalidate();
}
}
}
初始化
/**
* 左滑退出activity
*/
public class SwipeBackHelper {
private Activity activity;
private LeftSwipeBackLayout swipeBackLayout;
public SwipeBackHelper(Activity activity) {
this.activity = activity;
}
public void onActivityCreate() {
activity.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
activity.getWindow().getDecorView().setBackgroundDrawable(null);
swipeBackLayout = new LeftSwipeBackLayout(activity);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
swipeBackLayout.setLayoutParams(params);
swipeBackLayout.setEnableSwipeBack(true);
}
public void onPostCreate() {
swipeBackLayout.attachToActivity(activity);
}
public View findViewById(int id) {
if (swipeBackLayout != null) {
return swipeBackLayout.findViewById(id);
}
return null;
}
public LeftSwipeBackLayout getSwipeBackLayout() {
return swipeBackLayout;
}
}
Utils
/**
* 左滑退出activity
*/
public class Utils {
private Utils() {
}
public static void convertActivityFromTranslucent(Activity activity) {
try {
Method method = Activity.class.getDeclaredMethod("convertFromTranslucent");
method.setAccessible(true);
method.invoke(activity);
} catch (Throwable t) {
}
}
public static void convertActivityToTranslucent(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
convertActivityToTranslucentAfterL(activity);
} else {
convertActivityToTranslucentBeforeL(activity);
}
}
public static void convertActivityToTranslucentBeforeL(Activity activity) {
try {
Class<?>[] classes = Activity.class.getDeclaredClasses();
Class<?> translucentConversionListenerClazz = null;
for (Class clazz : classes) {
if (clazz.getSimpleName().contains("TranslucentConversionListener")) {
translucentConversionListenerClazz = clazz;
}
}
Method method = Activity.class.getDeclaredMethod("convertToTranslucent",
translucentConversionListenerClazz);
method.setAccessible(true);
method.invoke(activity, new Object[] {
null
});
} catch (Throwable t) {
}
}
private static void convertActivityToTranslucentAfterL(Activity activity) {
try {
Method getActivityOptions = Activity.class.getDeclaredMethod("getActivityOptions");
getActivityOptions.setAccessible(true);
Object options = getActivityOptions.invoke(activity);
Class<?>[] classes = Activity.class.getDeclaredClasses();
Class<?> translucentConversionListenerClazz = null;
for (Class clazz : classes) {
if (clazz.getSimpleName().contains("TranslucentConversionListener")) {
translucentConversionListenerClazz = clazz;
}
}
Method convertToTranslucent = Activity.class.getDeclaredMethod("convertToTranslucent",
translucentConversionListenerClazz, ActivityOptions.class);
convertToTranslucent.setAccessible(true);
convertToTranslucent.invoke(activity, null, options);
} catch (Throwable t) {
}
}
}
使用方法:
private SwipeBackHelper helper;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
helper = new SwipeBackHelper(this);
helper.onActivityCreate();
}
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
helper.onPostCreate();
}
@Override
public View findViewById(int id) {
View v = super.findViewById(id);
if (v == null && helper != null)
return helper.findViewById(id);
return v;
}
需要的drawable文件
简单的滑动冲突有处理,复杂的需要自己处理,下面是我自己处理的一个例子
float x1 = 0;
float x2 = 0;
float y1 = 0;
float y2 = 0;
int move = 100;//移动距离
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN://当手指按下的时候
x1 = ev.getX();
y1 = ev.getY();
break;
case MotionEvent.ACTION_MOVE://手指移动的时候
x2 = ev.getX();
y2 = ev.getY();
float slideX = x2 - x1;
float slideY = Math.abs(y1 - y2);
if (slideX > move && slideX > slideY) {
finish();
}
break;
}
return super.dispatchTouchEvent(ev);
}