TabLayout切换Tab时的生命周期及Fragment内FAB与CardView状态保持问题
Hey there! Let's tackle your two questions about TabLayout and Fragment state management step by step.
First off, TabLayout is almost always used with ViewPager (or ViewPager2) in Android, so the lifecycle behavior depends mostly on ViewPager's offscreen preloading mechanism. Here's how it plays out:
- Default behavior (
offscreenPageLimit = 1):
ViewPager keeps 1 fragment to the left and right of the current active tab alive by default.- When switching to an adjacent tab (e.g., Tab 1 → Tab 2):
- The original tab's fragment only goes through
onPause()→onStop()— it doesn't get destroyed, since it's within the preload range. - The new tab's fragment will run through its full creation lifecycle (
onAttach()→onCreate()→onCreateView()→onActivityCreated()→onStart()→onResume()) if it's being loaded for the first time. If it was preloaded earlier, it just resumes withonStart()→onResume().
- The original tab's fragment only goes through
- When switching to a tab outside the preload range (e.g., Tab 1 → Tab 3 with default settings):
- The original Tab 1 fragment will be fully destroyed:
onPause()→onStop()→onDestroyView()→onDestroy()→onDetach(). - The new Tab 3 fragment will execute its full creation lifecycle from start to finish.
- The original Tab 1 fragment will be fully destroyed:
- When switching to an adjacent tab (e.g., Tab 1 → Tab 2):
- Custom
offscreenPageLimit:
If you setoffscreenPageLimitto a value equal to the total number of tabs minus 1 (e.g., 4 for 5 tabs), all fragments will stay preloaded. Switching tabs will only triggeronPause()/onStop()for the old tab andonStart()/onResume()for the new one — no fragments get destroyed.
The issue you're facing (CardView reverting to hidden when you switch back) usually happens because the first fragment gets destroyed and recreated when you move to a tab outside the preload range. Here are 3 solid solutions, ordered by recommendation:
Solution 1: Use ViewModel to preserve state (Most reliable)
ViewModels are tied to the lifecycle of the hosting Activity, so their data survives fragment destruction/recreation. Here's how to implement it:
- Create a ViewModel class to hold the visibility flag:
class FirstTabViewModel : ViewModel() { // MutableLiveData lets us observe state changes var isCardViewVisible = MutableLiveData<Boolean>(false) }
- Connect the ViewModel to your first fragment and bind the state:
class FirstTabFragment : Fragment() { private lateinit var viewModel: FirstTabViewModel private lateinit var fab: FloatingActionButton private lateinit var cardView: CardView override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_first_tab, container, false) fab = view.findViewById(R.id.fab) cardView = view.findViewById(R.id.card_view) return view } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // Get the ViewModel tied to the Activity's lifecycle viewModel = ViewModelProvider(requireActivity())[FirstTabViewModel::class.java] // Update CardView visibility whenever the state changes viewModel.isCardViewVisible.observe(viewLifecycleOwner) { isVisible -> cardView.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE } // Handle FAB clicks to toggle the state fab.setOnClickListener { val currentState = viewModel.isCardViewVisible.value ?: false viewModel.isCardViewVisible.value = !currentState } } }
No matter how many times the fragment is recreated, the ViewModel will keep track of the visibility state, so the CardView will stay as you left it.
Solution 2: Save state with onSaveInstanceState
If you prefer not to use ViewModel, you can save the state in the fragment's instance state bundle before it's destroyed, then restore it when it's recreated:
class FirstTabFragment : Fragment() { private var isCardViewVisible = false private lateinit var fab: FloatingActionButton private lateinit var cardView: CardView override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_first_tab, container, false) fab = view.findViewById(R.id.fab) cardView = view.findViewById(R.id.card_view) // Restore state if it exists savedInstanceState?.let { isCardViewVisible = it.getBoolean("card_view_visible", false) cardView.visibility = if (isCardViewVisible) View.VISIBLE else View.INVISIBLE } return view } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) // Save the current visibility state outState.putBoolean("card_view_visible", isCardViewVisible) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) fab.setOnClickListener { isCardViewVisible = !isCardViewVisible cardView.visibility = if (isCardViewVisible) View.VISIBLE else View.INVISIBLE } } }
Note: This works for most cases, but if the system destroys the fragment to free up memory, the saved state might be lost — it's less reliable than ViewModel.
Solution 3: Increase ViewPager's offscreen preload limit
If memory usage isn't a concern, you can set offscreenPageLimit to 4 (since you have 5 tabs) to keep all fragments alive:
// In your Activity or parent fragment where ViewPager is initialized viewPager.offscreenPageLimit = 4
This way, switching tabs will never destroy the first fragment — it just pauses and resumes. The downside is that keeping all fragments in memory can cause performance issues if your fragments are resource-heavy.
内容的提问来源于stack exchange,提问作者Thrinadh Reddy

