Android:FragmentStatePagerAdapter切换Tab后Fragment重叠问题咨询
解决TabLayout+FragmentStatePagerAdapter的Fragment重叠问题
嘿,这个坑我之前做Tab页的时候也踩过!咱们先唠唠为啥会出现这种情况:
FragmentStatePagerAdapter默认的offscreenPageLimit是1,意思是只会保留当前Tab左右各1个Fragment的实例。当你从第一个Tab切到第三个Tab时,第一个Fragment已经超出了缓存范围,会被销毁(调用onDestroyView甚至onDestroy),但你的AsyncTask还拿着这个已销毁Fragment的引用,当它完成数据更新时,还是会去操作这个旧Fragment的ListView;而等你切回第一个Tab时,ViewPager会重新创建一个全新的第一个Fragment实例,这两个Fragment的视图就叠加在一起了,就出现了你看到的两个列表重叠的情况。
下面给你几个实用的解决方案,按优先级排序:
1. 调整ViewPager的缓存页数
最简单直接的方法,把ViewPager的offscreenPageLimit设为2,这样三个Tab对应的Fragment都会被缓存起来,不会被销毁:
viewPager.setOffscreenPageLimit(2);
这个方法适合Tab数量少的场景(比如你这种3个Tab的),缺点是如果Tab多了会占用更多内存,但你的情况完全没问题。
2. 规范AsyncTask与Fragment的生命周期绑定
别让AsyncTask“游离”在Fragment生命周期之外,要在Fragment销毁时及时取消任务,并且更新UI前检查Fragment的状态:
- 在Fragment里保存AsyncTask的实例:
private YourAsyncTask mAsyncTask; - 在Fragment的
onDestroy或onDestroyView方法里取消任务:@Override public void onDestroy() { super.onDestroy(); if (mAsyncTask != null && !mAsyncTask.isCancelled()) { mAsyncTask.cancel(true); } } - 在AsyncTask的
onPostExecute里先判断Fragment是否还处于活跃状态:@Override protected void onPostExecute(List<Data> result) { // 检查Fragment是否还附着在Activity上 if (!isAdded() || getActivity() == null) { return; } // 再更新ListView mAdapter.addAll(result); mAdapter.notifyDataSetChanged(); }
3. 用ViewModel统一管理数据(推荐)
这是更优雅的方式,把数据从Fragment里剥离出来,放到ViewModel中,不管Fragment销毁重建,数据都是统一的:
- 创建一个ViewModel类,持有数据和LiveData:
public class TabDataViewModel extends ViewModel { private MutableLiveData<List<Data>> mDataList = new MutableLiveData<>(); public void addData(List<Data> newData) { List<Data> currentList = mDataList.getValue(); if (currentList == null) { currentList = new ArrayList<>(); } currentList.addAll(newData); mDataList.setValue(currentList); } public LiveData<List<Data>> getDataList() { return mDataList; } } - 在Fragment中获取ViewModel,并观察数据变化:
@Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); TabDataViewModel viewModel = new ViewModelProvider(this).get(TabDataViewModel.class); viewModel.getDataList().observe(this, dataList -> { // 更新ListView mAdapter.clear(); mAdapter.addAll(dataList); mAdapter.notifyDataSetChanged(); }); } - AsyncTask直接更新ViewModel里的数据,不用再持有Fragment引用:
// 在合适的地方(比如Activity或ViewModel自身)执行AsyncTask new YourAsyncTask() { @Override protected void onPostExecute(List<Data> result) { viewModel.addData(result); } }.execute();
这种方式从根源上避免了Fragment重建导致的数据和视图不一致问题,也是Google推荐的Jetpack组件用法。
4. 检查ListView的更新逻辑
最后再确认一下你的ListView适配器更新逻辑,确保每次添加新数据时是基于统一的数据源,比如每次更新前先清空旧数据再添加:
mAdapter.clear(); // 先清空 mAdapter.addAll(newData); // 再添加新数据 mAdapter.notifyDataSetChanged();
虽然你的问题主要是Fragment重叠,但规范的数据源更新也能避免一些奇怪的视图问题。
内容的提问来源于stack exchange,提问作者Neo1989

