Activity中多视图架构实现咨询:展示Fragment与断连全屏视图
Nice problem to solve! Let's walk through a clean, maintainable architecture for your Activity that handles all three view states you described. The key here is to separate concerns: your Activity will act as a controller that switches between states, while each state's UI is handled by dedicated Fragments or a standalone offline view.
1. Core Layout Structure
First, set up your Activity's layout to accommodate all three states. We'll use a ConstraintLayout as the root to easily control visibility and positioning:
<?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"> <!-- Stable AppBarLayout - hidden only in offline state --> <com.google.android.material.appbar.AppBarLayout android:id="@+id/app_bar_layout" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" android:visibility="visible"> <com.google.android.material.appbar.MaterialToolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:title="Your App Title"/> </com.google.android.material.appbar.AppBarLayout> <!-- Fragment Container - holds either the scrollable or tabbed Fragment --> <FrameLayout android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/app_bar_layout" app:layout_constraintBottom_toBottomOf="parent" android:visibility="visible"/> <!-- Full-screen offline view - hidden by default --> <LinearLayout android:id="@+id/offline_view" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" android:visibility="gone" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"> <ImageView android:layout_width="120dp" android:layout_height="120dp" android:src="@drawable/ic_no_internet"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="No internet connection" android:textSize="18sp" android:layout_marginTop="16dp"/> <Button android:id="@+id/retry_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Retry" android:layout_marginTop="24dp"/> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
2. Define State Enums
Create an enum to represent your three states—this makes state switching explicit and avoids magic values:
enum class ScreenState { SCROLLABLE_CONTENT, // State 1: NestedScrollView in Fragment TABBED_CONTENT, // State 2: Tabbed Fragment OFFLINE // State 3: Full-screen offline view }
3. State Switching Logic
In your Activity, implement a method to switch between states. This method will handle showing/hiding the AppBar, swapping Fragments, and toggling the offline view:
class MainActivity : AppCompatActivity() { private lateinit var appBarLayout: AppBarLayout private lateinit var fragmentContainer: FrameLayout private lateinit var offlineView: LinearLayout private lateinit var retryButton: Button // Initialize Fragments once to preserve state private val scrollableFragment = ScrollableContentFragment() private val tabbedFragment = TabbedContentFragment() private var currentState: ScreenState = ScreenState.SCROLLABLE_CONTENT override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Initialize views appBarLayout = findViewById(R.id.app_bar_layout) fragmentContainer = findViewById(R.id.fragment_container) offlineView = findViewById(R.id.offline_view) retryButton = findViewById(R.id.retry_button) // Set up initial fragment supportFragmentManager.beginTransaction() .add(R.id.fragment_container, scrollableFragment, "scrollable") .add(R.id.fragment_container, tabbedFragment, "tabbed") .hide(tabbedFragment) .commit() // Retry button click listener retryButton.setOnClickListener { // Check internet connection and switch back to previous state if (isInternetAvailable()) { switchToState(currentState) } } } fun switchToState(newState: ScreenState) { currentState = newState when (newState) { ScreenState.SCROLLABLE_CONTENT -> { // Show AppBar and fragment container, hide offline view appBarLayout.visibility = View.VISIBLE fragmentContainer.visibility = View.VISIBLE offlineView.visibility = View.GONE // Switch to scrollable fragment supportFragmentManager.beginTransaction() .hide(tabbedFragment) .show(scrollableFragment) .commit() } ScreenState.TABBED_CONTENT -> { // Show AppBar and fragment container, hide offline view appBarLayout.visibility = View.VISIBLE fragmentContainer.visibility = View.VISIBLE offlineView.visibility = View.GONE // Switch to tabbed fragment supportFragmentManager.beginTransaction() .hide(scrollableFragment) .show(tabbedFragment) .commit() } ScreenState.OFFLINE -> { // Hide AppBar and fragment container, show offline view appBarLayout.visibility = View.GONE fragmentContainer.visibility = View.GONE offlineView.visibility = View.VISIBLE } } } // Helper method to check internet connectivity (simplified example) private fun isInternetAvailable(): Boolean { val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val network = connectivityManager.activeNetwork ?: return false val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) } }
4. Implement the Fragments
Now create your two content Fragments:
ScrollableContentFragment (State 1)
This fragment holds your NestedScrollView:
<!-- fragment_scrollable_content.xml --> <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- Your long scrollable content here --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Scrollable Content" android:textSize="24sp" android:layout_marginBottom="16dp"/> <!-- Add more views as needed --> </LinearLayout> </androidx.core.widget.NestedScrollView>
TabbedContentFragment (State 2)
This fragment uses a TabLayout and ViewPager2 for tab switching:
<!-- fragment_tabbed_content.xml --> <LinearLayout 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" android:orientation="vertical"> <com.google.android.material.tabs.TabLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="wrap_content" app:tabMode="fixed"> <com.google.android.material.tabs.TabItem android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Tab 1"/> <com.google.android.material.tabs.TabItem android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Tab 2"/> </com.google.android.material.tabs.TabLayout> <androidx.viewpager2.widget.ViewPager2 android:id="@+id/view_pager" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> </LinearLayout>
In the Fragment class, set up the ViewPager2 adapter:
class TabbedContentFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_tabbed_content, container, false) val tabLayout = view.findViewById<TabLayout>(R.id.tab_layout) val viewPager = view.findViewById<ViewPager2>(R.id.view_pager) viewPager.adapter = TabAdapter(this) TabLayoutMediator(tabLayout, viewPager) { tab, position -> tab.text = when (position) { 0 -> "Tab 1" 1 -> "Tab 2" else -> "" } }.attach() return view } private class TabAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) { override fun getItemCount() = 2 override fun createFragment(position: Int) = when (position) { 0 -> Tab1Fragment() 1 -> Tab2Fragment() else -> Tab1Fragment() } } }
5. Bonus Tips for Stability
- Preserve Fragment State: By using
hide()andshow()instead ofreplace(), we keep the Fragments in memory, preserving scroll positions, tab selections, and user input. - Internet Monitoring: Use a
ConnectivityManager.NetworkCallbackto automatically switch to the offline state when connectivity is lost, and switch back when it's restored. - ViewModel Integration: For more complex state management, use a
ViewModelto hold the current screen state, so it survives configuration changes (like screen rotations). - Testability: Each Fragment handles its own business logic, making it easier to test in isolation.
内容的提问来源于stack exchange,提问作者yozhik

