Android Compose 无网络状态界面处理全方案
引言
在移动应用开发中,网络连接不稳定是常见场景。优雅地处理无网络状态能显著提升用户体验。Jetpack Compose 提供了强大的工具来实现各种网络状态下的界面展示。本文将全面介绍在 Compose 中处理无网络状态的多种方案。
一、基础网络状态检测
1. 网络状态检测工具类
class NetworkMonitor(private val context: Context) {
val isOnline: Boolean
get() {
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkCapabilities =
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
return networkCapabilities?.let {
it.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
it.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
it.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
} ?: false
}
}
2. 网络状态 ViewModel
class NetworkViewModel(private val networkMonitor: NetworkMonitor) : ViewModel() {
private val _isOnline = mutableStateOf(networkMonitor.isOnline)
val isOnline: State<Boolean> = _isOnline
fun checkNetworkStatus() {
_isOnline.value = networkMonitor.isOnline
}
}
二、简单界面处理方案
1. 全屏覆盖式
@Composable
fun SimpleScreen() {
val isOnline by networkViewModel.isOnline.collectAsState()
Box(modifier = Modifier.fillMaxSize()) {
// 主内容
MainContent()
// 无网络覆盖层
if (!isOnline) {
Surface(
color = MaterialTheme.colors.background.copy(alpha = 0.9f),
modifier = Modifier.fillMaxSize()
) {
OfflineContent()
}
}
}
}
@Composable
fun OfflineContent() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(Icons.Filled.WifiOff, contentDescription = null, modifier = Modifier.size(64.dp))
Spacer(Modifier.height(16.dp))
Text("无网络连接", style = MaterialTheme.typography.h5)
Text("请检查您的网络设置", style = MaterialTheme.typography.body1)
Spacer(Modifier.height(24.dp))
Button(onClick = { /* 重试 */ }) {
Text("重试连接")
}
}
}
三、复杂界面处理方案
1. 局部替换法
@Composable
fun ComplexScreen() {
val isOnline by networkViewModel.isOnline.collectAsState()
Scaffold(
topBar = { AppBar() },
bottomBar = { BottomBar() }
) { padding ->
Column(modifier = Modifier.padding(padding)) {
// 不依赖网络的部分
LocalFeatures()
// 依赖网络的部分
if (isOnline) {
OnlineContent()
} else {
NetworkErrorCard(
title = "网络内容",
message = "此部分需要网络连接",
onRetry = { /* 重试 */ }
)
}
// 更多内容...
}
}
}
2. 分区块处理
@Composable
fun DashboardScreen() {
val networkState by rememberNetworkState()
LazyColumn {
item { HeaderSection() }
item {
if (networkState.isOnline) {
LiveDataSection()
} else {
OfflinePlaceholder(
icon = Icons.Filled.Update,
title = "实时数据"
)
}
}
item {
if (networkState.isOnline) {
RecommendationsSection()
} else {
OfflinePlaceholder(
icon = Icons.Filled.Star,
title = "个性化推荐"
)
}
}
}
}
3. 渐进式显示
@Composable
fun NewsFeedScreen(viewModel: NewsViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsState()
when {
uiState.isLoading -> FullScreenLoading()
!uiState.isOnline && uiState.cachedItems.isEmpty() -> FullScreenError()
!uiState.isOnline -> {
Column {
CachedNewsList(uiState.cachedItems)
OfflineBanner()
}
}
else -> NewsList(uiState.items)
}
}
四、高级网络状态管理
1. 增强版网络状态管理器
class AdvancedNetworkMonitor(context: Context) {
sealed class NetworkState {
object Available : NetworkState()
object Unavailable : NetworkState()
data class Limited(val type: ConnectionType) : NetworkState()
}
enum class ConnectionType { WIFI, CELLULAR, ETHERNET, VPN, OTHER }
private val _state = mutableStateOf<NetworkState>(NetworkState.Available)
val state: State<NetworkState> = _state
init {
val cm = context.getSystemService(ConnectivityManager::class.java)
val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
cm.registerNetworkCallback(request, object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
_state.value = NetworkState.Available
}
override fun onLost(network: Network) {
_state.value = NetworkState.Unavailable
}
override fun onCapabilitiesChanged(
network: Network,
capabilities: NetworkCapabilities
) {
_state.value = when {
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ->
NetworkState.Limited(ConnectionType.WIFI)
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ->
NetworkState.Limited(ConnectionType.CELLULAR)
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) ->
NetworkState.Limited(ConnectionType.VPN)
else -> NetworkState.Available
}
}
})
}
}
2. 状态监听Composable
@Composable
fun rememberNetworkState(): State<NetworkState> {
val context = LocalContext.current
val networkMonitor = remember { AdvancedNetworkMonitor(context) }
return networkMonitor.state
}
五、UI组件库
1. 离线占位符组件
@Composable
fun OfflinePlaceholder(
title: String,
message: String = "需要网络连接",
icon: ImageVector = Icons.Filled.CloudOff,
onRetry: (() -> Unit)? = null
) {
Card(
elevation = 4.dp,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = MaterialTheme.colors.error,
modifier = Modifier.size(48.dp)
)
Spacer(Modifier.height(8.dp))
Text(title, style = MaterialTheme.typography.h6)
Text(message, style = MaterialTheme.typography.body2)
onRetry?.let {
Spacer(Modifier.height(16.dp))
Button(onClick = it) {
Text("重试")
}
}
}
}
}
2. 顶部横幅通知
@Composable
fun NetworkStatusBanner() {
val networkState by rememberNetworkState()
val showBanner = networkState is NetworkState.Unavailable
AnimatedVisibility(
visible = showBanner,
enter = slideInVertically { -it },
exit = slideOutVertically { -it }
) {
Surface(
color = MaterialTheme.colors.error,
modifier = Modifier.fillMaxWidth()
) {
Row(
modifier = Modifier.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Filled.WifiOff, "离线", tint = MaterialTheme.colors.onError)
Spacer(Modifier.width(8.dp))
Text("离线模式 - 部分功能不可用", color = MaterialTheme.colors.onError)
}
}
}
}
六、最佳实践建议
- 分层处理:根据界面复杂度选择全屏覆盖或局部替换
- 缓存策略:尽可能显示缓存内容并明确标注
- 明确反馈:让用户清楚知道当前是离线状态
- 恢复途径:提供明显的重试或刷新选项
- 状态细分:区分完全离线、弱网等不同状态
- 视觉一致:保持离线UI与应用设计风格一致
- 性能考虑:避免不必要的网络状态监听和重组
结语
在Compose中处理无网络状态需要综合考虑用户体验、界面复杂度和技术实现。本文介绍的各种方案可以根据实际需求灵活组合使用。