Android 12启动画面API-增加启动画面持续时间

12
我正在学习Android 12引入的新的SplashScreen API。目前我已经在模拟器和Google Pixel 4A上成功运行了它,但我希望增加它的持续时间。在我的启动画面中,我不需要花哨的动画,只需要一个静态的drawable。
我知道,我知道(叹息),你们中的一些人可能会认为,我不应该增加持续时间,我知道有几个很好的理由支持不这样做。然而,对于我来说,一个没有动画的启动画面的持续时间太短了(不到一秒钟),我认为这引起了可访问性方面的问题,特别是因为它无法被禁用(具有讽刺意味)。简单地说,产品或品牌/产品身份背后的组织在那个大小和时间上不能被新用户充分吸收或识别,使新的启动画面变得多余。
我看到主题中的property windowSplashScreenAnimationDuration,但这对持续时间没有影响,因为我没有进行动画。
 <style name="Theme.App.starting" parent="Theme.SplashScreen">
        <!--Set the splash screen background, animated icon, and animation duration.-->
        <item name="windowSplashScreenBackground">@color/gold</item>
    
        <!-- Use windowSplashScreenAnimatedIcon to add either a drawable or an
             animated drawable. One of these is required-->
        <item name="windowSplashScreenAnimatedIcon">@drawable/accessibility_today</item>
        <item name="windowSplashScreenAnimationDuration">300</item> <!--# Required for-->
                                                                    <!--# animated icons-->
        <!--Set the theme of the activity that directly follows your splash screen-->
        <item name="postSplashScreenTheme">@style/Theme.MyActivity</item>
    
        <item name="android:windowSplashScreenBrandingImage">@drawable/wculogo</item>
    
    </style>

有没有一种简单的方法来延长非动画启动画面的持续时间?


启动画面在应用程序打开时间过长时非常有用,但在其他情况下不应使用。这会让用户无缘无故地等待。 - cmak
没错,但从Android 12开始,它们是强制性的,不能通过丑陋的非官方黑客方式关闭。在这种情况下,我会说没有启动画面可能是更好的选择,但如果我要部署适用于Android 12的应用程序,必须按照Google想要的方式启动。请注意,我知道有方法可以停止显示启动画面,但这只会使操作系统看起来像是冻结了一会儿,这更糟糕。 - Andrew S
你说得对,我刚意识到这是强制性的。这是 Google 的一个糟糕决定,希望他们能够取消它。 - cmak
4个回答

15
当我写这个问题并准备好发布时,我偶然发现了setKeepOnScreenCondition方法(如下),它属于我们必须在主活动的onCreate中安装的splashScreen。考虑到目前(2022年1月)还没有其他帖子涉及此主题,也没有类似的回答解决相关问题,因此我认为将其分享出来是非常有价值的。
SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
splashScreen.setKeepOnScreenCondition(....);

在检查后,我发现这个方法接收一个 splashScreen.KeepOnScreenCondition() 接口的实例,其实现必须提供以下的方法签名实现:
 public boolean shouldKeepOnScreen() 

看起来这个方法将被启动屏调用,并保留启动屏直到它返回false为止。这就是我非常喜欢编程中的灵光一闪。

如果我使用一个初始化为true的布尔值,在延迟后将其设置为false,会怎样呢?这个直觉结果可行。以下是我的解决方案。它似乎有效,并且我认为它对其他人也会有用。假定不是使用Handler进行延迟,而是在某些进程完成后使用此方法来设置布尔值。

package com.example.mystuff.myactivity;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.splashscreen.SplashScreen;
import android.os.Bundle;
import android.os.Handler;

public class MainActivity extends AppCompatActivity {
    
    private boolean keep = true;
    private final int DELAY = 1250;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Handle the splash screen transition.
        SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
        super.onCreate(savedInstanceState);

        //Keep returning false to Should Keep On Screen until ready to begin.
        splashScreen.setKeepOnScreenCondition(new SplashScreen.KeepOnScreenCondition() {
            @Override
            public boolean shouldKeepOnScreen() {
                return keep;
            }
        });
        Handler handler = new Handler();
        handler.postDelayed(runner, DELAY);
    }

    /**Will cause a second process to run on the main thread**/
    private final Runnable runner = new Runnable() {
        @Override
        public void run() {
            keep = false;
        }
    };
    
}

如果您正在使用Java Lambdas,更好、更紧凑的解决方案如下:
package com.example.mystuff.myactivity;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.splashscreen.SplashScreen;
import android.os.Bundle;
import android.os.Handler;

public class MainActivity extends AppCompatActivity {
    
    private boolean keep = true;
    private final int DELAY = 1250;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Handle the splash screen transition.
        SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Keep returning false to Should Keep On Screen until ready to begin.
    splashScreen.setKeepOnScreenCondition(() -> keep);
    Handler handler = new Handler();
    handler.postDelayed(() -> keep = false, DELAY);;
    }


    
}

如果您有评论或反馈(除了告诉我不应该增加启动画面的持续时间),或者有更好的方法,请在评论中或回复中提出。


1
这正是你应该这样做的方式(即使你不应该这样做(抱歉我忍不住了))。 - Vadim Caen
1
它将防止您的应用程序绘制,但不会影响启动画面(如果是动画的话)。 - Vadim Caen
但不是从运行启动屏幕图标动画。 - Vadim Caen
你为什么建议在主线程中修改 keep? - Eldar Budagov
1
@HasIElus 在启动界面上加一个弹出对话框并不是一个优雅的做法,而且完全违背了 Google 试图通过 Android 启动界面实现的目标。因此,我认为除了可怕的黑客技巧外,几乎不可能实现这一点(即使可能性很小)。 - Andrew S
显示剩余5条评论

8

Kotlin中:

var keepSplashOnScreen = true
val delay = 2000L

installSplashScreen().setKeepOnScreenCondition { keepSplashOnScreen }
Handler(Looper.getMainLooper()).postDelayed({ keepSplashOnScreen = false }, delay)

在具有Manifest中LAUNCHER意图筛选器的活动中,在调用super.onCreate之前,可以将此内容放入onCreate fun中。

非常简单。谢谢! - Mark Delphi

2

Kotlin, Compose:

使用postDelayed()或runBlocking {}不是最佳解决方案。我更喜欢使用异步调用来计算setKeepOnScreenCondition。例如,假设您需要在从DataStore(以显示或不显示入门屏幕)收集值或从API /网络中提取某些数据时显示闪屏图标。在视图模型中:

private val _isLoading = MutableStateFlow(true)
val isLoading get() = _isLoading.asStateFlow()

private val _startDestination = MutableStateFlow<String?>(null)
val startDestination get() = _startDestination.asStateFlow()

init {
    viewModelScope.launch {
        delay(2000) // long operation

        // use .collect if you're interested in collecting all emitted values
        onboardingRepository.onboardingState().collect { isFinished ->
            _startDestination.value = if (isFinished) {
                _isLoading.value = false
                Screen.Authentication.route
            } else {
                _isLoading.value = false
                Screen.Onboarding.route
            }
        }

        // or use .first() terminal operator that returns the first element emitted by the flow and then cancels flow's collection
        val isFinished = onboardingRepository.onboardingState().first()
        _startDestination.value = if (isFinished) {
            Screen.Authentication.route
        } else {
            Screen.Onboarding.route
        }
        _isLoading.value = false
    }
}

在MainActivity中创建一个函数,收集流值并将它们用于onScreenConditions:
private val splashViewModel by viewModels<SplashViewModel>()

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setupSplashScreen()

    setContent {
        DiaryAppTheme {
            val navController = rememberNavController()
            val startDestination by splashViewModel.startDestination.collectAsStateWithLifecycle()
            startDestination?.let {
                SetupNavGraph(
                    navController = navController,
                    startDestination = it
                )
            }
        }
    }
}

private fun setupSplashScreen() {
    var keepSplashScreenOn = true
    lifecycleScope.launch {
        lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
            splashViewModel.isLoading.collect {
                keepSplashScreenOn = it
            }
        }
    }

    installSplashScreen().setKeepOnScreenCondition {
        keepSplashScreenOn
    }
}

你能详细说明为什么postDelayed()或runBlocking {}不是最佳解决方案吗? - Andrew S
1
简短回答 - 阻塞主线程是不良实践。 - Vi Kalinka
我没有意识到上面的初始解决方案正在阻塞主线程。postDelayed调用是异步调用。 - Andrew S
那么你打算如何计算它的延迟值? - Vi Kalinka
我没有传递一个常量。 - Andrew S
如果您对在应用程序启动时加载某些资源或初始化不感兴趣,那么使用硬编码值的postDelayed是可以的 ;) - Vi Kalinka

0

一种代理方式是在onCreate方法中使用runBlocking { delay(1200) },以保持主线程在一定时间内。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接