Android12 Jetpack SplashScreen API总结

Posted hao_qi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android12 Jetpack SplashScreen API总结相关的知识,希望对你有一定的参考价值。


官方Android 12的Splash Screen文档地址
官方Splash Screen兼容库,支持所有版本系统

本篇文章主要围绕下面三个问题来介绍:

  1. 我们能从android 12 SplashScreen API里面学到什么?
  2. 新出的SplashScreen兼容库又是什么?能做成什么样子?
  3. 小甲同学:我想看Android12 SplashScreen源码,可以吗?

SplashScreen使用

首先我们需要把 compileSdktargetSdk(可选) 升级到31 。

Android12版本

(A).主题和外观配置

<!--文章末尾我们会把包含所有示例的链接地址提供出来,如有需要:请翻到文章末尾-->

<!-- values-v31/themes.xml -->
<!--单一颜色填充「启动画面」窗口背景-->
<item name="android:windowSplashScreenBackground">@color/...</item>

<!--「启动画面」中心的图标,
     可以配置AnimationDrawable 和 AnimatedVectorDrawable类型的drawable-->
<item name="android:windowSplashScreenAnimatedIcon">@drawable/...</item>

<!--「启动画面」中心图标动画的持续时间,这个属性不会对屏幕显示的实际时间产生任何影响-->
<item name="android:windowSplashScreenAnimationDuration">1000</item>

<!--「启动画面」中心图标后面设置背景-->
<item name="android:windowSplashScreenIconBackgroundColor">@color/...</item>

<!--「启动画面」底部显示的品牌图标-->
<item name="android:windowSplashScreenBrandingImage">@drawable/...</item>

(B).延长启动画面

val content: View = findViewById(android.R.id.content)
    content.viewTreeObserver.addOnPreDrawListener(
        object : ViewTreeObserver.OnPreDrawListener {
            override fun onPreDraw(): Boolean {
                // 模拟一些数据的初始化,再取消挂起
                return if (viewModel.isReady) {
                    // 取消挂起,恢复页面内容绘制
                    content.viewTreeObserver.removeOnPreDrawListener(this)
                    true
                } else {
                    // 挂起,内容还没有准备好
                    false
                }
            }
        }
    )

(C ).关闭启画面的动画

// 自己定制关闭的动画
splashScreen.setOnExitAnimationListener { splashScreenView ->
        val slideUp = ObjectAnimator.ofFloat(
            // 你们自己控制,自己随便写什么动画,这里我们测试让图标移动
            splashScreenView.iconView,
            View.TRANSLATION_Y,
            0f,
            -splashScreenView.height.toFloat()
        )
        slideUp.interpolator = AnticipateInterpolator()
        slideUp.duration = 200L
        slideUp.doOnEnd { splashScreenView.remove() }
        slideUp.start()
    }

(D).遇到的问题

  • android:windowSplashScreenBrandingImage 定义的图片尺寸要求是多少?总觉得有点拉伸;
  • 使用AnimationDrawable 或者 AnimatedVectorDrawable,来设置中心图标,会出现“中心图标”消失的情况,静态图标不会有这种问题出现;
  • Android12父主题设置android:windowBackground被覆盖,看不到效果

问题1: 在源码里面也没有看到具体的值或者比例大小,怎么办呢?

小技巧: 使用一个超大的正方形的图标设置进去测试了一下,拉伸不要紧,我们要的是比例, 然后测量了一下比例为:2.5 : 1,所以设计品牌名图标的时候,可以设置为400 * 160这样的比例为2.5:1的图标

问题2: 针对中心图标会闪现消失的问题做测试,
测试一:静态Icon,测试二:动态Icon

静态中心图标 - 正常 ……飘过……

下面我们来测试 动态中心图标,为了方便测试出效果,我们覆盖住图标后面的背景色,方便对比,最后发现:测试结果不太理想,效果不行

<!--AnimationDrawable写法-->
<!--没有真机测试,这个写法,效果看起来也挺奇怪的,可能是模拟器且是预览版的问题吧-->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">
    <item android:drawable="@mipmap/ic_launcher" android:duration="600" />
    <item android:drawable="@drawable/api12_logo" android:duration="200" />
    <item android:drawable="@mipmap/ic_launcher" android:duration="200" />
</animation-list>

动态中心图标,不正常

我们再来看一下官方文档中的顺滑效果

下面我们使用AnimatedVectorDrawable来制作动态图标,
为了观察出效果:我们打开模拟器的开发者选项,找到Animator时长缩放设置为:动画时长x10,自己看效果,此处略……

笑脸眼睛动画的矢量图文件 👇👇,点击查看在线制作矢量图动画

<!--仅测试玩耍,感兴趣的可以自己去制作一个-->
<?xml version="1.0" encoding="utf-8"?>
<animated-vector
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt">
    <aapt:attr name="android:drawable">
        <vector
            android:name="vector"
            android:width="24dp"
            android:height="24dp"
            android:viewportWidth="24"
            android:viewportHeight="24">
            <group android:name="group">
                <path
                    android:name="path_4"
                    android:pathData="M 11.99 2 C 6.47 2 2 6.48 2 12 C 2 17.52 6.47 22 11.99 22 C 17.52 22 22 17.52 22 12 C 22 6.48 17.52 2 11.99 2 Z M 12 20 C 7.58 20 4 16.42 4 12 C 4 7.58 7.58 4 12 4 C 16.42 4 20 7.58 20 12 C 20 16.42 16.42 20 12 20 Z M 12 17.5 C 14.33 17.5 16.32 16.05 17.12 14 L 15.45 14 C 14.76 15.19 13.48 16 12 16 C 10.52 16 9.25 15.19 8.55 14 L 6.88 14 C 7.68 16.05 9.67 17.5 12 17.5 Z"
                    android:fillColor="#FFFFFF"/>
            </group>
            <group android:name="group_1">
                <path
                    android:name="path_1"
                    android:pathData="M 8.5 9.5 M 7 9.5 C 7 9.102 7.158 8.721 7.439 8.439 C 7.721 8.158 8.102 8 8.5 8 C 8.898 8 9.279 8.158 9.561 8.439 C 9.842 8.721 10 9.102 10 9.5 C 10 9.898 9.842 10.279 9.561 10.561 C 9.279 10.842 8.898 11 8.5 11 C 8.102 11 7.721 10.842 7.439 10.561 C 7.158 10.279 7 9.898 7 9.5"
                    android:fillColor="#FFFFFF"/>
                <path
                    android:name="path_3"
                    android:pathData="M 8.5 9.5 M 7 9.5 C 7 9.102 7.158 8.721 7.439 8.439 C 7.721 8.158 8.102 8 8.5 8 C 8.898 8 9.279 8.158 9.561 8.439 C 9.842 8.721 10 9.102 10 9.5 C 10 9.898 9.842 10.279 9.561 10.561 C 9.279 10.842 8.898 11 8.5 11 C 8.102 11 7.721 10.842 7.439 10.561 C 7.158 10.279 7 9.898 7 9.5"
                    android:fillColor="#FFFFFF"/>
            </group>
            <group android:name="group_2">
                <path
                    android:name="path"
                    android:pathData="M 15.5 9.5 M 14 9.5 C 14 9.102 14.158 8.721 14.439 8.439 C 14.721 8.158 15.102 8 15.5 8 C 15.898 8 16.279 8.158 16.561 8.439 C 16.842 8.721 17 9.102 17 9.5 C 17 9.898 16.842 10.279 16.561 10.561 C 16.279 10.842 15.898 11 15.5 11 C 15.102 11 14.721 10.842 14.439 10.561 C 14.158 10.279 14 9.898 14 9.5"
                    android:fillColor="#FFFFFF"/>
                <path
                    android:name="path_2"
                    android:pathData="M 15.5 9.5 M 14 9.5 C 14 9.102 14.158 8.721 14.439 8.439 C 14.721 8.158 15.102 8 15.5 8 C 15.898 8 16.279 8.158 16.561 8.439 C 16.842 8.721 17 9.102 17 9.5 C 17 9.898 16.842 10.279 16.561 10.561 C 16.279 10.842 15.898 11 15.5 11 C 15.102 11 14.721 10.842 14.439 10.561 C 14.158 10.279 14 9.898 14 9.5"
                    android:fillColor="#FFFFFF"/>
            </group>
        </vector>
    </aapt:attr>
    <target android:name="group_1">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:propertyName="translateX"
                android:duration="1000"
                android:valueFrom="0"
                android:valueTo="7"
                android:valueType="floatType"
                android:interpolator="@android:interpolator/fast_out_slow_in"/>
        </aapt:attr>
    </target>
    <target android:name="group_2">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:propertyName="translateX"
                android:duration="1000"
                android:valueFrom="0"
                android:valueTo="-7"
                android:valueType="floatType"
                android:interpolator="@android:interpolator/fast_out_slow_in"/>
        </aapt:attr>
    </target>
</animated-vector>

后来我们又用了AnimationDrawable测试了一下慢放效果也不行,你仔细想一下:图片轮播放效果能好吗?
所以:AnimationDrawable不推荐,我们这里推荐使用:AnimatedVectorDrawable为矢量图添加动画效果。

问题3: Android12父主题设置android:windowBackground被覆盖,看不到效果
不要紧,只要我们的UI设计师(美工)按照如下尺寸规范来设计,使用静态中心图标,一样可以实现同样效果:

  • 中心图标: 图标内容区域内边距2/3,防止元素被切
  • 品牌名图标: 设计的尺寸比例为:2.5:1

SplashScreen兼容库

点击查看官方Splash Screen兼容库文档

(A).依赖库
点击查看Core库里面的最新版本

// 可在所有Android版本上使用的兼容库
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'

(B).主题和外观配置

  • 定义Activity应该使用的主题
<style name="Theme.App" parent="Theme.MaterialComponents.xxxxx.DarkActionBar">
        <item name="android:windowBackground">@color/...</item>
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:windowLightStatusBar" tools:targetApi="m">......</item>
        <item name="android:navigationBarColor">@android:color/transparent</item>
    </style>
  • 创建父主题给启动画面使用
<style name="Theme.App.Starting" parent="Theme.SplashScreen.IconBackground">
        <item name="android:windowBackground">@drawable/...</item>
        <item name="windowSplashScreenBackground">@color/...</item>
        <item name="windowSplashScreenAnimationDuration">200</item>
        <item name="postSplashScreenTheme">@style/Theme.App</item>
</style>
  • AndroidManifest.xml配置Activity的主题
<manifest>
   <application android:theme="@style/Theme.App.Starting">
    <!-- application和activity,两个选一个配置: @style/Theme.App.Starting -->
        <activity android:theme="@style/Theme.App.Starting">
...

(C ).初始化SplashScreen

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val splashScreen = installSplashScreen()
        setContent { ...... }
        splashScreen.setKeepVisibleCondition {
            !mainViewModel.mockDataLoading()
        }
        splashScreen.setOnExitAnimationListener(this)
    }

(D).中心图标大小修改

<item name="splashScreenIconSize">@dimen/....</item>

(E).遇到的问题
兼容库目前存在的问题

  1. 没有android:windowSplashScreenBrandingImage这个属性
  2. 配置了中心图标,会裁剪成圆形
  3. 低版本系统不配置windowSplashScreenAnimatedIcon会出现默认的Icon
  4. Android12父主题设置android:windowBackground被覆盖,看不到效果

问题1: 是因为兼容库的layout文件目录下面的splash_screen_view.xml没有“品牌名的视图”,大家点击查看一下,两个布局的xml内容就知道了:
frameworks下面的Android12的splash_screen_view.xml
core-splashscreen下面的兼容库的splash_screen_view.xml

但是我们在Android12即values-v31的themes.xml里面依然可以配置android:windowSplashScreenBrandingImage这个属性,因为Android12的SplashScreen是集成在frameworks里面的;

问题2: 是因为兼容库里面使用了MaskedDrawable包装了Icon,会裁剪成圆形,图标内容设计要保留2/3的内边距,否则会出现内容被裁剪掉的问题;
如何修复这个裁剪圆形问题呢?

把源码拷贝出来,总共就3个源代码文件,自己复制出来修改删除也可以的
或者,图标设计准则为:内容保留内边距为2/3,防止元素被裁剪

问题3: 写一个透明的drawable.xml然后替换就行了,类似如下方式

<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@android:color/transparent"/>
    <size android:width="0dp" android:height="0dp"/>
</shape>

问题4: Android12父主题设置android:windowBackground被覆盖,看不到效果
不要紧,只要我们的UI设计师(美工)按照如下尺寸规范来设计,使用静态中心图标,一样可以实现同样效果:

  • 中心图标: 图标内容区域内边距2/3,防止元素被切
  • 品牌名图标: 设计的尺寸比例为:2.5:1

(F).制作一个启动页

  • 模仿快手App的启动页

只需要配置父主题的android:windowBackground

Android5.0 ~ Android11 效果

由于我们在文章上面介绍到Android12上,无法为SplashScreen设置父主题的android:windowBackground,但我们依然可以通过配置静态中心图标来做到一样的效果的,请看下面的效果:

Android12 效果

如果你的UI设计师,给你矢量图,那么你就可以让中心图标在Android12系统上动起来了😆另外,可以建议UI设计师:统一所有系统上,启动页“中心”图标,居中展示,不然会有点怪

  • 动态图标启动页

如果设计成动态启动图标,这个需要考虑2个因素:

一、 低版本系统表现效果不一致,有些系统里面,动态图标只在启动页关闭的时候才显示(亲测Android平板5.1.1系统就是这样的);
二、如何兼容低版本系统,是先展示底部品牌名,最后只能等动态图标显示咯?

如果喜欢折腾的同学,可以多测试测试,我在使用Android10.0测试动态图标的时候,效果看着还可以,具体系统下限在哪?
这个看你们产品设计定位了,还有测试妹妹是否同意你们用,效果是否符合你们的产品;

建议的做法是:

  • Android 5.0 ~ Android 11.0系统,都统一使用android:windowBackground配置启动页背景
  • Android12.0 如果UI设计师给你做了矢量图,你可以做动态的中心图标,不给你,使用静态图标也可以的,参考上面:模拟快手App启动页

为了在模拟器上能正常显示出效果,我们在模拟器的开发者选项,找到Animator时
长缩放设置为:动画时长x10,放慢10倍,缺真机测试😂😂😂😂😂

Android12 动态启动页图标


源码分析

我们这里只分析Android12 SplashScreen,兼容库没有太多内容不足500行,感兴趣的同学可以自己阅读一下

我们在XXXActivity里面第一次用到了splashScreen.setOnExitAnimationListener,从这里开始往源头开始找,在下面的方法初始化了SplashScreen

//android.app.Activity
private SplashScreen getOrCreateSplashScreen() {
    synchronized (this) {
        if (mSplashScreen == null) {
           mSplashScreen = new SplashScreen.SplashScreenImpl(this);
        }
        return mSplashScreen;
    }
}

我们来看SplashScreenImpl实现类

//android.app.Activity

class SplashScreenImpl implements SplashScreen {
    ......
    //把SplashScreenImpl添加到这个单例类里面
    private final SplashScreenManagerGlobal mGlobal;
    public SplashScreenImpl(Context context) {
        mGlobal = SplashScreenManagerGlobal.getInstance();
    }
    @Override
    public void setOnExitAnimationListener(@NonNull SplashScreen.OnExitAnimationListener listener) {
        ......
        mGlobal.addImpl(this); // 用于后面执行启动画面将退出的回调
    }
    ......
    public void setSplashScreenTheme(@StyleRes int themeId) {
        ......
        try {
            //设置启动画面的主题
            AppGlobals.getPackageManager().setSplashScreenTheme(......);
        } catch (RemoteException e) {
            Log.w(TAG, "Couldn't persist the starting theme", e);
        }
    }
}

// 启动画面管理器
class SplashScreenManagerGlobal {
    ......
    // 管理多个闪屏实现
    private final ArrayList<SplashScreenImpl> mImpls = new ArrayList<>();

    private SplashScreenManagerGlobal() {
        // 向此进程注册启动画面管理器
        ActivityThread.currentActivityThread().registerSplashScreenManager(this);
    }
    ......
    
    private static final Singleton<SplashScreenManagerGlobal> sInstance =
    new Singleton<SplashScreenManagerGlobal>() {
        @Override
        protected SplashScreenManagerGlobal create() {
            return new SplashScreenManagerGlobal();
        }
    };

    private void addImpl(SplashScreenImpl impl) {
        synchronized (mGlobalLock) {
            mImpls.add(impl);
        }
    }

    private void removeImpl(SplashScreenImpl impl) {
        synchronized (mGlobalLock) {
            mImpls.remove(impl);
        }
    }
    ......
    public void handOverSplashScreenView(IBinder token,SplashScreenView splashScreenView) {
        //调用的是 => splashScreenView.transferSurface();
        transferSurface(splashScreenView);
        //回调 => impl.mExitAnimationListener.onSplashScreenExit(view);
        dispatchOnExitAnimation(token, splashScreenView);
    }
    ......
}

我们看到初始化SplashScreenManagerGlobal的时候,向此进程注册启动画面管理器

不可靠的 Android Jetpack 基准测试库测量结果?

Android Jetpack Paging 3:带 Room 的 PagingSource

Jetpack新成员SplashScreen:为全新的应用启动效果赋能!

Jetpack Compose 1.1 现已进入稳定版!

如何在使用改造和 android jetpack 库时使用此动态 json 键创建数据类

Android Jetpack简介