转载请标明出处:http://www.jianshu.com/p/705c2397a7f9
主要参考项目:https://github.com/ikew0ng/SwipeBackLayout
背景
首次通过向右滑动来返回的操作是在 IOS7系统上出现,android系统特性上并不支持两个activity间的滑动返回,但是android上有很多关于滑动的api,通过这些api也是可以实现视觉上的滑动返回效果。
效果图
原理的简单描述
首先设置activity的背景是透明的,然后让每个页面的DecorView下添加一个自定义的ViewGroup(SwipeBackLayout),让原先的DecorView里的子view添加到SwipeBackLayout里,通过滑动的api对SwipeBackLayout里的view进行滑动,当滑动结束后就finish当前的activity,为了实现联动,在滑动的过程中拿到下层的activity的SwipeBackLayout进行滑动操作即可。
布局图
实现
主要有以下四个类:
SwipeBackActivity //滑动返回基类
SwipeBackLayout //滑动返回viewGroup
SwipeBackLayoutDragHelper //修改ViewDragHelper后助手类
TranslucentHelper //代码中修改透明或者不透明的助手类
1. 设置activity为透明
这个看起来很简单,其实在实际开发中遇到过一个比较麻烦的页面切换动画的问题。在代码中,和activity透明背景相关的地方有两个:
- 第一个是在activity的主题style里设置
- @color/transparent
- true
但是问题来了,如果在某个activity的主题style中设置了android:windowIsTranslucent该属性为true,那么该activity切换动画变成了手机默认效果,不同手机有不同的效果,有些手机上完全不能看。于是需要自定义activity的切换动画(windowAnimationStyle),但是又发现以下几个属性是无效的
- @anim/activity_open_enter
- @anim/activity_open_exit
- @anim/activity_close_enter
- @anim/activity_close_exit
在网上搜了下,发现下面两个属性还是可以用的
- @anim/***
- @anim/***
但是这个在一个真正的项目中明显是不够的,一个是窗口进来动画,一个是窗口退出动画,因此还需要在代码中动态设置,也就有了TranslucentHelper 助手类。
- 第二个透明助手类(TranslucentHelper)里主要又有两个方法,一个是让activity变透明,一个是让activity变不透明,这两个都是通过反射来调用隐藏的系统api来实现的。
public class TranslucentHelper {
public interface TranslucentListener {
void onTranslucent();
}
private static class MyInvocationHandler implements InvocationHandler {
private TranslucentListener listener;
MyInvocationHandler(TranslucentListener listener) {
this.listener = listener;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
boolean success = (boolean) args[0];
if (success && listener != null) {
listener.onTranslucent();
}
} catch (Exception ignored) {
}
return null;
}
}
public static boolean convertActivityFromTranslucent(Activity activity) {
try {
Method method = Activity.class.getDeclaredMethod("convertFromTranslucent");
method.setAccessible(true);
method.invoke(activity);
return true;
} catch (Throwable t) {
return false;
}
}
public static void convertActivityToTranslucent(Activity activity, final TranslucentListener listener) {
try {
Class>[] classes = Activity.class.getDeclaredClasses();
Class> translucentConversionListenerClazz = null;
for (Class clazz : classes) {
if (clazz.getSimpleName().contains("TranslucentConversionListener")) {
translucentConversionListenerClazz = clazz;
}
}
MyInvocationHandler myInvocationHandler = new MyInvocationHandler(listener);
Object obj = Proxy.newProxyInstance(Activity.class.getClassLoader(),
new Class[] { translucentConversionListenerClazz }, myInvocationHandler);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Method getActivityOptions = Activity.class.getDeclaredMethod("getActivityOptions");
getActivityOptions.setAccessible(true);
Object options = getActivityOptions.invoke(activity);
Method method = Activity.class.getDeclaredMethod("convertToTranslucent",
translucentConversionListenerClazz, ActivityOptions.class);
method.setAccessible(true);
method.invoke(activity, obj, options);
} else {
Method method =
Activity.class.getDeclaredMethod("convertToTranslucent", translucentConversionListenerClazz);
method.setAccessible(true);
method.invoke(activity, obj);
}
} catch (Throwable t) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (listener != null) {
listener.onTranslucent();
}
}
}, 100);
}
}
}
让activity变不透明的方法比较简单就不多说了;让activity变透明的方法参数里出入了一个listener ,这个后面再讲,主要看调用invoke方法时,判断了版本是否是大于等于5.0,如果是,需要再传入一个ActivityOptions参数。
2. 让BaseActivity继承SwipeBackActivity
先直接看代码,比较少
public class SwipeBackActivity extends FragmentActivity {
/**
* 滑动返回ViewGroup
*/
private SwipeBackLayout mSwipeBackLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSwipeBackLayout = new SwipeBackLayout(this);
getWindow().setBackgroundDrawableResource(R.color.transparent);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
mSwipeBackLayout.attachToActivity(this);
mSwipeBackLayout.setOnSwipeBackListener(new SwipeBackLayout.onSwipeBackListener() {
@Override
public void onStart() {
onSwipeBackStart();
}
@Override
public void onEnd() {
onSwipeBackEnd();
}
});
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
getSwipeBackLayout().recovery();
}
}
/**
* 滑动返回开始时的回调
*/
protected void onSwipeBackStart() {
}
/**
* 滑动返回结束时的回调
*/
protected void onSwipeBackEnd() {
}
@Override
public View findViewById(int id) {
View v = super.findViewById(id);
if (v == null && mSwipeBackLayout != null) {
return mSwipeBackLayout.findViewById(id);
}
return v;
}
/**
* 设置是否可以边缘滑动返回,需要在onCreate方法调用
*/
public void setSwipeBackEnable(boolean enable) {
mSwipeBackLayout.setSwipeBackEnable(enable);
}
public SwipeBackLayout getSwipeBackLayout() {
return mSwipeBackLayout;
}
}
SwipeBackActivity中包含了一个SwipeBackLayout ,在onCreate方法中,new了一个SwipeBackLayout 、设置了window的背景色为透明色、主题设置为不透明。不要被这个透明搞晕了,window的背景色相当于在style中设置android:windowBackground为透明,这个也是activity透明的必要条件,由于我所开发的这个项目已经迭代了很多个版本,activity很多,而这些activity中android:windowBackground设置的颜色大部分是白色,少部分是灰色和透明的,所以需要在代码中设置统一设置一遍透明的,以达到最少修改代码的目的,如果是一个新的项目,可以直接在style中写死windowBackground为透明,这样就不要再代码中设置了。那么问题是在代码中设置了window是透明的,原来如果是灰色的视觉效果被影响到了怎么办?解决办法是获取原来的背景色赋值给原来DecorView的子view(也就是现在SwipeBackLayout的子view)就可以了。背景色赋值就是在onPostCreate方法的mSwipeBackLayout.attachToActivity(this);里做的。onPostCreate的执行时机是在onStart和onResume之间的。attachToActivity就是将SwipeBackLayout插入到DecorView和其子view之间,可以先看下代码:
/**
* 将View添加到Activity
*/
public void attachToActivity(Activity activity) {
mTopActivity = activity;
TypedArray a = activity.getTheme().obtainStyledAttributes(new int[] {
android.R.attr.windowBackground
});
int background = a.getResourceId(0, 0);
a.recycle();
ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
decorChild.setBackgroundResource(background);
decor.removeView(decorChild);
addView(decorChild);
setContentView(decorChild);
decor.addView(this);
Activity backActivity = ActivityUtils.getSecondTopActivity();
if (backActivity != null && backActivity instanceof SwipeBackActivity) {
mBackActivityWeakRf = new WeakReference<>(backActivity);
}
}
代码整体上都还是比较简单的,应该都能看懂。前面是获取window背景色,插入过程到decor.addView(this);为止,下面拿到backActivity 的引用是为了做到下层activity联动时用到的。
继续来看SwipeBackActivity,onPostCreate还设置了开始滑动和滑动结束的回调,在某些场合下还是需要的,比如一些PopupWindow在滑动返回时不会被消除,这个时候可以在onSwipeBackStart()调用其dismiss()方法。在onWindowFocusChanged中如果是hasFocus == true,就recovery()这个SwipeBackLayout,这个也是因为下层activity有联动效果而移动了SwipeBackLayout,所以需要recovery()下,防止异常情况。最后再提下setSwipeBackEnable(…),某些不可以滑动返回的页面比如MainActivity需要在其onCreate方法中调用下设置为false就可以了。
3. SwipeBackLayout和SwipeBackLayoutDragHelper
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
//绘制阴影
if (mContentPercent > 0
&& child == mContentView
&& mViewDragHelper.getViewDragState() != SwipeBackLayoutDragHelper.STATE_IDLE) {
child.getHitRect(mContentViewRect);
mShadowLeft.setBounds(mContentViewRect.left - mShadowLeft.getIntrinsicWidth(), mContentViewRect.top,
mContentViewRect.left, mContentViewRect.bottom);
//mShadowLeft.setAlpha((int) (mContentPercent * FULL_ALPHA));
mShadowLeft.draw(canvas);
}
return super.drawChild(canvas, child, drawingTime);
}
@Override
public void computeScroll() {
mContentPercent = 1 - mScrollPercent;
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
/**
* 设置是否可以滑动返回
*/
public void setSwipeBackEnable(boolean enable) {
mIsSwipeBackEnable = enable;
}
public boolean isActivityTranslucent() {
return mIsActivityTranslucent;
}
/**
* 启动进入动画
*/
private void startEnterAnim() {
if (mContentView != null) {
ObjectAnimator anim = ObjectAnimator
.ofFloat(mContentView, "TranslationX", mContentView.getTranslationX(), 0f);
anim.setDuration((long) (125 * mContentPercent));
mEnterAnim = anim;
mEnterAnim.start();
}
}
protected View getContentView() {
return mContentView;
}
private void setContentView(ViewGroup decorChild) {
mContentView = decorChild;
}
private class ViewDragCallback extends SwipeBackLayoutDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
if (mIsSwipeBackEnable && mViewDragHelper.isEdgeTouched(SwipeBackLayoutDragHelper.EDGE_LEFT, pointerId)) {
TranslucentHelper.convertActivityToTranslucent(mTopActivity,
new TranslucentHelper.TranslucentListener() {
@Override
public void onTranslucent() {
if (mListener != null) {
mListener.onStart();
}
mIsActivityTranslucent = true;
}
});
return true;
}
return false;
}
@Override
public int getViewHorizontalDragRange(View child) {
return mIsSwipeBackEnable ? SwipeBackLayoutDragHelper.EDGE_LEFT : 0;
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
if (changedView == mContentView) {
mScrollPercent = Math.abs((float) left / mContentView.getWidth());
mContentLeft = left;
//未执行动画就平移
if (!mIsEnterAnimRunning) {
moveBackActivity();
}
invalidate();
if (mScrollPercent >= 1 && !mTopActivity.isFinishing()) {
if (mBackActivityWeakRf != null && ActivityUtils.activityIsAlive(mBackActivityWeakRf.get())) {
((SwipeBackActivity) mBackActivityWeakRf.get()).getSwipeBackLayout().invalidate();
}
mTopActivity.finish();
mTopActivity.overridePendingTransition(0, 0);
}
}
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (xvel > DEFAULT_VELOCITY_THRESHOLD || mScrollPercent > DEFAULT_SCROLL_THRESHOLD){
if (mIsActivityTranslucent) {
mViewDragHelper.settleCapturedViewAt(releasedChild.getWidth() + mShadowLeft.getIntrinsicWidth(), 0);
if (mContentPercent < 0.85f) {
startAnimOfBackActivity();
}
}
} else {
mViewDragHelper.settleCapturedViewAt(0, 0);
}
if (mListener != null) {
mListener.onEnd();
}
invalidate();
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return Math.min(child.getWidth(), Math.max(left, 0));
}
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
if (state == SwipeBackLayoutDragHelper.STATE_IDLE && mScrollPercent < 1f) {
TranslucentHelper.convertActivityFromTranslucent(mTopActivity);
mIsActivityTranslucent = false;
}
}
@Override
public boolean isTranslucent() {
return SwipeBackLayout.this.isActivityTranslucent();
}
}
/**
* 背景Activity开始进入动画
*/
private void startAnimOfBackActivity() {
if (mBackActivityWeakRf != null && ActivityUtils.activityIsAlive(mBackActivityWeakRf.get())) {
mIsEnterAnimRunning = true;
SwipeBackLayout swipeBackLayout = ((SwipeBackActivity) mBackActivityWeakRf.get()).getSwipeBackLayout();
swipeBackLayout.startEnterAnim();
}
}
/**
* 移动背景Activity
*/
private void moveBackActivity() {
if (mBackActivityWeakRf != null && ActivityUtils.activityIsAlive(mBackActivityWeakRf.get())) {
View view = ((SwipeBackActivity) mBackActivityWeakRf.get()).getSwipeBackLayout().getContentView();
if (view != null) {
int width = view.getWidth();
view.setTranslationX(-width * 0.3f * Math.max(0f, mContentPercent - 0.15f));
}
}
}
/**
* 回复界面的平移到初始位置
*/
public void recovery() {
if (mEnterAnim != null && mEnterAnim.isRunning()) {
mEnterAnim.end();
} else {
mContentView.setTranslationX(0);
}
}
}
drawChild是来绘制了左侧阴影的,获取到原 子view 所在屏幕的矩形,之后确定阴影所在的矩形,然后就直接绘制了。
child.getHitRect(mContentViewRect);
mShadowLeft.setBounds(mContentViewRect.left - mShadowLeft.getIntrinsicWidth(), mContentViewRect.top,
mContentViewRect.left, mContentViewRect.bottom);
mShadowLeft.draw(canvas);
代码中滑动是用了ViewDragHelper,如果不熟悉这个类需要先自行百度。只是这个官方封装类有几个地方不能满足这个滑动返回的需求,于是就有了SwipeBackLayoutDragHelper,SwipeBackLayoutDragHelper主要是copy了ViewDragHelper源码后再添加了一些代码。
首先原生ViewDragHelper里面没有setMaxVelocity方法,如果滑动过快,会导致下层联动滑动跟不上,上层滑动结束后下层还没移动好,这导致的结果就是下下层可以被看到,下下层也许就是手机桌面,影响了体验,所以需要设置了最大速度。
我们知道ViewDragHelper需要通过shouldInterceptTouchEvent(event)和processTouchEvent(event)获取该view的onInterceptTouchEvent和onTouchEvent事件,之后设置一个回调ViewDragCallback里面写几个方法就基本上可以实现用手指拖拽了,回调中有许多的方法,其中isTranslucent()是自己添加进去的,接下来就讲讲重写回调里的方法都做了什么。
@Override
public boolean tryCaptureView(View child, int pointerId) {
if (mIsSwipeBackEnable && mViewDragHelper.isEdgeTouched(SwipeBackLayoutDragHelper.EDGE_LEFT, pointerId)) {
TranslucentHelper.convertActivityToTranslucent(mTopActivity,
new TranslucentHelper.TranslucentListener() {
@Override
public void onTranslucent() {
if (mListener != null) {
mListener.onStart();
}
mIsActivityTranslucent = true;
}
});
return true;
}
return false;
}
** tryCaptureView**方法当触摸到SwipeBackLayout里的子View时触发的,当返回true,表示捕捉成功,否则失败。判断条件是如果支持滑动返回并且是左侧边距被触摸时才可以,我们知道这个时候的的背景色是不透明的,如果直接开始滑动则是黑色的,所以需要在这里背景色改成透明的,如果直接调用 TranslucentHelper.convertActivityToTranslucent(mTopActivity)
后直接返回true,会出现一个异常情况,就是滑动过快时会导致背景还来不及变成黑色就滑动出来了,之后才变成透明的,从而导致了会从黑色到透明的一个闪烁现象,解决的办法是在代码中用了一个回调和标记,当变成透明后设置了mIsActivityTranslucent = true;
通过mIsActivityTranslucent 这个变量来判断是否进行移动的操作。由于修改activity变透明的方法是通过反射的,不能简单的设置一个接口后进行回调,而是通过动态代理的方式来实现的(InvocationHandler),在convertToTranslucent方法的第一个参数刚好是一个判断activity是否已经变成透明的回调,看下面代码中 if 语句里的注释和回调,如果窗口已经变成透明的话,就传了一个drawComplete (true)。
@SystemApi
public boolean convertToTranslucent(TranslucentConversionListener callback,
ActivityOptions options) {
boolean drawComplete;
try {
mTranslucentCallback = callback;
mChangeCanvasToTranslucent =
ActivityManagerNative.getDefault().convertToTranslucent(mToken, options);
WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, false);
drawComplete = true;
} catch (RemoteException e) {
// Make callback return as though it timed out.
mChangeCanvasToTranslucent = false;
drawComplete = false;
}
if (!mChangeCanvasToTranslucent && mTranslucentCallback != null) {
// Window is already translucent.
mTranslucentCallback.onTranslucentConversionComplete(drawComplete);
}
return mChangeCanvasToTranslucent;
}
在TranslucentHelper的convertActivityToTranslucent(…)方法中
MyInvocationHandler myInvocationHandler =
new MyInvocationHandler(new WeakReference<>(listener));
Object obj = Proxy.newProxyInstance(Activity.class.getClassLoader(),
new Class[] { translucentConversionListenerClazz }, myInvocationHandler);
通过动态代理,将translucentConversionListenerClazz 执行其方法onTranslucentConversionComplete的替换成myInvocationHandler中执行invoke方法。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
boolean success = (boolean) args[0];
if (success && listener.get() != null) {
listener.get().onTranslucent();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
其中赋值给success的args[0]正是 drawComplete
mTranslucentCallback.onTranslucentConversionComplete(drawComplete);
isTranslucent是上面提到,在SwipeBackLayoutDragHelper中的Callback回调里,自己添加了一个方法,主要是返回activity是否是透明的
public boolean isTranslucent() {
return true;
}
默认为true,在SwipeBackLayout重写后将mIsActivityTranslucent返回SwipeBackLayoutDragHelper
@Override
public boolean isTranslucent() {
return SwipeBackLayout.this.isActivityTranslucent();
}
仔细看SwipeBackLayoutDragHelper方法的话,会发现最后通过dragTo方法对view进行移动,因此在进行水平移动前判断下是否是透明的,只有透明了才能移动
private void dragTo(int left, int top, int dx, int dy) {
int clampedX = left;
int clampedY = top;
final int oldLeft = mCapturedView.getLeft();
final int oldTop = mCapturedView.getTop();
if (dx != 0) {
clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);
if (mCallback.isTranslucent()) {
ViewCompat.offsetLeftAndRight(mCapturedView, clampedX - oldLeft);
}
}
if (dy != 0) {
clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);
ViewCompat.offsetTopAndBottom(mCapturedView, clampedY - oldTop);
}
if (dx != 0 || dy != 0) {
final int clampedDx = clampedX - oldLeft;
final int clampedDy = clampedY - oldTop;
if (mCallback.isTranslucent()) {
mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY,
clampedDx, clampedDy);
}
}
}
onViewPositionChanged view移动过程中会持续调用,这里面的逻辑主要有这几个:
实时计算滑动了多少距离,用于绘制左侧阴影等
使下面的activity进行移动moveBackActivity();
当view完全移出屏幕后,销毁当前的activity
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
if (changedView == mContentView) {
mScrollPercent = Math.abs((float) left / mContentView.getWidth());
mContentLeft = left;
//未执行动画就平移
if (!mIsEnterAnimRunning) {
moveBackActivity();
}
invalidate();
if (mScrollPercent >= 1 && !mTopActivity.isFinishing()) {
if (mBackActivityWeakRf != null && ActivityUtils.activityIsAlive(mBackActivityWeakRf.get())) {
((SwipeBackActivity) mBackActivityWeakRf.get()).getSwipeBackLayout().invalidate();
}
mTopActivity.finish();
mTopActivity.overridePendingTransition(0, 0);
}
}
}
/**
* 移动背景Activity
*/
private void moveBackActivity() {
if (mBackActivityWeakRf != null && ActivityUtils.activityIsAlive(mBackActivityWeakRf.get())) {
View view = ((SwipeBackActivity) mBackActivityWeakRf.get()).getSwipeBackLayout().getContentView();
if (view != null) {
int width = view.getWidth();
view.setTranslationX(-width * 0.3f * Math.max(0f, mContentPercent - 0.15f));
}
}
}
onViewReleased是手指释放后触发的一个方法
如果滑动速度大于最大速度或者滑动的距离大于设定的阈值距离,则直接移到屏幕外,同时触发下层activity的复位动画mViewDragHelper.settleCapturedViewAt(releasedChild.getWidth() + mShadowLeft.getIntrinsicWidth(), 0);
否则移会到原来位置 mViewDragHelper.settleCapturedViewAt(0, 0);
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (xvel > DEFAULT_VELOCITY_THRESHOLD || mScrollPercent > DEFAULT_SCROLL_THRESHOLD){
if (mIsActivityTranslucent) {
mViewDragHelper.settleCapturedViewAt(releasedChild.getWidth() + mShadowLeft.getIntrinsicWidth(), 0);
if (mContentPercent < 0.85f) {
startAnimOfBackActivity();
}
}
} else {
mViewDragHelper.settleCapturedViewAt(0, 0);
}
if (mListener != null) {
mListener.onEnd();
}
invalidate();
}
/**
* 背景Activity开始进入动画
*/
private void startAnimOfBackActivity() {
if (mBackActivityWeakRf != null && ActivityUtils.activityIsAlive(mBackActivityWeakRf.get())) {
mIsEnterAnimRunning = true;
SwipeBackLayout swipeBackLayout = ((SwipeBackActivity) mBackActivityWeakRf.get()).getSwipeBackLayout();
swipeBackLayout.startEnterAnim();
}
}
/**
* 启动进入动画
*/
private void startEnterAnim() {
if (mContentView != null) {
ObjectAnimator anim = ObjectAnimator
.ofFloat(mContentView, "TranslationX", mContentView.getTranslationX(), 0f);
anim.setDuration((long) (125 * mContentPercent));
mEnterAnim = anim;
mEnterAnim.start();
}
}
onViewDragStateChanged当滑动的状态发生改变时的回调
主要是停止滑动后,将背景改成不透明,这样跳到别的页面是动画就是正常的。
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
if (state == SwipeBackLayoutDragHelper.STATE_IDLE && mScrollPercent < 1f) {
TranslucentHelper.convertActivityFromTranslucent(mTopActivity);
mIsActivityTranslucent = false;
}
}
clampViewPositionHorizontal 返回水平移动距离,下面这样写可以防止滑出父 view
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return Math.min(child.getWidth(), Math.max(left, 0));
}
getViewHorizontalDragRange对于clickable=true的子view,需要返回大于0的数字才能正常捕获。
@Override
public int getViewHorizontalDragRange(View child) {
return mIsSwipeBackEnable ? SwipeBackLayoutDragHelper.EDGE_LEFT : 0;
}