• Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
  • This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

1. Introduction

At the end of the article, there is a link to the APK demonstration. If you are interested, you can download and experience it yourself

Official Android 12 Splash Screen document address Official Android 12 Splash Screen compatible library, supporting all versions of the system

This article mainly focuses on the following three issues:

  • What can we learn from the Android 12 SplashScreen API?
  • What is the new SplashScreen compatible library? What will it look like?
  • I’d like to see the source code for Android12 SplashScreen. Is that ok?

In front of the high-energy warning: must remember “thumb up ❤ ️ + attention ❤ ️ + collection ❤ ️”, delimit the can find longer 😅 😅 🙈 🙈

How does SplashScreen work, what are the current problems, and how does it make a seamless transition? What problems will arise and how to solve them?

2. The SplashScreen use

First we need to upgrade to compileSdk and targetSdk(optional) to 31

2.1. Android12 version

(A). Theme and appearance configuration

<! At the end of the article, we provide links to all examples. If necessary, please go to the end of the article.

<! -- values-v31/themes.xml -->
<! Fill the splash Screen background with a single color -->
<item name="android:windowSplashScreenBackground">@color/...</item>

<! AnimationDrawable and AnimatedVectorDrawable can be configured with drawable of type AnimationDrawable -->
<item name="android:windowSplashScreenAnimatedIcon">@drawable/...</item>

<! The duration of the splash screen center icon animation. This property has no effect on the actual screen display time.
<item name="android:windowSplashScreenAnimationDuration">1000</item>

<! -- Set background behind the "Splash screen" center icon -->
<item name="android:windowSplashScreenIconBackgroundColor">@color/...</item>

<! -- Brand icon displayed at the bottom of "Splash Screen" -->
<item name="android:windowSplashScreenBrandingImage">@drawable/...</item>
Copy the code

(B). Extend the splash screen

val content: View = findViewById(android.R.id.content)
    content.viewTreeObserver.addOnPreDrawListener(
        object : ViewTreeObserver.OnPreDrawListener {
            override fun onPreDraw(a): Boolean {
                // Simulate initialization of some data, then cancel suspension
                return if (viewModel.isReady) {
                    // Cancel the suspension and resume the page content drawing
                    content.viewTreeObserver.removeOnPreDrawListener(this)
                    true
                } else {
                    // Suspend, content is not ready yet
                    false}}})Copy the code

(C). Close the animation of the opening screen

// Custom closed animation
splashScreen.setOnExitAnimationListener { splashScreenView ->
        val slideUp = ObjectAnimator.ofFloat(
            // You can control your own, write your own animation, here we test the icon to move
            splashScreenView.iconView,
            View.TRANSLATION_Y,
            0f,
            -splashScreenView.height.toFloat()
        )
        slideUp.interpolator = AnticipateInterpolator()
        slideUp.duration = 200L
        slideUp.doOnEnd { splashScreenView.remove() }
        slideUp.start()
    }
Copy the code

(D). Problems encountered

  • Android: windowSplashScreenBrandingImage defined image size requirements? It always feels a little stretched;
  • AnimationDrawable or AnimatedVectorDrawable to set the center icon,The center icon disappearsStatic ICONS do not have this problem;
  • Android12The parent themeSet up theandroid:windowBackgroundCovered, no effect

Problem 1: I don’t see a specific value or scale in the source code, what can I do?

Tip: use a large square icon set into the test, stretching does not matter, we want the proportion, and then measured the proportion is: 2.5:1, so when designing the brand name icon, can be set to 400 * 160 such a 2.5:1 ratio of the icon

Problem 2: Test the problem that the center Icon will flash and disappear. Test 1: static Icon, test 2: dynamic Icon





Static center iconNormal –

Next, we tested the dynamic center icon. In order to test the effect, we covered the background color behind the icon to facilitate comparison. Finally, we found that the test result was not ideal and the effect was not good

<! - the AnimationDrawable writing -- -- >
<! -- There is no real test, the effect of this writing method is also very strange, maybe it is the simulator and the preview version of the problem.
<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>
Copy the code




Dynamic center icon, abnormal



Look closely at the “blue background” behind the icon

Let’s take a look at the smooth effect in the official documentation





The official effect is smooth

Comparing the official effect, I guess there may be a problem between the emulator and the preview Android12, mainly because there is no real machine to test the effect of Android12, but it is not difficult to us, if your emulator has the same problem, please follow us to do the following:

Now let’s use AnimatedVectorDrawable to create a dynamic icon. To see the effect: Open the developer option of the emulator, go to the Animator and set the zoom to x10.





10 times slower. – Looks normal

Vector file of smiley eye animation 👇👇, click to view online vector animation

<! -- Test play only, if you are interested, you can make your own -->

      
<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 22 6.48 2 12 C 2 17.52 6.47 22 11.99 22 C 17.52 22 17.52 22 12 C 22 6.48 17.52 2 11.99 2 Z M 12 20 C 7.58 20 16.42 4 12 C 4 7.58 7.58 4 12 C 16.42 4 20 7.58 20 12 C 20 16.42 16.42 20 20 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 17.5 Z 12"
                    android:fillColor="#FFFFFF"/>
            </group>
            <group android:name="group_1">
                <path
                    android:name="path_1"
                    android:pathData="M 8.5 9.5m 7 9.5c 7 9.102 7.158 8.721 7.439 8.439 C 7.721 8.158 8.102 8 8.5 8c 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.5m 7 9.5c 7 9.102 7.158 8.721 7.439 8.439 C 7.721 8.158 8.102 8 8.5 8c 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.5m 14 9.5c 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.5m 14 9.5c 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="Seven"
                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="To 7"
                android:valueType="floatType"
                android:interpolator="@android:interpolator/fast_out_slow_in"/>
        </aapt:attr>
    </target>
</animated-vector>
Copy the code

Later we used AnimationDrawable to test the effect of slow playback, you can think about it: does the image wheel play well? AnimationDrawable is not recommended. We recommend AnimatedVectorDrawable to animate vector images

Problem 3: Android12 parent theme setting Android :windowBackground is overwritten, no effect can be seen

Never mind, as long as our UI designer (artist) follows the following dimensions and uses a static center icon, the same effect can be achieved: Center icon: the margin inside the content area of the icon is 2/3 to prevent elements from being cut. Brand name icon: Design size ratio: 2.5:1

2.2. SplashScreen compatible library

Click to view the official Splash Screen compatibility library documentation

(A). Rely on libraries

Check out the latest version of the Core library

// Compatible libraries available on all versions of Android
implementation 'androidx. Core: the core - splashscreen: 1.0.0 - alpha02'
Copy the code

(B). Theme and appearance configuration

  • Define the theme the Activity should use

<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>
Copy the code
  • Create a parent theme for the splash screen
<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>
Copy the code
  • Androidmanifest.xml configates the Activity theme
<manifest>
   <application android:theme="@style/Theme.App.Starting">
    <! -- Application or Activity: @style/Theme.App.Starting -->
        <activity android:theme="@style/Theme.App.Starting">.Copy the code

(C) initialize SplashScreen

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

(D). Change the size of the center icon

<item name="splashScreenIconSize">@dimen/....</item>
Copy the code

(E). Problems encountered

Current problems with the compatibility library

  • There is noandroid:windowSplashScreenBrandingImageThis attribute
  • Configure the center icon, will be trimmed to a circle
  • This parameter is not configured for earlier versionswindowSplashScreenAnimatedIconThe default Icon appears
  • Android12The parent themeSet up theandroid:windowBackgroundCovered, no effect

Splash_screen_view. XML does not have a view of the brand name in the file directory of the compatible library layout. Splash_screen_view.xml on Android12 under the frameworks core-splashscreen compatibility library splash_screen_view.xml

But we in Android12 values – v31 themes. The XML inside still can configure the android: windowSplashScreenBrandingImage this property, Android12’s SplashScreen is integrated into frameworks;

Problem 2: Because the compatible library uses MaskedDrawable to pack the Icon, it will be trimmed into a circle. The content of the Icon should be designed with 2/3 inner margin, otherwise the content will be trimmed out. How to fix this clipping problem?

Copy the source code out, a total of 3 source code files, copy yourself to modify the deletion can also be, or, icon design guidelines are: keep content inside margin of 2/3, to prevent elements from cropping

Problem 3: Write a transparent drawable. XML and replace it, similar to the following


      
<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>
Copy the code

Problem 4: Android12 parent theme setting Android :windowBackground is overwritten, no effect can be seen

Never mind, as long as our UI designer (artist) follows the following dimensions and uses a static center icon, the same effect can be achieved: Center icon: the margin inside the content area of the icon is 2/3 to prevent elements from being cut. Brand name icon: Design size ratio: 2.5:1

(F). Make a launch page

  • 1. Imitate the launch page of Kuaishou App

Just configure the parent theme for Android :windowBackground





Android5.0 ~ Android11 effect

We can’t set the parent theme for SplashScreen to Android :windowBackground as described in this article on Android12, but we can still set the static center icon to the same effect, as shown below:





Android12 effect

If your UI designer gives you a vector image, then you can make the center icon move on Android12 😆 alternatively, you can advise the UI designer: on all systems, launch the “center” icon and display it in the center, otherwise it will be a little weird

  • 2. Start the page with a dynamic icon

If you design a dynamic launch icon, there are two factors to consider:

In some systems, the dynamic icon is only displayed when the startup page is closed (this is the case in Android tablet 5.1.1). Two, how to compatible with the lower version of the system, is the first display of the bottom brand name, and finally can only wait for the dynamic icon display?

If you like to play around, you can test more. When I test dynamic ICONS using Android10.0, the effect looks ok. Where is the lower limit of the system? This depends on your product design positioning, there is a test sister agrees with you to use, the effect is consistent with your products;

The suggested approach is:

  • Android 5.0 to Android 11.0android:windowBackgroundConfigure the startup page background
  • If the UI designer gives you a vector image, you can make a dynamic center icon, not for you, using a static icon is also ok, refer to the above:Simulate the launch page of Kuaishou App

In order to display the effects properly on the emulator, go to the developer option of the emulator and set the Animator zoom to: animation duration x10, slow down 10 times, missing the real machine test 😂😂😂😂 port





Android12 Dynamic launch page icon

3. Source code analysis

We are only analyzing Android12 SplashScreen here, there is not much content in the compatible library that is less than 500 lines, so you can read it for yourself

In the splashScreen. For the first time, we in XXXActivity setOnExitAnimationListener, to find it from here to the source, the method to initialize the splashScreen below

//android.app.Activity
private SplashScreen getOrCreateSplashScreen(a) {
    synchronized (this) {
        if (mSplashScreen == null) {
           mSplashScreen = new SplashScreen.SplashScreenImpl(this);
        }
        returnmSplashScreen; }}Copy the code

Let’s look at the SplashScreenImpl implementation class

//android.app.Activity

class SplashScreenImpl implements SplashScreen {...// Add SplashScreenImpl to the singleton class
    private final SplashScreenManagerGlobal mGlobal;
    public SplashScreenImpl(Context context) {
        mGlobal = SplashScreenManagerGlobal.getInstance();
    }
    @Override
    public void setOnExitAnimationListener(@NonNull SplashScreen.OnExitAnimationListener listener) {... mGlobal.addImpl(this); // A callback that will exit the splash screen after execution}...public void setSplashScreenTheme(@StyleRes int themeId) {...try {
            // Set the theme of the splash screenAppGlobals.getPackageManager().setSplashScreenTheme(......) ; }catch (RemoteException e) {
            Log.w(TAG, "Couldn't persist the starting theme", e); }}}// Launch screen manager
class SplashScreenManagerGlobal {...// Manage multiple splash screen implementations
    private final ArrayList<SplashScreenImpl> mImpls = new ArrayList<>();

    private SplashScreenManagerGlobal(a) {
        // Register the splash screen manager with this process
        ActivityThread.currentActivityThread().registerSplashScreenManager(this); }...private static final Singleton<SplashScreenManagerGlobal> sInstance =
    new Singleton<SplashScreenManagerGlobal>() {
        @Override
        protected SplashScreenManagerGlobal create(a) {
            return newSplashScreenManagerGlobal(); }};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) {
        / / call is = > splashScreenView. TransferSurface ();
        transferSurface(splashScreenView);
        / / callback = > impl. MExitAnimationListener. OnSplashScreenExit (view);dispatchOnExitAnimation(token, splashScreenView); }... }Copy the code

We saw the initialization SplashScreenManagerGlobal, registered splash screen with the process of the manager

//android.app.ActivityThread

public void registerSplashScreenManager(SplashScreen.SplashScreenManagerGlobal manager) {
    synchronized (this) { mSplashScreenGlobal = manager; }}Copy the code

How do I add SplashScreen to the current window? ActivityThread inherits ClientTransactionHandler, which has an abstract method like this:

//android.app.ClientTransactionHandler
public abstract void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
            @NonNull SplashScreenViewParcelable parcelable);
Copy the code

ActivityThread must implement this method, so who called it? Due to the space problem, I will not introduce and analyze the code line by line. If you are interested, you can do in-depth research by yourself. The flow chart of the call is posted below

Who calls the handleAttachSplashScreenView (), its invocation chain flow chart is as follows:

Ok, finish see flow chart, we look at the ActivityThread# handleAttachSplashScreenView

//android.app.ActivityThread

@Override
public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
@Nullable SplashScreenView.SplashScreenViewParcelable parcelable) {
    final DecorView decorView = (DecorView) r.window.peekDecorView();
    if(parcelable ! =null&& decorView ! =null) { createSplashScreen(r, decorView, parcelable); }... }private void createSplashScreen(ActivityClientRecord r, DecorView decorView, SplashScreenView.SplashScreenViewParcelable parcelable) {
    // Initialize the SplashScreenView builder
    final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity);
    // Get the configuration data from parcelable and initialize SplashScreenView with build() to set the data
    final SplashScreenView view = builder.createFromParcel(parcelable).build();
    // Add SplashScreenView to the DecorView
    decorView.addView(view);
    // Set the SystemUI color
    view.attachHostActivityAndSetSystemUIColors(r.activity, r.window);
    // Refresh the viewview.requestLayout(); . }Copy the code

Core part of the source code has been almost analyzed, the rest of the source code, interested students can view their own reading

4. To summarize

  • Upgrade compileSdk to 31
  • The compatible library SplashScreen is used uniformly in the product
implementation 'Androidx. core: Core-splashScreen: latest version'
Copy the code
  • Resource directories in the demo example
Drawable -- drawable-v23 -- drawable-v31 -- Android12 and above drawable values -- default value -night -- define default dark mode resources values-v23 -- define system resources above 6.0 values-v31 -- define resources for Android12 and above values-night-v31 -- define dark mode resources for Android12 and aboveCopy the code
  • Launch page icon design guidelines

The large image of the center icon, the content should be kept 2/3 of the inner margin, otherwise the icon will be cut out, in addition: the size of the icon can be modified; Bottom brand name icon: The size ratio must be 2.5:1. SplashScreen in earlier versions does not support setting the bottom brand icon. You need to manually add the following attributes in the values-v31 directory on Android12 to display the brand name icon.

<! -- compatible library does not have this property, we need to configure values v31 separately -->
<item name="android:windowSplashScreenBrandingImage">@drawable/...</item>
Copy the code
  • The following systems can use android:windowBackground for the parent theme set windowBackground, remember not to set the parent theme windowBackground on Android12 and above (because there is no effect 😅😅)

  • Android12 system under the system, use the android: windowBackground, must give windowSplashScreenAnimatedIcon set the drawable of a transparent, otherwise there will be a robot icon

  • WindowSplashScreenBackground the color of this property is important to note that there is something wrong with the configuration, start the transition to the page on the surface of the home page, there will be the color come out, and the Activity of android: windowBackground configured to the same color

  • Above the splash screen, add an “advertising or promotion page” with the following code and effect:

override fun onSplashScreenExit(splashScreenViewProvider: SplashScreenViewProvider) {
    if(splashScreenViewProvider.view is ViewGroup){
        // Add a launch page AD or promotion page here
        val composeView = ComposeView(this@SplashScreenCompatActivity).apply {
            setContent {
                SplashAdScreen(onCloseAd = {
                    splashScreenViewProvider.remove()
                })
            }
        }
        (splashScreenViewProvider.view as ViewGroup).addView(composeView)
        return}}Copy the code




Practice – The launch page adds an AD or promotion page

Refer to the address

  • Online flow chart making
  • Official documentation: Splash Screens
  • Google introduces vector graphics
  • Online SVG editor
  • Create vector animations online
  • Merge multiple GIFs online

Examples in the article demo APK and source address:

Static icon launches the page Dynamic icon launch page

(Android12 has animation effects)
Launch page plus ads
Download: SplashScreen Quick start page effect of APK001 Download: SplashScreen Quick Start page effect for APK002 Download: SplashScreen launch page AD APK
Extraction code: 7GJ2 Extraction code: B6CE Extraction code: FNVA

Click to see the source code for the SplashScreen demo