Android设备旋转时如何维持动态添加的Tab状态?
问题描述
应用背景
我正在开发一款采用MVVM设计模式的Android应用,应用包含多个Fragment,每个Fragment对应一个足球联赛的标题和图标,这些Tab会根据API调用结果动态添加。
API调用签名
@GET("/leagues") suspend fun getLeague( @Query("id") leagueId: Int = Shared.LEAGUES_IDS[0], @Query("current") current: String = "true" ): Response<Leagues>
ViewModel核心代码
@HiltViewModel class FootBallViewModel @Inject constructor( private val app: Application, private val remoteRepository: RemoteRepository, private val defaultLocalRepository: DefaultLocalRepository ): ViewModel() { var apiCounter = 0 val leagues: MutableList<Leagues> = mutableListOf() private val _leaguesMutableLiveData = MutableLiveData<ResponseState<Leagues>>() val leaguesMutableLiveData: LiveData<ResponseState<Leagues>> = _leaguesMutableLiveData init { viewModelScope.launch(Dispatchers.IO) { if(Shared.isConnected) { Shared.LEAGUES_IDS.forEach { leagueId -> when(val responseState = getLeague(leagueId)) { // 调用联赛API is ResponseState.Success -> { if(Shared.isLiveMatches) responseState.data?.body()?.response?.get(0)?.seasons?.get(0)?.year?.let { season -> getLeagueMatches( leagueId, season, app.getString(R.string.live_matches) ) } else responseState.data?.body()?.response?.get(0)?.seasons?.get(0)?.year?.let { season -> getLeagueMatches( leagueId, season, null ) } } is ResponseState.Loading -> {} is ResponseState.Error -> {} } } } } } suspend fun getLeague(id: Int): ResponseState<Response<Leagues>> = viewModelScope.async(Dispatchers.IO) { if (Shared.isConnected) { try { val response = remoteRepository.getLeague(id) response?.body()?.let { leagues.add(it) } _leaguesMutableLiveData.postValue(ResponseState.Success(response?.body()!!)) apiCounter++ Log.i("apiCounter", apiCounter.toString()) return@async ResponseState.Success(response) } catch (exception: Exception) { _leaguesMutableLiveData.postValue(ResponseState.Error(app.getString(R.string.unknown_error))) handleLeaguesException(exception) return@async ResponseState.Error(app.getString(R.string.unknown_error)) } } else { _leaguesMutableLiveData.postValue(ResponseState.Error(app.getString(R.string.unknown_error))) return@async ResponseState.Error(app.getString(R.string.unable_to_connect)) } }.await() // 其他省略代码 }
动态添加Tab的代码
footBallViewModel.leaguesMutableLiveData.observe(viewLifecycleOwner) { TabItemBinding.inflate(layoutInflater).apply { this.league = it.data?.response?.get(0)?.league val tab = tabLayout.newTab().setCustomView(this.root) tabLayout.addTab(tab) val animation = AnimationUtils.loadAnimation(context, R.anim.slide_in) tab.customView?.startAnimation(animation) } Log.i("tabsCount", tabLayout.tabCount.toString()) }
遇到的问题
Tab初始添加正常,但设备旋转后,由于leaguesMutableLiveData仅保存最后一个联赛的数据,TabLayout只显示最后一个Tab。
尝试过让leaguesMutableLiveData存储MutableList<League>,但每次向列表添加新联赛时,观察者会遍历整个列表重复添加Tab,导致出现大量重复项。
需求:实现根据联赛列表动态添加Tab,且设备旋转后能维持Tab状态。
解决方案
1. 改造ViewModel的LiveData,存储完整联赛列表
把ViewModel中的LiveData改为存储整个列表的状态,用私有可变列表维护数据,对外发布不可变列表实例,确保每次数据更新都能触发观察者回调:
@HiltViewModel class FootBallViewModel @Inject constructor( private val app: Application, private val remoteRepository: RemoteRepository, private val defaultLocalRepository: DefaultLocalRepository ): ViewModel() { // 私有可变列表维护所有联赛数据 private val _leaguesList = mutableListOf<Leagues>() // 改为存储列表的ResponseState private val _leaguesLiveData = MutableLiveData<ResponseState<List<Leagues>>>() val leaguesLiveData: LiveData<ResponseState<List<Leagues>>> = _leaguesLiveData init { viewModelScope.launch(Dispatchers.IO) { if(Shared.isConnected) { // 发布加载状态(可选) _leaguesLiveData.postValue(ResponseState.Loading) Shared.LEAGUES_IDS.forEach { leagueId -> when(val responseState = getLeague(leagueId)) { is ResponseState.Success -> { responseState.data?.body()?.let { league -> _leaguesList.add(league) // 发布不可变列表,触发观察者更新 _leaguesLiveData.postValue(ResponseState.Success(_leaguesList.toList())) // 处理赛事逻辑... } } is ResponseState.Error -> { _leaguesLiveData.postValue(ResponseState.Error(app.getString(R.string.unknown_error))) } else -> {} } } } else { _leaguesLiveData.postValue(ResponseState.Error(app.getString(R.string.unable_to_connect))) } } } suspend fun getLeague(id: Int): ResponseState<Response<Leagues>> = viewModelScope.async(Dispatchers.IO) { return@async try { val response = remoteRepository.getLeague(id) ResponseState.Success(response) } catch (exception: Exception) { handleLeaguesException(exception) ResponseState.Error(app.getString(R.string.unknown_error)) } }.await() // 其他省略代码 }
2. 修改观察者逻辑,避免重复添加Tab
在Fragment/Activity中,收到列表更新时先清空已有Tab,再重新添加所有项,确保每次都是完整且唯一的Tab集合:
footBallViewModel.leaguesLiveData.observe(viewLifecycleOwner) { state -> when(state) { is ResponseState.Success -> { // 清空已有Tab,避免重复 tabLayout.removeAllTabs() state.data.forEach { leagueData -> TabItemBinding.inflate(layoutInflater).apply { this.league = leagueData.response?.get(0)?.league val tab = tabLayout.newTab().setCustomView(this.root) tabLayout.addTab(tab) val animation = AnimationUtils.loadAnimation(context, R.anim.slide_in) tab.customView?.startAnimation(animation) } } } is ResponseState.Error -> { // 处理错误提示 Toast.makeText(context, state.message, Toast.LENGTH_SHORT).show() } is ResponseState.Loading -> { // 可选:显示加载进度 } } }
3. 额外优化:避免旋转时重复请求API
ViewModel的生命周期独立于Activity/Fragment,设备旋转时不会重新创建,因此init块中的API请求只会执行一次。如果需要根据网络状态重新加载,可以在onViewCreated中判断列表是否为空,再触发加载逻辑(通常无需额外操作)。
内容的提问来源于stack exchange,提问作者Ahmed Gamal
相关产品推荐
相关产品推荐

