如何在Jetpack Compose中实现适配服务端数据的国家代码选择器
Jetpack Compose 国家代码选择器实现方案
效果参考
- 默认收起状态:

- 点击展开状态:

实现步骤
1. 定义数据结构与数据源
首先定义国家代码对应的数据类,存储接口返回的字段:
data class CountryCodeItem( val id: String, val countryName: String, val countryCode: String, // 国家区号,如+86 val flagUrl: String? = null // 可选国旗资源地址 )
封装自有服务端的请求逻辑,示例采用协程挂起函数实现:
class CountryCodeRepository { // 替换为实际的服务端接口请求逻辑 suspend fun getCountryCodeList(): Result<List<CountryCodeItem>> { return try { // 此处插入Retrofit/OkHttp等请求自有接口的代码 val response = yourApiService.fetchCountryCodeList() Result.success(response) } catch (e: Exception) { Result.failure(e) } } }
2. 核心选择器组件实现
组件内部维护展开状态、选中项、列表加载状态,完整实现代码如下:
@Composable fun CountryCodePicker( modifier: Modifier = Modifier, defaultSelected: CountryCodeItem? = null, onSelected: (CountryCodeItem) -> Unit ) { // 控制下拉展开/收起状态 var expanded by rememberSaveable { mutableStateOf(false) } // 当前选中的国家代码项 var selectedItem by rememberSaveable { mutableStateOf<CountryCodeItem?>(defaultSelected) } // 服务端返回的国家代码列表 var countryList by remember { mutableStateOf<List<CountryCodeItem>>(emptyList()) } // 加载状态标记 var isLoading by remember { mutableStateOf(false) } // 错误提示内容 var errorMsg by remember { mutableStateOf<String?>(null) } val coroutineScope = rememberCoroutineScope() val repository = remember { CountryCodeRepository() } // 组件首次加载时拉取服务端列表 LaunchedEffect(Unit) { isLoading = true val result = repository.getCountryCodeList() result.onSuccess { countryList = it // 未设置默认选中项时,可默认匹配当前系统区域的国家代码,可选逻辑 if (selectedItem == null && it.isNotEmpty()) { val defaultItem = it.firstOrNull { item -> item.countryCode == Locale.getDefault().country } ?: it.first() selectedItem = defaultItem onSelected(defaultItem) } errorMsg = null }.onFailure { errorMsg = "列表加载失败,点击重试" } isLoading = false } Column(modifier = modifier) { // 头部点击区域:展示当前选中状态 Row( modifier = Modifier .fillMaxWidth() .clickable { if (countryList.isNotEmpty()) { expanded = !expanded } else if (errorMsg != null) { // 加载失败时点击重试 coroutineScope.launch { isLoading = true repository.getCountryCodeList().onSuccess { list -> countryList = list errorMsg = null }.onFailure { errorMsg = "列表加载失败,点击重试" } isLoading = false } } } .padding(horizontal = 16.dp, vertical = 12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { when { isLoading -> Text("加载中...") errorMsg != null -> Text(errorMsg ?: "", color = Color.Red) selectedItem != null -> { Row(verticalAlignment = Alignment.CenterVertically) { // 可选:展示国旗图标 selectedItem?.flagUrl?.let { url -> AsyncImage( model = url, contentDescription = null, modifier = Modifier.size(24.dp), placeholder = painterResource(R.drawable.default_flag) ) Spacer(modifier = Modifier.width(8.dp)) } Text(selectedItem?.countryCode ?: "") Spacer(modifier = Modifier.width(4.dp)) Text(selectedItem?.countryName ?: "", color = Color.Gray) } // 下拉箭头 Icon( imageVector = if (expanded) Icons.Default.ArrowDropUp else Icons.Default.ArrowDropDown, contentDescription = null ) } } } // 展开的下拉列表 if (expanded && countryList.isNotEmpty()) { Column( modifier = Modifier .fillMaxWidth() .heightIn(max = 300.dp) .verticalScroll(rememberScrollState()) .border(1.dp, Color.LightGray, RoundedCornerShape(4.dp)) ) { countryList.forEach { item -> Row( modifier = Modifier .fillMaxWidth() .clickable { selectedItem = item expanded = false onSelected(item) } .padding(horizontal = 16.dp, vertical = 12.dp), verticalAlignment = Alignment.CenterVertically ) { // 可选:展示国旗图标 item.flagUrl?.let { url -> AsyncImage( model = url, contentDescription = null, modifier = Modifier.size(24.dp), placeholder = painterResource(R.drawable.default_flag) ) Spacer(modifier = Modifier.width(8.dp)) } Text(item.countryCode) Spacer(modifier = Modifier.width(8.dp)) Text(item.countryName, color = Color.Gray) // 选中状态标记 if (item.id == selectedItem?.id) { Spacer(modifier = Modifier.weight(1f)) Icon( imageVector = Icons.Default.Check, contentDescription = null, tint = Color.Blue ) } } } } } } }
提示:代码中
AsyncImage使用Coil图片加载库实现,如需使用需在build.gradle中引入Coil的Compose依赖。
3. 组件调用示例
@Composable fun RegisterScreen() { Column(modifier = Modifier.padding(16.dp)) { Text("请选择国家代码") Spacer(modifier = Modifier.height(8.dp)) CountryCodePicker( modifier = Modifier.fillMaxWidth(), onSelected = { selectedCountry -> // 处理选中结果 Log.d("CountryCode", "选中:${selectedCountry.countryCode} ${selectedCountry.countryName}") } ) } }
可选优化点
- 可添加搜索框,支持按国家名/区号快速筛选
- 实现点击下拉列表外部区域自动收起,可通过
ClickableModifier配合全局触摸事件监听实现 - 列表可添加分组,比如常用国家置顶
- 支持深色模式适配
内容的提问来源于stack exchange,提问作者Saeed Noshadi
相关产品推荐
相关产品推荐

