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库,已经比较成熟,需要处理的问题少

祝大家开发愉快!

推荐阅读更多精彩内容