考拉Android全局滑动返回及联动效果的实现

做者:许方镇java

前言

首次经过右滑来返回到上一个页面的操做是在 IOS7上出现。到目前android应用上支持这种操做的依然很少。分析其主要缘由应该是android已有实体的返回按键,这样的功能变得不重要,但我以为有这样的功能便于单手操做,能提高app的用户体验,特别是从ios转到android的用户。写这篇博文但愿能够对你们有所帮助,但愿本身的app上有滑动返回功能的能够参考下。android

原理的简单描述

Android系统里有不少滑动相关的API和类,好比ViewDragHelper就是一个很好的滑动助手类。首先设置Window的背景为透明,再经过ViewDragHelper对Activity上DecorView的子view进行滑动,当滑动到必定距离,手指离开后就自动滑到最右侧,而后finish当前的activity,这样便可实现滑动返回效果。为了可以 “全局的”、“联动的” 实现滑动返回效果,在每一个activity的DecorView下插入了SwipeBackLayout,当前activity滑动和下层activity的联动都在该类中完成。ios

效果图

下层联动滑动返回

布局图

Hierarchy View

实现主要类:

SwipeBackActivity //滑动返回基类
SwipeBackLayout //滑动返回布局类
SwipeBackLayoutDragHelper //修改ViewDragHelper后助手类
TranslucentHelper //代码中修改透明或者不透明的助手类
canvas

##代码层面的讲解api

一. 设置activity为透明、activity跳转动画(TranslucentHelper 讲解)

这个看起来很简单,但若是要兼容到API16及如下,会遇到过一个比较麻烦的页面切换动画问题:bash

1.一、经过activity的主题style进行设置

<item name="android:windowBackground">@color/transparent</item>
<item name="android:windowIsTranslucent">true</item>```

**遇到问题:**若是在某个activity的主题style中设置了android:windowIsTranslucent属性为true,那么该activity切换动画与没设置以前是不一样的,有些手机切换动画会变得很是跳。因此须要自定义activity的切换动画。
接下来咱们会想到经过主题style里的windowAnimationStyle来设置切换动画
复制代码

@anim/activity_open_enter @anim/activity_open_exit @anim/activity_close_enter @anim/activity_close_exit``` **实践证实:**当android:windowIsTranslucent为true时,以上几个属性是无效的,而下面两个属性仍是能够用。可是这两个属性一个是窗口进来动画,一个是窗口退出动画,明显是不够。app

<item name="android:windowEnterAnimation">@anim/***</item>
<item name="android:windowExitAnimation">@anim/***</item>```

结合overridePendingTransition(int enterAnim, int exitAnim)能够复写窗口进来动画和窗口退出动画,这种我以为最终多是能够实现的,不过控制起来比较复杂:
好比有A、B、C三个页面:
A跳到B,进场页面B动画从右进来,出场页面A动画从左出去,能够直接在style中写死
复制代码

@anim/*** @anim/***```ide

若是B返回到A,进场页面A动画从左进来,出场页面B动画从右出去,此时须要经过复写onBackPressed() 方法,
在其中添加overridePendingTransition(int enterAnim, int exitAnim)方法来改变更画。布局

若是B是finish()后到A页面,在finish()后面加上overridePendingTransition ……
因为onBackPressed() 方法最终会调finish(),因此实际上只须要复写finish(),在其中添加overridePendingTransition……post

可是假如B finish()后跳到C,则又不该该执行overridePendingTransition……,那么就须要判断finish执行后是否要加 overridePendingTransition……
对于一个较为庞大的项目,采起这种方法须要对每一个页面进行排查,所以是不可行的,而对于刚刚起步的应用来讲则是一个选择。

1.二、经过透明助手类(TranslucentHelper)进行设置

透明助手类(TranslucentHelper)里主要又有两个方法,一个是让activity变不透明,一个是让activity变透明,这两个都是经过反射来调用隐藏的系统api来实现的。由于较低的版本不支持代码中修改背景透明不透明,因此在类中有个静态变量mTranslucentState 来记录是否能够切换背景,这样低版本就不须要每次都反射经过捕获到的异常来作兼容方案。 另外:发现有些手机支持背景变黑,但不支持背景变透明(中兴z9 mini 5.0.2系统)

public class TranslucentHelper {
    private static final String TRANSLUCENT_STATE = "translucentState";
    private static final int INIT = 0;//表示初始
    private static final int CHANGE_STATE_FAIL = INIT + 1;//表示确认不能够切换透明状态
    private static final int CHANGE_STATE_SUCCEED = CHANGE_STATE_FAIL + 1;//表示确承认以切换透明状态
    private static int mTranslucentState = INIT;

    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;
        }
    }

    static boolean convertActivityFromTranslucent(Activity activity) {
        if (mTranslucentState == INIT) {
            mTranslucentState = PreferencesUtils.getInt(TRANSLUCENT_STATE, INIT);
        }
        if (mTranslucentState == INIT) {
            convertActivityToTranslucent(activity, null);
        } else if (mTranslucentState == CHANGE_STATE_FAIL) {
            return false;
        }

        try {
            Method method = Activity.class.getDeclaredMethod("convertFromTranslucent");
            method.setAccessible(true);
            method.invoke(activity);
            mTranslucentState = CHANGE_STATE_SUCCEED;
            return true;
        } catch (Throwable t) {
            mTranslucentState = CHANGE_STATE_FAIL;
            PreferencesUtils.saveInt(TRANSLUCENT_STATE, CHANGE_STATE_FAIL);
            return false;
        }
    }

    static void convertActivityToTranslucent(Activity activity, final TranslucentListener listener) {
        if (mTranslucentState == CHANGE_STATE_FAIL) {
            if (listener != null) {
                listener.onTranslucent();
            }
            return;
        }

        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);
            }
            mTranslucentState = CHANGE_STATE_SUCCEED;
        } catch (Throwable t) {
            mTranslucentState = CHANGE_STATE_FAIL;
            PreferencesUtils.saveInt(TRANSLUCENT_STATE, CHANGE_STATE_FAIL);
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (listener != null) {
                        listener.onTranslucent();
                    }
                }
            }, 100);
        }
    }
}
复制代码

让activity变不透明的方法比较简单;让activity变透明的方法参数里传入了一个listener接口 ,主要是当antivity变透明后会回调,由于这个接口也在activity里,并且是私有的,因此咱们只能经过动态代理去获取这个回调。最后若是版本大于等于5.0,还须要再传入一个ActivityOptions参数。

在实际开发中,这两个方法在android 5.0以上是有效的,在5.0如下须要当android:windowIsTranslucent为true时才有效,这样又回到了以前的问题activity切换动画异常。

**最终决解方法:**setContentView以前就调用 convertActivityFromTranslucent方法,让activity背景变黑,这样activity切换效果就正常。

**总结:**在style中设置android:windowIsTranslucent为true ,setContentView以前就调用 convertActivityFromTranslucent方法,当触发右滑时调用convertActivityToTranslucent,经过动态代理获取activity变透明后的回调,在回调后容许开始滑动。

二. 让BaseActivity继承SwipeBackActivity(SwipeBackActivity讲解)

先直接看代码,比较少

public abstract class SwipeBackActivity extends CoreBaseActivity {
    /** * 滑动返回View */
    private SwipeBackLayout mSwipeBackLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (!isSwipeBackDisableForever()) {
            TranslucentHelper.convertActivityFromTranslucent(this);
            mSwipeBackLayout = new SwipeBackLayout(this);
        }
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        if (!isSwipeBackDisableForever()) {
            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 (!isSwipeBackDisableForever() && hasFocus) {
            getSwipeBackLayout().recovery();
        }
    }

    /** * 滑动返回开始时的回调 */
    protected void onSwipeBackStart() {}

    /** * 滑动返回结束时的回调 */
    protected void onSwipeBackEnd() {}

    /** * 设置是否能够边缘滑动返回,须要在onCreate方法调用 */
    public void setSwipeBackEnable(boolean enable) {
        if (mSwipeBackLayout != null) {
            mSwipeBackLayout.setSwipeBackEnable(enable);
        }
    }

    public boolean isSwipeBackDisableForever() {
        return false;
    }

    public SwipeBackLayout getSwipeBackLayout() {
        return mSwipeBackLayout;
    }
}
复制代码

SwipeBackActivity中包含了一个SwipeBackLayout对象

在onCreate方法中:
1.activity转化为不透明。
2.new了一个SwipeBackLayout。

在onPostCreate方法中:
1.attachToActivity主要是插入SwipeBackLayout、窗口背景设置……
2.设置了滑动返回开始和结束的监听接口,建议在滑动返回开始时,把PopupWindow给dismiss掉。

onWindowFocusChanged 方法中
若是是hasFocus == true,就recovery()这个SwipeBackLayout,这个也是由于下层activity有联动效果而移动了SwipeBackLayout,因此须要recovery()下,防止异常状况。

isSwipeBackDisableForever 方法是一个大开关,默认返回false,在代码中复写后返回 true,则至关于直接继承了SwipeBackActivity的父类。

setSwipeBackEnable 方法是一个小开关,设置了false以后就暂时不能滑动返回了,能够在特定的时机设置为true,就恢复滑动返回的功能。

**总结说明:**下层activity设置了setSwipeBackEnable 为 false,上层activity滑动时仍是能够联动的,好比MainActivity。而isSwipeBackDisableForever 返回true就不会联动了,并且一些仿PopupWindow的activity须要复写这个方法,由于activity须要透明。

3、滑动助手类的使用和滑动返回布局类的实现(SwipeBackLayout讲解)

直接贴SwipeBackLayout源码:

/** * 滑动返回容器类 */
public class SwipeBackLayout extends FrameLayout {
    /** * 滑动销毁距离比例界限,滑动部分的比例超过这个就销毁 */
    private static final float DEFAULT_SCROLL_THRESHOLD = 0.5f;
    /** * 滑动销毁速度界限,超过这个速度就销毁 */
    private static final float DEFAULT_VELOCITY_THRESHOLD = ScreenUtils.dpToPx(250);
    /** * 最小滑动速度 */
    private static final int MIN_FLING_VELOCITY = ScreenUtils.dpToPx(200);
    /** * 左边移动的像素值 */
    private int mContentLeft;
    /** * 左边移动的像素值 / (ContentView的宽+阴影) */
    private float mScrollPercent;
    /** * (ContentView可见部分+阴影)的比例 (即1 - mScrollPercent) */
    private float mContentPercent;
    /** * 阴影图 */
    private Drawable mShadowDrawable;
    /** * 阴影图的宽 */
    private int mShadowWidth;
    /** * 内容view,DecorView的原第一个子view */
    private View mContentView;
    /** * 用于记录ContentView所在的矩形 */
    private Rect mContentViewRect = new Rect();
    /** * 设置是否可滑动 */
    private boolean mIsSwipeBackEnable = true;
    /** * 是否正在放置 */
    private boolean mIsLayout = true;
    /** * 判断背景Activity是否启动进入动画 */
    private boolean mIsEnterAnimRunning = false;
    /** * 是不是透明的 */
    private boolean mIsActivityTranslucent = false;
    /** * 进入动画(只在释放手指时使用) */
    private ObjectAnimator mEnterAnim;
    /** * 退拽助手类 */
    private SwipeBackLayoutDragHelper mViewDragHelper;
    /** * 执行滑动时的最顶层Activity */
    private Activity mTopActivity;
    /** * 后面的Activity的弱引用 */
    private WeakReference<Activity> mBackActivityWeakRf;
    /** * 监听滑动开始和结束 */
    private onSwipeBackListener mListener;

    public SwipeBackLayout(Context context) {
        super(context);
        init(context);
    }

    public SwipeBackLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public SwipeBackLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        mViewDragHelper = SwipeBackLayoutDragHelper.create(SwipeBackLayout.this, new ViewDragCallback());
        mViewDragHelper.setEdgeTrackingEnabled(SwipeBackLayoutDragHelper.EDGE_LEFT);
        mViewDragHelper.setMinVelocity(MIN_FLING_VELOCITY);
        mViewDragHelper.setMaxVelocity(MIN_FLING_VELOCITY * 2);
        try {
            mShadowDrawable = context.getResources().getDrawable(R.drawable.swipeback_shadow_left);
        } catch (Exception ignored) {
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        try {
            if (!mIsSwipeBackEnable) {
                super.onLayout(changed, left, top, right, bottom);
                return;
            }
            mIsLayout = true;
            if (mContentView != null) {
                mContentView.layout(mContentLeft, top, mContentLeft + mContentView.getMeasuredWidth(),
                        mContentView.getMeasuredHeight());
            }
            mIsLayout = false;
        } catch (Exception e) {
            super.onLayout(changed, left, top, right, bottom);
        }
    }

    @Override
    public void requestLayout() {
        if (!mIsLayout || !mIsSwipeBackEnable) {
            super.requestLayout();
        }
    }

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        try {
            //绘制阴影
            if (mContentPercent > 0
                    && mShadowDrawable != null
                    && child == mContentView
                    && mViewDragHelper.getViewDragState() != SwipeBackLayoutDragHelper.STATE_IDLE) {
                child.getHitRect(mContentViewRect);
                mShadowWidth = mShadowDrawable.getIntrinsicWidth();
                mShadowDrawable.setBounds(mContentViewRect.left - mShadowWidth, mContentViewRect.top,
                        mContentViewRect.left, mContentViewRect.bottom);
                mShadowDrawable.draw(canvas);
            }
            return super.drawChild(canvas, child, drawingTime);
        } catch (Exception e) {
            return super.drawChild(canvas, child, drawingTime);
        }
    }

    @Override
    public void computeScroll() {
        mContentPercent = 1 - mScrollPercent;
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (!mIsSwipeBackEnable) {
            return false;
        }
        try {
            return mViewDragHelper.shouldInterceptTouchEvent(event);
        } catch (ArrayIndexOutOfBoundsException e) {
            return super.onInterceptTouchEvent(event);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!mIsSwipeBackEnable) {
            return false;
        }
        try {
            mViewDragHelper.processTouchEvent(event);
            return true;
        } catch (Exception e) {
            return super.onTouchEvent(event);
        }
    }

    /** * 将View添加到Activity */
   public void attachToActivity(Activity activity) {
        //插入SwipeBackLayout
        ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
        ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
        decor.removeView(decorChild);
        if (getParent() != null) {
            decor.removeView(this);
        }
        decor.addView(this);
        this.removeAllViews();
        this.addView(decorChild);

        //mContentView为SwipeBackLayout的直接子view,获取window背景色进行赋值
        activity.getWindow().setBackgroundDrawableResource(R.color.transparent_white);
        TypedArray a = activity.getTheme().obtainStyledAttributes(new int[] {
                android.R.attr.windowBackground
        });
        mContentView = decorChild;
        mContentView.setBackgroundResource(a.getResourceId(0, 0));
        a.recycle();

        //拿到顶层activity和下层activity,作联动操做
        mTopActivity = activity;
        Activity backActivity = ActivityUtils.getSecondTopActivity();
        if (backActivity != null && backActivity instanceof SwipeBackActivity) {
            if (!((SwipeBackActivity) backActivity).isSwipeBackDisableForever()) {
                mBackActivityWeakRf = new WeakReference<>(backActivity);
            }
        }
    }

    /** * 设置是否能够滑动返回 */
    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 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() + mShadowWidth, 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);
        }
    }

    interface onSwipeBackListener {
        void onStart();

        void onEnd();
    }

    public void setOnSwipeBackListener(onSwipeBackListener listener) {
        mListener = listener;
    }
}
复制代码
  • attachToActivity
    上面讲到SwipeBackLayout是在activity的onCreate时被建立,在onPostCreate是插入到DecorView里,主要是由于DecorView是在setContentView时与Window关联起来插入SwipeBackLayout方法如代码所示,不难,而后是设置了window的背景色为透明色,mContentView为SwipeBackLayout的直接子view,获取window背景色进行赋值。因为考拉项目已经有不少activity,而这些activity中android:windowBackground设置的颜色大部分是白色,少部分是灰色和透明的,因此须要在代码中设置统一设置一遍透明的,原来的背景色则赋值给SwipeBackLayout的子View就能够达到最少修改代码的目的。最后拿到顶层activity和下层activity,作联动操做

  • SwipeBackLayoutDragHelper 和 ViewDragCallback
    SwipeBackLayout中包含了一个滑动助手类(SwipeBackLayoutDragHelper)的对象,该类是在ViewDragHelper的基础上进行修改得来的。
    修改点:
    1.右侧触发区域 EDGE_SIZE由 20dp 改到 25dp
    2.提供滑动最大速度 的设置方法
    3.在ViewDragHelper 的内部类Callback方法中提供是否activity为透明的回调接口
    4.在最终调用滑动的方法dragTo中添加判断逻辑,activity为透明时才支持滑动
    SwipeBackLayoutDragHelper 在init 方法中初始化,经过onInterceptTouchEvent和onTouchEvent拿到滑动事件,经过ViewDragCallback的一些方法返回相应的滑动回调, ViewDragCallback实现了SwipeBackLayoutDragHelper.Callback里的如下几个接口,其中其中isTranslucent()是本身添加进去的。

    ViewDragCallback

  • tryCaptureView方法当触摸到SwipeBackLayout里的子View时触发的,当返回true,表示捕捉成功,不然失败。判断条件是若是支持滑动返回而且是左侧边距被触摸时才能够,咱们知道这个时候的的背景色是不透明的,若是直接开始滑动则是黑色的,因此须要在这里背景色改为透明的,若是直接调用 TranslucentHelper.convertActivityToTranslucent(mTopActivity, null)后直接返回true,会出现一个异常状况,就是滑动过快时会致使背景还来不及变成黑色就滑动出来了,以后才变成透明的,从而致使了会从黑色到透明的一个闪烁现象,解决的办法是在代码中用了一个回调和标记,当变成透明后设置了mIsActivityTranslucent = true;经过mIsActivityTranslucent 这个变量来判断是否进行移动的操做。因为修改activity变透明的方法是经过反射的,不能简单的设置一个接口后进行回调,而是经过动态代理的方式来实现的(InvocationHandler),在convertToTranslucent方法的第一个参数恰好是一个判断activity是否已经变成透明的回调,看下面代码中 if 语句里的注释和回调,若是窗口已经变成透明的话,就传了一个drawComplete (true)。经过动态代理,将translucentConversionListenerClazz 执行其方法onTranslucentConversionComplete的替换成myInvocationHandler中执行invoke方法。其中赋值给success的args[0]正是 drawComplete。

  • isTranslucent是本身添加了一个方法,主要是返回activity是不是透明的默认为true,在SwipeBackLayout重写后将mIsActivityTranslucent返回。仔细看SwipeBackLayoutDragHelper方法的话,会发现最后经过dragTo方法对view进行移动,所以在进行水平移动前判断下是不是透明的,只有透明了才能移动

  • onViewPositionChanged view移动过程当中会持续调用,这里面的逻辑主要有这几个: 1.实时计算滑动了多少距离,用于绘制左侧阴影等
    2.使下面的activity进行移动moveBackActivity();
    3.当view彻底移出屏幕后,销毁当前的activity

  • onViewReleased是手指释放后触发的一个方法。若是滑动速度大于最大速度或者滑动的距离大于设定的阈值距离,则直接移到屏幕外,同时触发下层activity的复位动画,不然移会到原来位置 。

  • onViewDragStateChanged当滑动的状态发生改变时的回调,主要是中止滑动后,将背景改为不透明,这样跳到别的页面是动画就是正常的。

  • clampViewPositionHorizontal 返回水平移动距离,防止滑出父 view。

  • getViewHorizontalDragRange对于clickable=true的子view,须要返回大于0的数字才能正常捕获。

其余方法都较为简单,注释也写了,就很少说了,最后绝不吝啬的贴上SwipeBackLayoutDragHelper的dragTo代码,就多了if (mCallback.isTranslucent())

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);
            }
        }
    }

复制代码
相关文章
相关标签/搜索