我自己写的 IT之家 App 中使用的主题切换方式是直接重新创建 Activity ,但是这样就会有一个问题,切换主题时没有过渡效果,而且在 Android 9 上会闪一下。因为要重新创建 Activity ,就没办法在上面覆盖一个 View 做渐变动画;而一个一个给 View 使用动画又太麻烦了。然后我想到了通过另一个 Activity 的退出效果做动画的方案。不知道这个方案算不算好,不过至少达到了我预期的效果,而且实现起来也不复杂。
这个方案的思路是先获取当前 Activity 的屏幕截图,然后在调用 recreate()
之前无动画启动过渡用的 Activity 并显示这张截图,最后以渐变的动画退出过渡 Activity 。
先实现当前 Activity 截图的获取。
val rootView = window.decorView.rootView
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // 在 Android O 和更高版本使用 PixelCopy 获取截图
// RGB_565 的视觉效果也可以,但是在 Android 9 上会导致 PixelCopy 失败
val bitmap = Bitmap.createBitmap(rootView.width, rootView.height, Bitmap.Config.ARGB_8888)
val locationOfRootView = IntArray(2)
rootView.getLocationInWindow(locationOfRootView)
PixelCopy.request(
window,
Rect( // 截取的范围
locationOfRootView[0],
locationOfRootView[1],
locationOfRootView[0] + rootView.width,
locationOfRootView[1] + rootView.height
),
bitmap,
{ copyResult ->
if (copyResult == PixelCopy.SUCCESS) { // 当 PixelCopy 结果成功
// 由于 Intent 传递限制数据量,暂时想到用静态变量
ThemeSwitchTransitionActivity.screenshot = bitmap
startThemeSwitchTransition()
}
},
Handler()
)
} else { // 在低版本系统中使用缓存获取截图
rootView.isDrawingCacheEnabled = true
ThemeSwitchTransitionActivity.screenshot = Bitmap.createBitmap(rootView.drawingCache)
rootView.isDrawingCacheEnabled = false
startThemeSwitchTransition()
}
startThemeSwitchTransition()
方法中,重点是将当前 Activity 的状态栏和导航栏的样式 systemUiVisibility
进行传递,直接在过渡用 Activity 中设置,这样在过渡用的 Activity 中就不需要再判断当前应用使用的是什么主题来设置对应的样式。然后只需要在启动 Activity 时使用 overridePendingTransition()
设置为无动画效果就可以了。
private fun startThemeSwitchTransition() {
val intent = Intent(this, ThemeSwitchTransitionActivity::class.java).apply {
putExtra(KEY_SYSTEM_UI_VISIBILITY, window.decorView.systemUiVisibility)
}
startActivity(intent)
overridePendingTransition(0, 0)
Handler().postDelayed({ // 设置一点延迟,避免闪烁
recreate()
}, 50)
}
接下来就是 ThemeSwitchTransitionActivity
的内容了,这里说几个需要注意的地方,完整代码可以看文末的链接。
使用 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
来使 Window
扩展到整个屏幕,避免状态栏和导航栏占用空间。
window.setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
)
还原之前的 systemUiVisibility
。
window.decorView.systemUiVisibility = intent.getIntExtra(KEY_SYSTEM_UI_VISIBILITY, 0)
在结束 Activity 时,设置渐变的退出动画(我直接使用了系统自带的淡出动画)。
Handler().postDelayed({ // 设置一点延迟避免闪烁,实际测试不影响体验
finish()
overridePendingTransition(0, android.R.anim.fade_out)
}, 100)
避免用户按下返回键影响动画效果。
override fun onBackPressed() {
}
避免屏幕旋转影响效果,在 Manifest 中为这个 Activity 加上属性 android:screenOrientation="locked"
。
<activity
android:name=".ui.activity.ThemeSwitchTransitionActivity"
android:screenOrientation="locked"
android:theme="@style/Theme.MaterialComponents.NoActionBar" />
另外,没必要使用单独的布局 XML ,只需要一个 ImageView
即可。
val imageView = ImageView(this)
imageView.layoutParams =
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
setContentView(imageView)
最终效果演示如下(MP4 ,大小 217.8KB),完整代码可以见 MainActivity.kt 和 ThemeSwitchTransitionActivity.kt。
RGB_565 的视觉效果也可以,但是在 Android 9 上会导致 PixelCopy 失败。
2022-10-21 11:55 回复遇到同样的问题困扰很久,搜了全网终于在你这里找到答案。