Fragment切换返回后ComposeView组件消失问题求助
在基于FragmentContainerView的Activity中管理多个Fragment,首个Fragment采用XML嵌入ComposeView的混合开发模式。使用replace策略切换Fragment,且将Fragment实例预存在列表中,但从第二个Fragment切回首个Fragment时,ComposeView中的内容会消失。已尝试修改ViewCompositionStrategy仍未解决,使用的Compose BOM版本为2022.10.00至2023.01.00。
相关代码
NavigationActivity.kt
import android.os.Bundle import android.view.MenuItem import androidx.appcompat.app.AppCompatActivity import androidx.core.net.toUri import androidx.navigation.NavDeepLinkRequest import androidx.navigation.Navigation import androidx.navigation.findNavController import id.derysudrajat.inlinebinding.viewBinding import id.derysudrajat.library.R import id.derysudrajat.library.databinding.ActivityNavigationBinding class NavigationActivity : AppCompatActivity() { private val binding by viewBinding(ActivityNavigationBinding::inflate) private val listOfFragment = listOf(FragmentMainNav(), FragmentDetailNav()) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) binding.btnHome.setOnClickListener{ replace(HOME_NAV) } binding.btnDetail.setOnClickListener{ replace(DETAIL_NAV) } Navigation.createNavigateOnClickListener(R.id.fragmentDetailNav, null) } private fun replace(nav: String) { supportFragmentManager .beginTransaction() .replace( binding.fragmentContainerView.id, if (nav == HOME_NAV) listOfFragment[0] else listOfFragment[1] ) .commit() } companion object { const val HOME_NAV = "home_nav" const val DETAIL_NAV = "detail_nav" } }
FragmentMainNav.kt
import android.os.Bundle import android.view.View import androidx.compose.material3.Text import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.lifecycle.Lifecycle import androidx.navigation.findNavController import id.derysudrajat.inlinebinding.BindingFragment import id.derysudrajat.inlinebinding.viewBinding import id.derysudrajat.library.R import id.derysudrajat.library.databinding.FragmentNavMainBinding class FragmentMainNav : BindingFragment<FragmentNavMainBinding>() { override val binding by viewBinding(FragmentNavMainBinding::inflate) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.textMain.text = "Fragment Main" binding.composeView.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { Text(text = "This is Compose") } } } }
fragment_nav_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/text_main" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Fragment Main" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/text_main" /> </androidx.constraintlayout.widget.ConstraintLayout>
FragmentDetailNav.kt
import android.os.Bundle import android.view.View import id.derysudrajat.inlinebinding.BindingFragment import id.derysudrajat.inlinebinding.viewBinding import id.derysudrajat.library.databinding.FragmentNavDetailBinding import id.derysudrajat.library.databinding.FragmentNavMainBinding class FragmentDetailNav : BindingFragment<FragmentNavDetailBinding>() { override val binding by viewBinding(FragmentNavDetailBinding::inflate) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.textDetail.text = "Detail Navigation" } }
fragment_nav_detail.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/text_detail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Detail Navigation" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
解决方案
问题根源:使用replace策略切换Fragment时,首个Fragment的View会被销毁,当前设置的DisposeOnViewTreeLifecycleDestroyed策略会让Compose内容随View生命周期销毁;复用预存的Fragment实例时,View重建后Compose内容未重新初始化。
方法一:绑定Fragment生命周期调整Composition策略
将ComposeView的生命周期绑定到Fragment的viewLifecycleOwner,而非ViewTree的生命周期,这样View重建后Compose会自动恢复内容:
修改FragmentMainNav.kt的onViewCreated方法:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.textMain.text = "Fragment Main" binding.composeView.apply { // 绑定Fragment的View生命周期 setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner)) setContent { Text(text = "This is Compose") } } }
方法二:在View状态恢复后重新设置Compose内容
如果不想修改策略,可在onViewStateRestored回调中重新设置Compose内容,该回调会在View重建并恢复状态后触发:
在FragmentMainNav.kt中添加:
override fun onViewStateRestored(savedInstanceState: Bundle?) { super.onViewStateRestored(savedInstanceState) binding.composeView.setContent { Text(text = "This is Compose") } }
额外检查
确认自定义BindingFragment在onDestroyView中正确解绑Binding,避免持有旧View引用导致的异常。
内容的提问来源于stack exchange,提问作者Dery Sudrajat

