Android側滑踩坑記(仿IOS側滑finish頁面基于Slidr庫)

抓住人生中的一分一秒,勝過虛度中的一月一年!

背景

用過蘋果手機的都知道,蘋果沒有物理返回鍵,原生自帶側滑回退頁面api,手勢操控起來很方便,但是Android去實現較為困難,現微信、今日頭條等app各自都實現了側滑返回,于是也去研究了下如何實現,目前GitHub上有很多開源的框架,有更好的輪子那必須用輪子了,但實現還是需要注意一些東西事項,下面給大家講解下如何正確去實現側滑回退功能

有很多類似的開源框架 暫舉五個
  1. SwipeBackLayout
  2. Slidr
  3. Snake
  4. and_swipeback
  5. ParallaxBackLayout
先看一個效果圖
cehua.gif
原理分析

側滑看似頂層Activity整體向右移動,然而并不是這樣的,android不支持倆個頁面聯動效果,所以我們得想方設法在一個View中看到低層布局,和頂層布局倆個畫面,才能去做這種效果,實現方案有倆種

不透明方案

在頂層Activity的DecorView中插入一個Layout。監聽側滑事件,移動頂層Activity的ContentView同時,在該Layout的onDraw中調用View.draw(Canvas canvas)繪制下層ActivityContentView。造成側滑透視到下層Activity的假象。
存在問題:當布局變化或數據更新,如橫豎屏切換、導航欄隱藏、窗口模式、分屏模式等,該假象始終如一不會有對應改變

透明方案

設置頂層Activity透明

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

然后監聽側滑事件,移動頂層Activity的ContentView,即可真正透視到下層Activity的界面。此時無論布局變化、數據更新,都沒問題。BUT!該方案問題多如牛毛。。。
存在問題:windowIsTranslucenttrue會引起一系列的動畫問題,如前后臺切換動畫、Activity回退動畫等。網上有解決方案說設置"android:windowEnterAnimation""android:windowExitAnimation",經測試并無卵用。同時,在SDK26(Android8.0)及以上,會與固定屏幕方向沖突造成閃退。同時,下層的Activity只會進入onPause狀態,不會onStop,等等問題

下面來說明下本人如何去實現了這個效果,以第二個Slidr來演示如何實現,有能力的朋友可以自己寫一個側滑功能,用其他框架遇到下述類似情況可以借簽處理方案
1、引入第三方庫
 implementation 'com.r0adkll:slidableactivity:2.0.6'
2、在BaseActivity的onCreate中初始化一下就可以了
 protected void initSlidable() {
        SlidrConfig config = new SlidrConfig.Builder()
                .edge(false)//true 代表邊界   false全屏觸摸
                .build();
        slidrInterface = Slidr.attach(this, config);
    }
3、在AppTheme中加入支持透明屬性
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>

如此簡單就實現了...,是不是很簡單,先別高興太早了,這只是第一步,
SDK26(Android8.0)及以上頁面同時設置了android:screenOrientation="portrait"和透明屬性,運行會出現Only fullscreen opaque activities can request orientation異常,大概意思為“只有不透明的全屏activity可以自主設置界面方向”,這樣說明Android8.0以上透明屬性和強制豎屏倆個只能取其一?現在的APP無特殊需求根本沒必要需要橫豎屏切換,只有豎屏效果,這該怎么辦?后來經過很長時間嘗試并終于解決了此問題

4、初探fullUser來實現強制豎屏(fullUser功能:允許使用用戶的任意方向 。自動旋轉打開:四個方向 。自動旋轉關閉:不旋轉)

一般情況下強制豎屏我們都會這樣寫

 <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait" />

或者

  setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);//設置豎屏模式

其實我們設置android:screenOrientation="fullUser",具體詳細大家可以自行百度下
所以我們將portrait替換成fullUser解決了上邊遺留下來的問題奔潰問題,SDK26(Android8.0)不再崩潰報異常,但是經過機型測試,偶然間發現小米一款紅米手機強制豎屏效果沒適配,任然可以橫豎屏切換,那。。。,此方案就此作廢

5、最終behind來實現強制豎屏(behind功能:與Activity堆下的Activity方向相同 。自動旋轉打開:四個方向 。自動旋轉關閉:不旋轉)

behind此屬性說白了講是說與上個頁面屏幕旋轉方向相同,這樣我們便可以邏輯轉換去思考下,第一個Activity設置強制豎屏,第二個頁面設置跟隨第一個屏幕方向屬性behind,首頁面MainActivity,登陸頁LoginActivity,閃屏頁SplashActivity都不需要實現側滑,我們只給它們設置強制豎屏"portrait",不設置透明屬性,因為這幾個頁面不需要側滑功能,這樣便可避免了倆者共存,經過多方面測試,確實可以這樣,暫時沒發現問題

6、一些根本不需要實現側滑finish的頁面不設置透明屬性android:windowIsTranslucent

4.1也講了,再詳細說一下,比如LoginActivityMainActivity等打開App第一個顯示的頁面其實沒必要具有側滑功能,上述我們是在全局主題AppTheme加的支持透明屬性android:windowIsTranslucent">true
所以應當修改為需要側滑的頁面增加該透明屬性,不需要側滑的頁面不設置android:windowIsTranslucent透明屬性,于是乎需要倆個主題

//主題屬性  全局狀態  Application中加入
 <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <!--<item name="colorPrimary">@color/colorTop</item>-->

        <item name="android:windowAnimationStyle">@style/activityAnimation</item>
    </style>
//不需要側滑頁面增加該theme
<style name="AppTheme.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>
//需要側滑頁面增加該theme
    <style name="AppTheme.NoActionBar.Slidable" parent="AppTheme.NoActionBar">
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
    </style>

示例如下,讓大家更好理解

 <application
        android:name=".App"
        android:allowBackup="true"
        android:icon="@mipmap/logo"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/logo"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

 //不需要實現側滑的頁面
        <activity
            android:name=".ui.activity.MainActivity"
            android:screenOrientation="portrait"
            android:theme="@style/AppTheme.NoActionBar" />
 //需要實現側滑的頁面
 <activity
            android:name=".ui.login.ForgetPwdActivity"
            android:screenOrientation="behind"
            android:theme="@style/AppTheme.NoActionBar.Slidable" />

最后一步,每個第三方庫一般都會擴展開放是否支持側滑finish頁面接口,我們將不需要側滑頁面設置成true
比如MainActivity中重寫BaseActivityinitSlidable方法,禁止初始化側滑屬性

 @Override
    protected void initSlidable() {
        // 禁止滑動返回
    }
7、側滑狀態欄跟隨側滑頁面一起移動

這回運行完美,能正常使用,豎屏效果和透明效果Android版本已兼容了,但是還會發現有點怪的地方是狀態欄,側滑后頁面變了,狀態欄還有那么一橫條,太難看了,想到了設置統一的一個透明或者灰色,但是還是難看,如何能夠做到側滑狀態欄跟隨側滑頁面一起移動呢?

思路:狀態欄可以設置顏色,也可以設置透明隱藏
所以不就簡單了?,將狀態欄隱藏掉,頁面布局整體頂到狀態欄上,頂部給一個狀態欄高度padding,為了版本兼容問題,api小于19不支持沉浸式,所以可以判斷版本>=19給整體頁面一個paddingTop=狀態欄高度
不就實現了側滑狀態欄跟隨側滑頁面一起移動?

 /**
     * 獲取狀態欄高度
     *
     * @param context context
     * @return 狀態欄高度
     */
    public static int getStatusBarHeight(Context context) {
        // 獲得狀態欄高度
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        return context.getResources().getDimensionPixelSize(resourceId);
    }
 /**
     * 為布局文件中新增的狀態欄布局設置背景色和高度
     */
    public static void setStatusViewAttr(View view, Activity activity) {
        if (view == null || activity == null) {
            return;
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
            layoutParams.height = StatusBarUtil.getStatusBarHeight(activity);
            view.setLayoutParams(layoutParams);
        }
    }

    /**
     * 增加View的paddingTop,增加的值為狀態欄高度 (智能判斷,并設置高度)
     */
    public static void setPaddingSmart(Context context, View view) {
        if (Build.VERSION.SDK_INT >= 19) {
            ViewGroup.LayoutParams lp = view.getLayoutParams();
            if (lp != null && lp.height > 0) {
                lp.height += getStatusBarHeight(context);//增高
            }
            view.setPadding(view.getPaddingLeft(), view.getPaddingTop() + getStatusBarHeight(context),
                    view.getPaddingRight(), view.getPaddingBottom());
        }
    }

運行一下便是上邊的gif圖片,想實現的朋友可以嘗試一下,但是還遺留下幾個問題,但不影響整體效果

問題1:設置的跳轉頁面動畫效果不起作用

在整體AppTheme中設置<item name="android:windowAnimationStyle">@style/activityAnimation</item>
比如,左進右出

<!--頁面打開關閉動畫-->
    <style name="activityAnimation" parent="@android:style/Animation">
        <!-- 新的Activity啟動時Enter動畫 -->
        <item name="android:activityOpenEnterAnimation">@anim/right_in</item>
        <!-- 新的Activity啟動時原有Activity的Exit動畫 -->
        <item name="android:activityOpenExitAnimation">@anim/left_out</item>
        <!-- 新的Activity退出時原有ActivityEnter動畫 -->
        <item name="android:activityCloseEnterAnimation">@anim/left_in</item>
        <!-- 新的Activity退出時Exit動畫 -->
        <item name="android:activityCloseExitAnimation">@anim/right_out</item>


        <item name="android:taskOpenEnterAnimation">@anim/right_in</item>
        <item name="android:taskOpenExitAnimation">@anim/left_out</item>
        <item name="android:taskCloseEnterAnimation">@anim/left_in</item>
        <item name="android:taskCloseExitAnimation">@anim/right_out</item>
        <item name="android:taskToFrontEnterAnimation">@anim/right_in</item>
        <item name="android:taskToFrontExitAnimation">@anim/left_out</item>
        <item name="android:taskToBackEnterAnimation">@anim/left_in</item>
        <item name="android:taskToBackExitAnimation">@anim/right_out</item>

    </style>

解決:可以更改一種實現動畫方式

@Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
        }
        return super.onCreateView(name, context, attrs);
    }

    @Override
    public void finish() {
        super.finish();
        overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
    }
問題2:有很多言論說onStop()不執行?

由于被設置了<item name="android:windowIsTranslucent">true</item>Activity無法進入onStop()生命周期,所以導致ActivityWindow無法回收,所以在多個Activity疊加時會出現明顯的卡頓現象,目前并沒有特別好的解決辦法。
但是本人打印了下日志不管側滑返回,物理鍵返回,onStop都有日志,大家可以測試下,本文章再繼續完善

問題3:最初選用android:screenOrientation="fullUser"來實現固定方向,但是暫時發現小米手機不適配

最終采用behind來實現固定頁面方向,behind此屬性說白了講是說與上個頁面屏幕旋轉方向相同,這樣我們便可以邏輯轉換去思考下,第一個Activity設置強制豎屏,第二個頁面設置跟隨第一個屏幕方向屬性behind,首頁面MainActivity,登陸頁LoginActivity,閃屏頁SplashActivity都不需要實現側滑,我們只給它們設置強制豎屏"portrait",不設置透明屬性,因為這幾個頁面不需要側滑功能,這樣便可避免了倆者共存,經過多方面測試,確實可以這樣,暫時沒發現問題

問題4:后續繼續補充

最后補充

現側滑已應用到我的項目中,持續踩坑中,本所有優化是基于 Slidr庫所操作,其他開源庫有類似問題可以借簽上述處理,有能力的朋友可以自己寫個側滑功能,最后的最后建議用SwipeBackLayout庫,已經比較成熟,需要處理的問題少

祝大家開發愉快!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容