1. 程式人生 > >Android版與微信Activity側滑後退效果完全相同的SwipeBackLayout

Android版與微信Activity側滑後退效果完全相同的SwipeBackLayout

本文緣起

因為我做的app裡使用了SwipeBackHelper的開源庫來實現Activity的側滑後退,本來使用起來一直沒什麼問題,但在新版本中接入了騰訊x5核心的WebView後就出現了一個小問題。看下圖:


圖1
圖2

圖2中兩條黑線之間就是圖1中所展示的視訊播放的區域,但圖2中顯示的不是視訊內容,而是當前的WebActivity下層的MainActivity的部分檢視。因為當進入網頁播放頁面點選視訊播放按鈕後,視訊播放區域會突然變成透明的,直到視訊加載出來之後才會開始顯示視訊內容,該過程持續1秒到數秒不等。本來如果只是閃現一下就消失也沒什麼大問題,但有的網頁中的視訊載入過慢,導致這個透明現象出現的時間過長,所以app運營渠道提出需要解決該問題。

問題分析

經測試,該問題出現是因為滿足了兩個條件:
1.Activity的主題style中滿足屬性:<item name="android:windowIsTranslucent">true</item> (這也是使用SwipeBackHelper的必要條件);
2.使用x5核心的WebView播放視訊。
對於我們的專案來說,x5是不能放棄的,但側滑退出的效果在三個版本之前就加入了,現在要針對某些頁面去掉,也讓我覺得很不爽。此時當然是參考微信的效果嘍,結果微信給我的結果是這樣的:


微信x5核心WebView播放視訊效果

微信同樣是使用x5核心,同樣具有側滑退出得效果,當播放相同視訊時,本該顯示透明的區域卻顯示的是黑色的背景。微信究竟是如何解決的呢?
我嘗試了給WebView增加背景色,給WebView增加父容器後再增加背景色,給Activity的Window和DecorView設定背景色,但沒有作用。只要Activity的主題style中設定了窗體透明,該問題無論如何都會出現。

問題解決

無奈之下,我嘗試解決這個問題,雖然說是個小問題,著實花了一番功夫。下面我會從三個方面來說明我在尋求解決方案的過程中學習和總結到的一些東西。因為這個問題遇到的人不多,而且我只是在SwipeBackHelper的原始碼基礎上做了一些修改,所以就不上傳程式碼到github了,但我會詳細說明我修改的過程和原理,相信讀完本文,你會對SwipeBackHelper的工作原理有更多地瞭解,也會了解到通過反編譯成熟apk尋找解決方案的學習方法。

其實我搜索了很久找其他實現側滑後退的方案,但發現不管什麼方案,設定<item name="android:windowIsTranslucent">true</item>這一條件都被宣告為必要條件,否則就會出現側滑時出現下層背景為黑的bug。所以最終我只有閱讀一下原始碼來看看側滑後退的原理究竟是什麼。大家搜尋時會發現github上有一個star數量更多的相關專案

SwipeBackLayout,我看了兩個專案各自的程式碼,從github分支推送的時間來看,SwipeBackLayout是最先出現的。兩者的程式碼80%的程式碼是相似的,SwipeBackHelper只是在SwipeBackLayout的基礎上對其中的主要控制元件進行了解耦,提取出來了一個SwipeBackHelper和SwipeBackPage兩個管理類,使用法更加清晰明瞭,同時實現了當前Activity側滑關閉時與下層Activity的聯動效果,跟微信已經99%相似了(是的,我要解決的就是那1%的問題)。因為我專案用的是SwipeBackHelper專案,所以我也是在它的原始碼基礎上進行修改的。


SwipeBackHelper原始碼檔案

原始碼並不複雜,具體用法我就不解釋了,專案github上說得很詳細。我簡單說下每個類的主要功能:

  1. SwipeBackLayout,是一個繼承自FrameLayout的ViewGroup,我們側滑後退時滑動的就是這個ViewGroup,需要側滑的Activity執行onCreate時,需要設定setSwipeBackEnable(true),這句程式碼執行時會呼叫SwipeBackLayout的attachToActivity,如下所示,該方法會找到Activity的Window介面的最頂層View,即DecorView,並找到DecorView的直接子view將它替換為SwipeBackLayout,同時將原來的子view新增到SwipeBackLayout中。這樣一來,SwipeBackLayout就會在Activity的所有佈局(我們自己寫得xml所生成的佈局)之上了),當我們滑動Activity時,如果是在側邊(一般是螢幕左側)可以觸發側滑後退動作的區域內,SwipeBackLayout就會攔截觸控事件,自己進行處理,執行被拖動或滑動退出的UI效果;

    public void attachToActivity(Activity activity) {
         if (getParent() != null) {
             return;
         }
         mActivity = 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();
         View decorChild = decor.findViewById(android.R.id.content);
         while (decorChild.getParent() != decor) {
             decorChild = (View) decorChild.getParent();
         }
         decorChild.setBackgroundResource(background);
         decor.removeView(decorChild);
         addView(decorChild);
         setContentView(decorChild);
         decor.addView(this);
     }
  2. ViewDragHelper,實現滑動和拖動的輔助類,其實就是在Android原生的ViewDragHelper上進行了小小的修改,ViewDragHelper是一個非常強大的類,簡單的呼叫就可以幫我們實現View的滑動和拖動效果,SwipeBackLayout的onInterceptTouchEvent和onTouchEvent的處理都是交給ViewDragHelper來做的,所以要深入理解側滑的實現機制,需要知道ViewDragHelper是如何工作的,感興趣的同學可以直接讀下面兩篇部落格,讀完應該就理解得差不多了:
    Android ViewDragHelper完全解析 自定義ViewGroup神器
    Android ViewDragHelper原始碼解析

  3. SwipeBackPage,每個滑動頁面的管理類,該類持有當前Activity、與Activity關聯的SwipeBackLayout和一個RelateSlider的引用,並提供一系列鏈式呼叫的方法設定SwipeBackLayout的相關屬性;

  4. SwipeBackHelper,滑動的全域性管理類,也是提供給我們在Activity中開啟側滑退出功能的工具類。在Activity的onCreate中呼叫SwipeBackHelper的onCreate方法時,其內部會建立一個與該Activity關聯的SwipeBackPage,並通過一個Stack集合記錄管理所有關聯過Activity的SwipeBackPage,需要下層Activity聯動時就可以通過該類的getPrePage獲取到下層Activity相關聯的SwipeBackPage類;

    private static final Stack<SwipeBackPage> mPageStack = new Stack<>();
    ……
    
     public static void onCreate(Activity activity) {
         SwipeBackPage page;
         if ((page = findHelperByActivity(activity)) == null){
             page = mPageStack.push(new SwipeBackPage(activity));
         }
         page.onCreate();
     }
  5. SwipeListener,簡單的介面,提供了觸控和滑動SwipeBackLayout時的三個回撥方法;

  6. RelateSlider,有下層Activity聯動時需要用到的一個類,它實現了SwipeListener介面,在上層Activity的SwipeBackLayout被滑動時,會回撥到它實現的onScroll和onScrollToClose方法,從而實現下層Activity的SwipeBackLayout位置的改變,達到聯動的效果。

  7. Utils 最不起眼的一個類,在這個專案中都沒用到好伐。不過正是這個類,才是我解決問題的關鍵,這個類的原始碼不太對,後面我會貼出修改後的程式碼。

二. 反編譯微信apk尋找靈感

雖然瞭解了SwipeBackHelper的實現原理,但剛開始我還是想不通微信是如何處理我開頭提出的問題。我Google了大半天都找不出有人有類似的問題,索性直接反編譯微信apk,看看能不能找到一些端倪,沒想到,還真被我找到了。


微信的SwipeBackLayout

在反編譯後的java程式碼中,我找到了一個SwipeBackLayout的類,很明顯,微信側滑後退的實現方式跟上面開源庫的差不多,只不過人家自己做了整合和優化。我一眼看到"convertToTranslucent",就知道這個肯定跟處理透明問題有關,後來我才發現原來同時出現在SwipeBackHelperSwipeBackLayout專案中的Utils中寫的正是反射呼叫Activity的"convertToTranslucent"方法,而且在SwipeBackLayout中的Utils是被使用過的,使用時機是在SwipeBackLayout的onEdgeTouch回掉中,也就是在側滑動作觸發之前。而這個"convertToTranslucent"方法的作用正是讓不透明的Activity轉為透明。
5.0及其以上版本的Activity中的convertToTranslucent方法:

  /**
     * Convert a translucent themed Activity {@link android.R.attr#windowIsTranslucent} back from
     * opaque to translucent following a call to {@link #convertFromTranslucent()}.
     * <p>
     * Calling this allows the Activity behind this one to be seen again. Once all such Activities
     * have been redrawn {@link TranslucentConversionListener#onTranslucentConversionComplete} will
     * be called indicating that it is safe to make this activity translucent again. Until
     * {@link TranslucentConversionListener#onTranslucentConversionComplete} is called the image
     * behind the frontmost Activity will be indeterminate.
     * <p>
     * This call has no effect on non-translucent activities or on activities with the
     * {@link android.R.attr#windowIsFloating} attribute.
     *
     * @param callback the method to call when all visible Activities behind this one have been
     * drawn and it is safe to make this Activity translucent again.
     * @param options activity options delivered to the activity below this one. The options
     * are retrieved using {@link #getActivityOptions}.
     * @return <code>true</code> if Window was opaque and will become translucent or
     * <code>false</code> if window was translucent and no change needed to be made.
     *
     * @see #convertFromTranslucent()
     * @see TranslucentConversionListener
     *
     * @hide
     */
    @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;
    }

5.0以下版本的Activity中的convertToTranslucent方法:

/**
     * Convert a translucent themed Activity {@link android.R.attr#windowIsTranslucent} to a
     * fullscreen opaque Activity.
     * <p>
     * Call this whenever the background of a translucent Activity has changed to become opaque.
     * Doing so will allow the {@link android.view.Surface} of the Activity behind to be released.
     * <p>
     * This call has no effect on non-translucent activities or on activities with the
     * {@link android.R.attr#windowIsFloating} attribute.
     *
     * @see #convertToTranslucent(android.app.Activity.TranslucentConversionListener,
     * ActivityOptions)
     * @see TranslucentConversionListener
     *
     * @hide
     */
    @SystemApi
    public void convertFromTranslucent() {
        try {
            mTranslucentCallback = null;
            if (ActivityManagerNative.getDefault().convertFromTranslucent(mToken)) {
                WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, true);
            }
        } catch (RemoteException e) {
            // pass
        }
    }

既然如此,那麼我將我的WebActivity主題的android:windowIsTranslucent設定為false,然後在側滑被觸發之前呼叫convertToTranslucent不就好了。
事實證明的確是可以的,但有兩個明顯不好的地方在於:

  1. 反射呼叫convertToTranslucent方法會使相關聯的Activity重繪,測試發現這個過程需要100ms的時間,所以如果側滑動作很快,就會出現黑邊閃現,體驗不太好;
    2.如果側滑動作進行一半,使用者又滑回去了選擇暫時不關閉Activity,其實Activity已經轉換成透明瞭,再播放視訊的話透明現象還會出現。對於這個問題,我本來覺得可以在它滑回的時候呼叫Utils中的convertActivityFromTranslucent再將Activity轉為不透明,但測試發現,這樣反轉一下後,視訊播放區域就直接全黑了,再也不出現視訊內容了。

對於問題2,我在微信上進行了嘗試,不得不說我機智地發現微信並沒有處理這種情況:



上圖中視訊區域顯示的是下層Activity的內容(我的聊天視窗)。
一方面這個問題確實難以解決,另一方面使用者進行問題2所述操作的概率並不會很高,所以這種問題暫時就參考微信,不去解決了。
真正讓我鬱悶的還是問題1,看到微信怎麼滑都不會有黑邊的效果,我還是決定嘗試將它徹底解決。

三. 解決問題的終極姿勢

快速滑動出現黑邊問題的根本原因是convertToTranslucent是需要100ms左右的時間的,而且這個事件不固定跟手機的硬體配置有關,所以思路是先等待convertToTranslucent成功的回撥,然後再觸發Activity的側滑。

 /**
     * Calling the convertToTranslucent method on platforms after Android 5.0
     */
    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) {
        }
    }

然而呼叫Activity的convertToTranslucent方法本來就是通過反射的方式,無法直接傳入回撥介面。這樣一來只有通過動態代理的方式了。我的這個想法在我重新看微信反編譯程式碼時得到了印證:


微信也是通過動態代理獲取convertToTranslucent成功的回撥

首先在Utils中增加一個繼承自InvocationHandler的類:

    public interface PageTranslucentListener {
        void onPageTranslucent();
    }

    static class MyInvocationHandler implements InvocationHandler {
        private static final String TAG = "MyInvocationHandler";
        private WeakReference<PageTranslucentListener> listener;

        public MyInvocationHandler(WeakReference<PageTranslucentListener> listener) {
            this.listener = listener;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Log.d(TAG, "invoke: end time: " + System.currentTimeMillis());
            Log.d(TAG, "invoke: 被回調了");
            try {
                boolean success = (boolean) args[0];
                if (success && listener.get() != null) {
                    listener.get().onPageTranslucent();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }

然後改造一下原來的convertActivityToTranslucentAfterL方法,convertActivityToTranslucentBeforeL同理:

    private static void convertActivityToTranslucentAfterL(Activity activity, PageTranslucentListener listener) {
        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;
                }
            }


            MyInvocationHandler myInvocationHandler = new MyInvocationHandler(new WeakReference<PageTranslucentListener>(listener));
            Object obj = Proxy.newProxyInstance(Activity.class.getClassLoader(), new Class[]{translucentConversionListenerClazz}, myInvocationHandler);

            Method convertToTranslucent = Activity.class.getDeclaredMethod("convertToTranslucent",
                    translucentConversionListenerClazz, ActivityOptions.class);
            convertToTranslucent.setAccessible(true);
            Log.d("MyInvocationHandler", "start time: " + System.currentTimeMillis());
            convertToTranslucent.invoke(activity, obj, options);
        } catch (Throwable t) {
        }
    }

原來呼叫convertToTranslucent的時機是在onEdgeTouch回撥中,但這樣會導致只要觸控到螢幕左側就會執行convertToTranslucent而且觸控事件會不止一次回撥。所以這裡呼叫時機改到ViewDragHelper.Callback的onEdgeDragStarted回撥中,只有當SwipeBackLayout開始動了才呼叫,並且只會呼叫一次:

        @Override
        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
            super.onEdgeDragStarted(edgeFlags, pointerId);
            Log.d("translucentTest", "onEdgeDragStarted");
            Utils.convertActivityToTranslucent(mActivity, new Utils.PageTranslucentListener() {
                @Override
                public void onPageTranslucent() {
                    setPageTranslucent(true);
                    Log.d("translucentTest", "onPageTranslucent: ");
                }
            });
        }

SwipeBackLayout中增加下面的成員pageTranslucent和兩個方法以作設定和標識,pageTranslucent預設值為true:

    private boolean pageTranslucent = true;

    public void setPageTranslucent(boolean pageTranslucent) {
        this.pageTranslucent = pageTranslucent;
    }

    public boolean isPageTranslucent() {
        return pageTranslucent;
    }

有了上述標識,我們就可以知道當前的Activity是否是透明的。
有兩個地方需要處理:

  1. 在手指嘗試滑動SwipeBackLayout時,判斷pageTranslucent是否為true,為true才允許被滑動。而通過分析ViewDragHelper的原始碼可知,它的dragTo()方法是唯一觸發拖動行為的方法。所以在dragTo()方法中加入如下兩處判斷:

     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);
             Log.d("translucentTest", "dragTo: mCallback.isPageTranslucent()-->" + mCallback.isPageTranslucent());
             //增加是否透明的判斷
             if (mCallback.isPageTranslucent()) {
                 mCapturedView.offsetLeftAndRight(clampedX - oldLeft);
             }
         }
         if (dy != 0) {
             clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);
             mCapturedView.offsetTopAndBottom(clampedY - oldTop);
         }
    
         if (dx != 0 || dy != 0) {
             final int clampedDx = clampedX - oldLeft;
             final int clampedDy = clampedY - oldTop;
             //增加是否透明的判斷
             if (mCallback.isPageTranslucent()) {
                 mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY, clampedDx, clampedDy);
             }
         }
     }

    在Callback中增加回調方法isPageTranslucent()並在SwipeBackLayout中如下實現即可:

         public boolean isPageTranslucent() {
             return SwipeBackLayout.this.isPageTranslucent();
         }

2.在手指鬆開時,會回撥CallBack的onViewReleased()方法,SwipeBackLayout實現了此方法,判斷滑回左邊還是滑到最右邊關閉Activity:

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            final int childWidth = releasedChild.getWidth();

            int left = 0, top = 0;
            //判斷釋放以後是應該滑到最右邊(關閉),還是最左邊(還原)
            left = xvel > 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? childWidth
                    + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE : 0;

            // settleCapturedViewAt中呼叫了ViewDragHelper內部mScroller的startScroll()方法,然後通過invalidate重新整理就可以觸發SwipeBackLayout的自行滾動
            mDragHelper.settleCapturedViewAt(left, top);
            invalidate();
        }

所以在這裡還是要判斷一下,如果當前Activity不透明,那麼手指鬆開後也不進行滑動。
但改完這裡測試時發現了一個問題,就是低於21版本的手機執行convertActivityToTranslucentBeforeL()方法時怎麼也不起作用,經過一番折騰我找到了原因。原來我一直忽略了Activity的convertToTranslucent方法的真正用法,關於這個方法Activity原始碼中有註釋說明,高低版本中均有提到:

Convert a translucent themed Activity {@link android.R.attr#windowIsTranslucent} back from opaque to translucent following a call to {@link #convertFromTranslucent()}.
……
This call has no effect on non-translucent activities or on activities with the {@link android.R.attr#windowIsFloating} attribute.

意思是說該方法的作用是,在Activity被convertFromTranslucent方法轉為不透明之後,將其再從不透明轉為透明。而且該方法對本來不透明的Activity是沒有作用的。所以我們只有在本身就為透明的Activity中呼叫convertFromTranslucent將其轉為不透明之後才可以通過convertToTranslucent方法將其再轉為透明。
雖說如此,但api21以上的手機確實是可以直接將本身主題不透明的Activity轉為透明的,21一下的就不行。所以為了相容,我還是統一將Activity的主題設定為透明,而針對還有web頁面的Activity,再它的onCreate方法中先呼叫convertFromTranslucent轉為不透明,設定其SwipeBackLayout的pageTranslucent為false,再在側滑開始時呼叫convertToTranslucent將其轉為透明.

        //在Activity的onCreate中做如下設定
        //將Activity轉為不透明,設定成功,則pageTranslucent為false,否則為true
        boolean opaque = Utils.convertActivityFromTranslucent(this); 
        SwipeBackHelper.onCreate(this);
        SwipeBackHelper.getCurrentPage(this)
                .setSwipeBackEnable(true)
                .setPageTranslucent(!opaque);

Utils中的convertActivityFromTranslucent我也做了點改動:

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

鏈式呼叫中的setPageTranslucent(!opaque)方法是我新增在SwipeBackPage類中的:

public void setPageTranslucent(boolean pageTranslucent) {
    mSwipeBackLayout.setPageTranslucent(pageTranslucent);
}

還有一點可能有人會注意到,就是既然呼叫convertToTranslucent後到接受到回撥需要100ms的時間(如果本身是透明,又呼叫convertToTranslucent,只需要2ms),那麼如果我快速的側滑,在100ms之前就鬆開手指了,豈不是側滑無法響應了,這樣就會出現慢速地話可以滑動,快速滑不能滑動的情況。還有,如果convertToTranslucent出現異常了,pageTranslucent始終為false,豈不是也滑不動了。
確實,這兩個問題也著實讓我頭疼了兩個小時。最終我找到了一個取巧的方式解決了,更巧的事,我發現微信也是這樣整的。先看我的程式碼:

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            final int childWidth = releasedChild.getWidth();

            int left = 0, top = 0;
            //判斷釋放以後是應該滑到最右邊(關閉),還是最左邊(還原)
            left = xvel > 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? childWidth
                    + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE : 0;

            if (isPageTranslucent()) {
                // 當前page背景是透明時,釋放手指後才可以滑動
                mDragHelper.settleCapturedViewAt(left, top);
                invalidate();
            } else {
                if (left > 0 && !mActivity.isFinishing()) {
                    mActivity.finish();
                    mActivity.overridePendingTransition(0, R.anim.slide_out_right);
                }
            }
        }

R.anim.slide_out_right的xml程式碼:

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="250"
    android:fromXDelta="0"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:toXDelta="100%p" />

為什麼說取巧呢,因為我這裡用Activity退出的動畫以假亂真模擬了側滑退出的效果。那憑什麼說微信也是用這種方式呢,請看我的證據:


微信web介面側滑退出的兩種效果

這兩張圖,左邊的是慢速滑動時的效果,右邊是快速滑動時的效果。相信大家已經看出不一致的地方了,那就是滑動層左側的陰影。側滑時是上層Activity的SwipeBackLayout不停改變座標平移產生的效果,而陰影是在SwipeBackLayout不停重繪的過程中畫上去的:

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        
            
           

相關推薦

AndroidActivity後退效果完全相同SwipeBackLayout

本文緣起 因為我做的app裡使用了SwipeBackHelper的開源庫來實現Activity的側滑後退,本來使用起來一直沒什麼問題,但在新版本中接入了騰訊x5核心的WebView後就出現了一個小問題。看下圖: 圖1 圖2 圖2中兩條黑線之間就是圖1中所展

仿退出Activity

用蘋果的小夥伴們都會知道,ios系統退出當前的頁面只需要向右邊側滑,這是一個非常好的使用者體驗,但是Android官方並沒有出具有這有性質的Activity,所以我們只能辛苦辛苦,自己動手寫一個具有這種性質的Activity。 其實寫這個自定義的Activit

Android使用ToolBar+DrawerLayout+NavigationView實現抽屜效果

ets 代碼 ret r+ content custom port 單選 常見 學會使用DrawerLayout 學會使用NavigationView 學會使用ToolBar+DrawerLayout+NavigationView實現側滑抽屜效果 學會實現Toolbar

實現Activity返回效果

先看一張效果圖 通過ViewDragHelper來檢測到螢幕側滑,然後通過內建介面傳遞給Acitivity觸發了側滑事件,通知其關閉。 1、實現側滑刪除,這裡的方法是先要建立一個監聽側滑的自定義佈局. public class SwipeBackLayout exten

android手把手教你實現QQ選單效果

側滑選單功能非常常見,借鑑學習了之後,自己總結記錄一下,下面實現一種最簡單的側滑選單,後面再修改程式碼實現不同的側滑選單效果 首先是第一種效果 第一種效果是繼承ViewGroup,需要我們自己來測量、滑動處理等。 一、首先講解一下思路: 1、繼承GroupView重

android高仿表情輸入鍵盤輸入詳解-解決跳閃表情切換問題

private void unlockContentHeightDelayed() { mEditText.postDelayed(new Runnable() { @Override public void run() { ((LinearLa

Android授權登入分享全解析

前言 在移動網際網路浪潮中,聯網APP已經把單機拍死在沙灘上,很多公司都希望自家應用能夠有一套帳號系統,可是許多使用者卻並不一定買賬:我憑啥註冊你家應用的帳號?微博,微信,QQ幾乎成了每個人手機中的必裝應用,於是微信,微博,QQ說了:來來來,你們都可以用我家的

[android]手把手通過一個類實現退出activity功能

1、概述 *本文程式碼 非原創 來自於 一個 叫做NBAPlus的開原始碼中.https://github.com/SilenceDut/NBAPlus 有興趣的小夥伴們可以好好去了解下,推薦下,真™可以的~ 2、簡單描述下,所謂側滑退出功

破解開通號碼過濾軟件

  破解版開通微信號碼過濾軟件,相信很多人做微商,開口就會說我朋友圈人太少,根本做不起來。還沒有開始,就已經否定了自己其實不是有了人脈才能做很多事情,而是做了很多事情才會有人脈!很多人把順序都搞反了,一直等著貴人來幫助自己成就人生。如果做微商朋友,朋友圈沒人,雄霸微信檢測助手可以幫你!不僅可以檢測對方手機號或

酷客多小程序新版發布:卡券、小票打印等無縫對接

微信小程序 酷客多小程序 酷客多小程序商城系統更新了!酷客多小程序商城系統更新了!酷客多小程序商城系統更新了!重要的事情說3遍!這一次我們對原有版本做了全方位體驗優化,從UI UE等方面對商家後臺進行了體驗式改善,使商家後臺變得更加美觀和易用。除此之外,還推出了“微信優惠券”和“小票打印”兩個重要功能

Android調用登陸、分享、支付

cep from thum 請求 mil 問題 start 返回 ear 前言:用了微信sdk各種痛苦,感覺比qq sdk調用麻煩多了,回調過於麻煩,還必須要在指定包名下的actvity進行回調,所以我在這裏寫一篇博客,有這個需求的朋友可以借鑒一下,以後自己別的項目有用到也

php 判斷手機端

min aso text plain nokia pla com mobile r12 1、判斷手機端 function isMobile() { // 如果有HTTP_X_WAP_PROFILE則一定是移動設備 if (isset($_SERVER[‘HTTP_

Android 超高仿圖片選擇器 圖片該這麽載入

主界面 asto 布局 sage comm equal ear 博文 細致 轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/39943731,本文出自:【張鴻洋的博客】1、概述關於手機圖片載入器,在當今

Android封裝類似的頂部TitleBar彈出的PopupWindow代碼

彈出 調用 pro con and switch nload csdn 實例化 Android仿微信頂部titlebar,點擊加號彈出的PopupWindow,是封裝好的PopupWindow,直接拿來用即可,先看效果圖: 調用代碼非常簡單,這是MainActivity的

樹莓派公眾號對接(python)

需要 lan upd alt cnblogs col spa render 80端口 一 內網穿透,讓外網可以訪問樹莓派 二 樹莓派對接微信 需要安裝webpy和python-lxml git clonegit://github.com/webpy/webpy.git

談談GoogleH5牛牛的Java開發規範

基本類 cli 多好 而且 spring final關鍵字 永久 zab rop 多年前,Google發布微信H5牛牛搭建平臺(h5.fanshubbs.com)來定義Java編碼時應遵循的微信牛牛Q_1687054422規範;今年年初阿裏則發布阿裏巴巴Java 開發手冊,

Ubuntu 16.04安裝Wine(deepin-wechat)

ges ctu hub post ubun archlinux window 多好 轉發 說明: 1、使用的Wine版本是深度出品(Deepin),已經精簡了很多沒用的配置,使啟動能非常快,占用資源小。 2、關於沒有.wine文件夾的解決方法:在命令行上運行winecf

web仿聊天界面|h5仿電腦端案例開發

... http gen length false www. wrap 繼續 node 前幾天開發了一款手機端h5仿微信聊天,人唯有不停學習才能進步,這段時間倒騰著整理了下之前項目,又重新在原先的那版基礎上開發了一款仿微信聊天電腦端web版本,聊天頁面又重新優化了多圖預覽、

android app使用登錄接口回調沒有被執行的問題研究

The 應用平臺 cat 分享 github theme 調用 runnable handler 本人開發的一個app使用了sharesdk集成微信登錄功能,在測試的過程中微信授權登錄界面有調用,但是授權後原應用的回調沒有被執行 應用的包名是com.kimi.searche

C# 小程序 互為加解密方案

password dst hexstring else malformed write all ref prototype CryptoJS下載地址: https://code.google.com/archive/p/crypto-js/downloads http://