Android单Activity多Fragment全屏切换平滑动画实现问题
解决单Activity多Fragment全屏切换时的动画卡顿问题
我之前也碰到过一模一样的头疼问题——单Activity架构下,全屏Fragment和普通Fragment切换时,系统UI的显示/隐藏总是和转场动画打架,要么卡顿要么跳变。试了好几种方法后,终于找到几个规范且简洁的方案,分享给你:
方案一:使用WindowInsets API(推荐,兼容Android 11+及低版本)
旧的SYSTEM_UI_FLAG已经被官方废弃,现在推荐用WindowInsets和ViewCompat的API处理系统栏显示/隐藏,这种方式能和Android的布局动画系统深度协同,从根源避免冲突。
步骤1:MainActivity中统一处理系统栏内边距
在Activity里给Fragment容器(比如FrameLayout或NavHostFragment)设置默认的系统栏内边距,让普通Fragment自动适配系统栏:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val container = findViewById<FrameLayout>(R.id.fragment_container) // 让容器自动适配系统栏内边距 ViewCompat.setOnApplyWindowInsetsListener(container) { view, insets -> val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) view.setPadding(systemBarInsets.left, systemBarInsets.top, systemBarInsets.right, systemBarInsets.bottom) insets } } }
步骤2:全屏Fragment中消费Insets并隐藏系统UI
在全屏Fragment的根布局里,通过OnApplyWindowInsetsListener消费系统栏Insets(即不传递给父布局),同时隐藏系统UI:
class FullScreenFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val rootView = inflater.inflate(R.layout.fragment_full_screen, container, false) // 开启沉浸式全屏 requireActivity().window.decorView.systemUiVisibility = ( View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION ) // 消费Insets,让Activity容器的内边距不生效 ViewCompat.setOnApplyWindowInsetsListener(rootView) { _, insets -> insets.consume(WindowInsetsCompat.Type.systemBars()) } return rootView } override fun onDestroyView() { super.onDestroyView() // 恢复系统UI显示 requireActivity().window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE // 通知Activity容器重新计算内边距,配合退出动画同步完成 ViewCompat.requestApplyInsets(requireActivity().findViewById(R.id.fragment_container)) } }
方案二:配合Fragment转场动画延迟UI模式切换
如果暂时不想切换到WindowInsets API,可以把UI模式的切换时机延迟到转场动画的周期内,用postOnAnimation保证和动画同步:
全屏Fragment中调整时机:
override fun onStart() { super.onStart() // 延迟到下一帧动画周期再隐藏UI,避免和转场动画冲突 requireActivity().window.decorView.postOnAnimation { requireActivity().window.decorView.systemUiVisibility = ( View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION ) } } override fun onStop() { super.onStop() // 延迟到退出动画启动后再恢复UI requireActivity().window.decorView.postOnAnimation { requireActivity().window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE } }
同时确保Fragment转场动画是通过setCustomAnimations设置的,比如:
supportFragmentManager.beginTransaction() .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right) .replace(R.id.fragment_container, FullScreenFragment()) .addToBackStack(null) .commit()
方案三:优化全屏DialogFragment
之前你用DialogFragment出现卡顿,大概率是没设置正确的全屏样式和动画。可以这样优化:
1. 创建全屏Dialog样式
在styles.xml中添加:
<style name="FullScreenDialog" parent="Theme.AppCompat.Light.Dialog"> <item name="android:windowNoTitle">true</item> <item name="android:windowFullscreen">true</item> <item name="android:windowIsFloating">false</item> <item name="android:windowAnimationStyle">@style/DialogAnimation</item> </style> <style name="DialogAnimation"> <item name="android:windowEnterAnimation">@anim/slide_in_right</item> <item name="android:windowExitAnimation">@anim/slide_out_right</item> </style>
2. 在DialogFragment中应用样式
class FullScreenDialogFragment : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(STYLE_NORMAL, R.style.FullScreenDialog) } // 其余逻辑和普通Fragment一致即可 }
这种方式下,DialogFragment的全屏由系统原生处理,转场动画能和UI隐藏完美同步,不会出现卡顿。
关键注意点
- 避免在
onCreateView或onDestroyView中直接修改系统UI模式,此时视图还未完全布局/销毁,容易触发布局重绘和动画冲突。 - 优先选择WindowInsets API,这是Android官方推荐的现代方案,兼容性更好,也更贴合系统布局逻辑。
- 如需支持Android 10及以下,务必使用
ViewCompat的相关方法做兼容。
内容的提问来源于stack exchange,提问作者ribafish
相关产品推荐
相关产品推荐

