android 全局頁面滑動返回聯(lián)動效果的實現(xiàn)

轉(zhuǎn)載請標(biāo)明出處:http://www.reibang.com/p/705c2397a7f9
主要參考項目:https://github.com/ikew0ng/SwipeBackLayout

背景

首次通過向右滑動來返回的操作是在 IOS7系統(tǒng)上出現(xiàn)水泉,android系統(tǒng)特性上并不支持兩個activity間的滑動返回,但是android上有很多關(guān)于滑動的api窒盐,通過這些api也是可以實現(xiàn)視覺上的滑動返回效果草则。

效果圖

下層聯(lián)動滑動返回

原理的簡單描述

首先設(shè)置activity的背景是透明的,然后讓每個頁面的DecorView下添加一個自定義的ViewGroup(SwipeBackLayout)蟹漓,讓原先的DecorView里的子view添加到SwipeBackLayout里炕横,通過滑動的api對SwipeBackLayout里的view進行滑動,當(dāng)滑動結(jié)束后就finish當(dāng)前的activity葡粒,為了實現(xiàn)聯(lián)動份殿,在滑動的過程中拿到下層的activity的SwipeBackLayout進行滑動操作即可。

布局圖

Hierarchy View

實現(xiàn)

主要有以下四個類:
SwipeBackActivity //滑動返回基類
SwipeBackLayout //滑動返回viewGroup
SwipeBackLayoutDragHelper //修改ViewDragHelper后助手類
TranslucentHelper //代碼中修改透明或者不透明的助手類

1. 設(shè)置activity為透明

這個看起來很簡單嗽交,其實在實際開發(fā)中遇到過一個比較麻煩的頁面切換動畫的問題卿嘲。在代碼中,和activity透明背景相關(guān)的地方有兩個:

  • 第一個是在activity的主題style里設(shè)置
<item name="android:windowBackground">@color/transparent</item> 
<item name="android:windowIsTranslucent">true</item>

但是問題來了夫壁,如果在某個activity的主題style中設(shè)置了android:windowIsTranslucent該屬性為true拾枣,那么該activity切換動畫變成了手機默認效果,不同手機有不同的效果,有些手機上完全不能看梅肤。于是需要自定義activity的切換動畫(windowAnimationStyle)司蔬,但是又發(fā)現(xiàn)以下幾個屬性是無效的

<!-- activity 新創(chuàng)建時進來的動畫-->
<item name="android:activityOpenEnterAnimation">@anim/activity_open_enter</item>
<!-- 上層activity返回后,下層activity重新出現(xiàn)的動畫-->
<item name="android:activityOpenExitAnimation">@anim/activity_open_exit</item>
<!-- 跳到新的activity后姨蝴,該activity被隱藏時的動畫-->
<item name="android:activityCloseEnterAnimation">@anim/activity_close_enter</item>
<!-- activity 銷毀時的動畫-->
<item name="android:activityCloseExitAnimation">@anim/activity_close_exit</item> 

在網(wǎng)上搜了下俊啼,發(fā)現(xiàn)下面兩個屬性還是可以用的

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

但是這個在一個真正的項目中明顯是不夠的,一個是窗口進來動畫似扔,一個是窗口退出動畫吨些,因此還需要在代碼中動態(tài)設(shè)置,也就有了TranslucentHelper 助手類炒辉。

  • 第二個透明助手類(TranslucentHelper)里主要又有兩個方法豪墅,一個是讓activity變透明,一個是讓activity變不透明黔寇,這兩個都是通過反射來調(diào)用隱藏的系統(tǒng)api來實現(xiàn)的偶器。
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變透明的方法參數(shù)里出入了一個listener 缝裤,這個后面再講屏轰,主要看調(diào)用invoke方法時,判斷了版本是否是大于等于5.0憋飞,如果是霎苗,需要再傳入一個ActivityOptions參數(shù)。

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

    /**
     * 滑動返回開始時的回調(diào)
     */
    protected void onSwipeBackStart() {

    }

    /**
     * 滑動返回結(jié)束時的回調(diào)
     */
    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;
    }

    /**
     * 設(shè)置是否可以邊緣滑動返回唁盏,需要在onCreate方法調(diào)用
     */
    public void setSwipeBackEnable(boolean enable) {
        mSwipeBackLayout.setSwipeBackEnable(enable);
    }

    public SwipeBackLayout getSwipeBackLayout() {
        return mSwipeBackLayout;
    }
}

SwipeBackActivity中包含了一個SwipeBackLayout ,在onCreate方法中检眯,new了一個SwipeBackLayout 厘擂、設(shè)置了window的背景色為透明色、主題設(shè)置為不透明锰瘸。不要被這個透明搞暈了刽严,window的背景色相當(dāng)于在style中設(shè)置android:windowBackground為透明,這個也是activity透明的必要條件避凝,由于我所開發(fā)的這個項目已經(jīng)迭代了很多個版本舞萄,activity很多,而這些activity中android:windowBackground設(shè)置的顏色大部分是白色管削,少部分是灰色和透明的鹏氧,所以需要在代碼中設(shè)置統(tǒng)一設(shè)置一遍透明的,以達到最少修改代碼的目的佩谣,如果是一個新的項目把还,可以直接在style中寫死windowBackground為透明,這樣就不要再代碼中設(shè)置了。那么問題是在代碼中設(shè)置了window是透明的吊履,原來如果是灰色的視覺效果被影響到了怎么辦安皱?解決辦法是獲取原來的背景色賦值給原來DecorView的子view(也就是現(xiàn)在SwipeBackLayout的子view)就可以了。背景色賦值就是在onPostCreate方法的mSwipeBackLayout.attachToActivity(this);里做的艇炎。onPostCreate的執(zhí)行時機是在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);
    }
}

代碼整體上都還是比較簡單的缀踪,應(yīng)該都能看懂居砖。前面是獲取window背景色,插入過程到decor.addView(this);為止驴娃,下面拿到backActivity 的引用是為了做到下層activity聯(lián)動時用到的奏候。

繼續(xù)來看SwipeBackActivity,onPostCreate還設(shè)置了開始滑動和滑動結(jié)束的回調(diào)唇敞,在某些場合下還是需要的蔗草,比如一些PopupWindow在滑動返回時不會被消除,這個時候可以在onSwipeBackStart()調(diào)用其dismiss()方法疆柔。在onWindowFocusChanged中如果是hasFocus == true咒精,就recovery()這個SwipeBackLayout,這個也是因為下層activity有聯(lián)動效果而移動了SwipeBackLayout旷档,所以需要recovery()下模叙,防止異常情況。最后再提下setSwipeBackEnable(…)鞋屈,某些不可以滑動返回的頁面比如MainActivity需要在其onCreate方法中調(diào)用下設(shè)置為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);
        }
    }

    /**
     * 設(shè)置是否可以滑動返回
     */
    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;
                //未執(zhí)行動畫就平移
                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));
            }
        }
    }

    /**
     * 回復(fù)界面的平移到初始位置
     */
    public void recovery() {
        if (mEnterAnim != null && mEnterAnim.isRunning()) {
            mEnterAnim.end();
        } else {
            mContentView.setTranslationX(0);
        }
    }

}

drawChild是來繪制了左側(cè)陰影的,獲取到原 子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方法盗迟,如果滑動過快坤邪,會導(dǎo)致下層聯(lián)動滑動跟不上,上層滑動結(jié)束后下層還沒移動好罚缕,這導(dǎo)致的結(jié)果就是下下層可以被看到艇纺,下下層也許就是手機桌面,影響了體驗,所以需要設(shè)置了最大速度黔衡。

我們知道ViewDragHelper需要通過shouldInterceptTouchEvent(event)和processTouchEvent(event)獲取該view的onInterceptTouchEvent和onTouchEvent事件蚓聘,之后設(shè)置一個回調(diào)ViewDragCallback里面寫幾個方法就基本上可以實現(xiàn)用手指拖拽了,回調(diào)中有許多的方法盟劫,其中isTranslucent()是自己添加進去的夜牡,接下來就講講重寫回調(diào)里的方法都做了什么。

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

** tryCaptureView**方法當(dāng)觸摸到SwipeBackLayout里的子View時觸發(fā)的侣签,當(dāng)返回true塘装,表示捕捉成功,否則失敗影所。判斷條件是如果支持滑動返回并且是左側(cè)邊距被觸摸時才可以蹦肴,我們知道這個時候的的背景色是不透明的,如果直接開始滑動則是黑色的型檀,所以需要在這里背景色改成透明的冗尤,如果直接調(diào)用 TranslucentHelper.convertActivityToTranslucent(mTopActivity)后直接返回true,會出現(xiàn)一個異常情況胀溺,就是滑動過快時會導(dǎo)致背景還來不及變成黑色就滑動出來了裂七,之后才變成透明的,從而導(dǎo)致了會從黑色到透明的一個閃爍現(xiàn)象仓坞,解決的辦法是在代碼中用了一個回調(diào)和標(biāo)記背零,當(dāng)變成透明后設(shè)置了mIsActivityTranslucent = true;通過mIsActivityTranslucent 這個變量來判斷是否進行移動的操作。由于修改activity變透明的方法是通過反射的无埃,不能簡單的設(shè)置一個接口后進行回調(diào)徙瓶,而是通過動態(tài)代理的方式來實現(xiàn)的(InvocationHandler),在convertToTranslucent方法的第一個參數(shù)剛好是一個判斷activity是否已經(jīng)變成透明的回調(diào)嫉称,看下面代碼中 if 語句里的注釋和回調(diào)侦镇,如果窗口已經(jīng)變成透明的話,就傳了一個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);

通過動態(tài)代理壳繁,將translucentConversionListenerClazz 執(zhí)行其方法onTranslucentConversionComplete的替換成myInvocationHandler中執(zhí)行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回調(diào)里闹炉,自己添加了一個方法,主要是返回activity是否是透明的

public boolean isTranslucent() {
    return true;
}

默認為true润樱,在SwipeBackLayout重寫后將mIsActivityTranslucent返回SwipeBackLayoutDragHelper

@Override
public boolean isTranslucent() {
    return SwipeBackLayout.this.isActivityTranslucent();
}

仔細看SwipeBackLayoutDragHelper方法的話渣触,會發(fā)現(xiàn)最后通過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移動過程中會持續(xù)調(diào)用嗅钻,這里面的邏輯主要有這幾個:
實時計算滑動了多少距離皂冰,用于繪制左側(cè)陰影等
使下面的activity進行移動moveBackActivity();
當(dāng)view完全移出屏幕后,銷毀當(dāng)前的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;
        //未執(zhí)行動畫就平移
        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是手指釋放后觸發(fā)的一個方法
如果滑動速度大于最大速度或者滑動的距離大于設(shè)定的閾值距離啊犬,則直接移到屏幕外灼擂,同時觸發(fā)下層activity的復(fù)位動畫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當(dāng)滑動的狀態(tài)發(fā)生改變時的回調(diào)
主要是停止滑動后,將背景改成不透明,這樣跳到別的頁面是動畫就是正常的。

@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的數(shù)字才能正常捕獲峻贮。

@Override
public int getViewHorizontalDragRange(View child) {
    return mIsSwipeBackEnable ? SwipeBackLayoutDragHelper.EDGE_LEFT : 0;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市应闯,隨后出現(xiàn)的幾起案子纤控,更是在濱河造成了極大的恐慌,老刑警劉巖碉纺,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件船万,死亡現(xiàn)場離奇詭異,居然都是意外死亡骨田,警方通過查閱死者的電腦和手機耿导,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來态贤,“玉大人舱呻,你說我怎么就攤上這事∮破” “怎么了箱吕?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長柿冲。 經(jīng)常有香客問我茬高,道長,這世上最難降的妖魔是什么假抄? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任怎栽,我火速辦了婚禮,結(jié)果婚禮上慨亲,老公的妹妹穿的比我還像新娘。我一直安慰自己宝鼓,他們只是感情好刑棵,可當(dāng)我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著愚铡,像睡著了一般蛉签。 火紅的嫁衣襯著肌膚如雪胡陪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天碍舍,我揣著相機與錄音柠座,去河邊找鬼。 笑死片橡,一個胖子當(dāng)著我的面吹牛妈经,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捧书,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼吹泡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了经瓷?” 一聲冷哼從身側(cè)響起爆哑,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎舆吮,沒想到半個月后揭朝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡色冀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年潭袱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呐伞。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡敌卓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出伶氢,到底是詐尸還是另有隱情趟径,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布癣防,位于F島的核電站蜗巧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蕾盯。R本人自食惡果不足惜幕屹,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望级遭。 院中可真熱鬧望拖,春花似錦、人聲如沸挫鸽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丢郊。三九已至盔沫,卻和暖如春医咨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背架诞。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工拟淮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人谴忧。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓很泊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親俏蛮。 傳聞我的和親對象是個殘疾皇子撑蚌,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容