抓住人生中的一分一秒,勝過虛度中的一月一年!
背景
用過蘋果手機的都知道,蘋果沒有物理返回鍵,原生自帶側滑回退頁面api
,手勢操控起來很方便,但是Android
去實現較為困難,現微信、今日頭條等app
各自都實現了側滑返回,于是也去研究了下如何實現,目前GitHub
上有很多開源的框架,有更好的輪子那必須用輪子了,但實現還是需要注意一些東西事項,下面給大家講解下如何正確去實現側滑回退功能
有很多類似的開源框架 暫舉五個
先看一個效果圖
原理分析
側滑看似頂層Activity
整體向右移動,然而并不是這樣的,android
不支持倆個頁面聯動效果,所以我們得想方設法在一個View
中看到低層布局,和頂層布局倆個畫面,才能去做這種效果,實現方案有倆種
不透明方案
在頂層Activity的DecorView
中插入一個Layout。監聽側滑事件,移動頂層Activity的ContentView
同時,在該Layout的onDraw
中調用View.draw(Canvas canvas)
繪制下層Activity
的ContentView
。造成側滑透視到下層Activity
的假象。
存在問題:當布局變化或數據更新,如橫豎屏切換、導航欄隱藏、窗口模式、分屏模式等,該假象始終如一不會有對應改變
透明方案
設置頂層Activity
透明
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
然后監聽側滑事件,移動頂層Activity的ContentView
,即可真正透視到下層Activity
的界面。此時無論布局變化、數據更新,都沒問題。BUT!該方案問題多如牛毛。。。
存在問題:windowIsTranslucent
為true
會引起一系列的動畫問題,如前后臺切換動畫、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也講了,再詳細說一下,比如LoginActivity
,MainActivity
等打開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
中重寫BaseActivity
中initSlidable
方法,禁止初始化側滑屬性
@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()
生命周期,所以導致Activity
的Window
無法回收,所以在多個Activity
疊加時會出現明顯的卡頓現象,目前并沒有特別好的解決辦法。
但是本人打印了下日志不管側滑返回,物理鍵返回,onStop
都有日志,大家可以測試下,本文章再繼續完善
問題3:最初選用android:screenOrientation="fullUser"
來實現固定方向,但是暫時發現小米手機不適配
最終采用behind
來實現固定頁面方向,behind
此屬性說白了講是說與上個頁面屏幕旋轉方向相同,這樣我們便可以邏輯轉換去思考下,第一個Activity設置強制豎屏,第二個頁面設置跟隨第一個屏幕方向屬性behind
,首頁面MainActivity
,登陸頁LoginActivity
,閃屏頁SplashActivity
都不需要實現側滑,我們只給它們設置強制豎屏"portrait",不設置透明屬性,因為這幾個頁面不需要側滑功能,這樣便可避免了倆者共存,經過多方面測試,確實可以這樣,暫時沒發現問題
問題4:后續繼續補充
最后補充
現側滑已應用到我的項目中,持續踩坑中,本所有優化是基于 Slidr庫所操作,其他開源庫有類似問題可以借簽上述處理,有能力的朋友可以自己寫個側滑功能,最后的最后建議用SwipeBackLayout庫,已經比較成熟,需要處理的問題少
祝大家開發愉快!