ViewStub布局无法隐藏求助:网络恢复后仍显示无网界面
问题:ViewStub填充的无网络布局在网络恢复后无法隐藏
我用ViewStub实现了无网络连接布局,仅在网络断开时填充显示。已经通过NetworkListener监听网络状态,用MutableStateFlow的isNetworkAvailable接收状态,但网络恢复后,这个布局没法隐藏,一直显示。
相关代码
无网络布局(no_internet_connection_layout.xml)
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" xmlns:app="http://schemas.android.com/apk/res-auto"> <ImageView android:id="@+id/no_wifi_image" android:layout_width="match_parent" android:layout_height="@dimen/_250sdp" android:contentDescription="@string/no_wifi_image" android:src="@drawable/no_internet_connection_icon" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/textView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/no_wifi_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:padding="8dp" android:text="@string/no_internet_connection_message" android:textSize="@dimen/_20ssp" /> </androidx.constraintlayout.widget.ConstraintLayout>
Fragment布局
<?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" android:background="@color/backgroundColor" android:orientation="vertical" tools:context=".ui.MainFragment"> <com.facebook.shimmer.ShimmerFrameLayout android:id="@+id/shimmerLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:shimmer_auto_start="false" app:shimmer_repeat_mode="restart" app:shimmer_shape="radial"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <include layout="@layout/placeholder_row" /> <include layout="@layout/placeholder_row" /> <include layout="@layout/placeholder_row" /> <include layout="@layout/placeholder_row" /> <include layout="@layout/placeholder_row" /> <include layout="@layout/placeholder_row" /> </LinearLayout> </com.facebook.shimmer.ShimmerFrameLayout> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" android:orientation="vertical" android:paddingBottom="50dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ProgressBar android:id="@+id/progressBar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|center" android:visibility="invisible" app:backgroundTint="@color/buttonColor" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" tools:visibility="visible"> </ProgressBar> <ViewStub android:id="@+id/no_internet_connection_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:layout="@layout/no_internet_connection_layout" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" tools:visibility="gone"> </ViewStub> </androidx.constraintlayout.widget.ConstraintLayout>
Fragment逻辑代码
lifecycleScope.launchWhenStarted { networkListener.checkNetworkAvailability(requireContext()).collect { stats -> Log.d(TAG, "networkListener: $stats") postViewModel.networkStats = stats networkStats = stats postViewModel.showNetworkStats() if (stats) { mainViewModel.getData() } else { noInternetConnectionLayout() Log.e(TAG, "onViewCreated: no internet") } } } private fun noInternetConnectionLayout() { hideShimmerEffect() binding.apply { progressBar.visibility = View.GONE noInternetConnectionLayout.inflate().also { if(networkStats){ it.visibility = View.GONE recyclerView.visibility = View.VISIBLE progressBar.visibility = View.VISIBLE } } } }
NetworkListener代码
class NetworkListener : ConnectivityManager.NetworkCallback() { private var isNetworkAvailable = MutableStateFlow(false) fun checkNetworkAvailability(context: Context): MutableStateFlow<Boolean> { val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager var isConnected = false if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { connectivityManager.registerDefaultNetworkCallback(this) connectivityManager.allNetworks.forEach { network -> val networkCapabilities = connectivityManager.getNetworkCapabilities(network) networkCapabilities?.let { if (it.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { isConnected = true return@forEach } } } isNetworkAvailable.value = isConnected } else { val networkChangeFilter = NetworkRequest.Builder().build() connectivityManager.registerNetworkCallback(networkChangeFilter, this) connectivityManager.allNetworks.forEach { network -> val networkCapabilities = connectivityManager.getNetworkCapabilities(network) networkCapabilities?.let { if (it.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { isConnected = true return@forEach } } } isNetworkAvailable.value = isConnected } return isNetworkAvailable } override fun onAvailable(network: Network) { isNetworkAvailable.value = true } override fun onLost(network: Network) { isNetworkAvailable.value = false } }
问题原因
- ViewStub特性限制:
inflate()方法仅能调用一次,调用后ViewStub会被替换为填充后的布局。当前逻辑仅在网络断开时调用填充方法,网络恢复时未对已填充的布局执行隐藏操作。 - 状态处理不完整:仅处理网络断开的情况,网络恢复时没有同步更新布局显示状态,也未控制主内容控件的可见性。
修复方案
步骤1:保存填充后的布局引用
在Fragment中添加变量存储无网络布局的实例,避免重复调用inflate():
private var noInternetView: View? = null
步骤2:统一处理网络状态的布局更新
替换原有的noInternetConnectionLayout()方法,同时处理网络断开和恢复的情况:
private fun updateNetworkLayout(hasNetwork: Boolean) { hideShimmerEffect() binding.apply { if (!hasNetwork) { // 网络断开时:显示无网络布局,隐藏主内容 progressBar.visibility = View.GONE recyclerView.visibility = View.GONE // 复用已填充的布局,避免重复inflate noInternetView = noInternetView ?: no_internet_connection_layout.inflate() noInternetView?.visibility = View.VISIBLE } else { // 网络恢复时:隐藏无网络布局,显示主内容 noInternetView?.visibility = View.GONE recyclerView.visibility = View.VISIBLE progressBar.visibility = View.VISIBLE mainViewModel.getData() } } }
步骤3:修改Flow收集逻辑
在状态收集时直接调用统一处理方法,覆盖两种网络状态:
lifecycleScope.launchWhenStarted { networkListener.checkNetworkAvailability(requireContext()).collect { stats -> Log.d(TAG, "networkListener: $stats") postViewModel.networkStats = stats networkStats = stats postViewModel.showNetworkStats() updateNetworkLayout(stats) // 统一处理网络状态变化 } }
额外优化:避免重复注册NetworkCallback
当前checkNetworkAvailability()每次调用都会注册新的回调,可能引发内存泄漏,建议在Fragment生命周期内仅注册一次,并在销毁时注销:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) lifecycleScope.launchWhenStarted { networkListener.checkNetworkAvailability(requireContext()).collect { stats -> // 状态处理逻辑 updateNetworkLayout(stats) } } // 在Fragment销毁时注销网络回调 viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onDestroy(owner: LifecycleOwner) { val connectivityManager = requireContext().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager connectivityManager.unregisterNetworkCallback(networkListener) } }) }
内容的提问来源于stack exchange,提问作者Krista
相关产品推荐
相关产品推荐

