Android ViewPager2切换Tab后RecyclerView不显示更新列表问题
TabLayout+ViewPager2切回Tab后RecyclerView数据回退问题
问题现象
- 技术栈:
TabLayout + ViewPager2实现多分类页签,每个页签通过RecyclerView展示对应分类列表 - 当前页签内执行增删操作、调用
notifyDataSetChanged()时,停留当前Tab可见正常更新效果 - 切换到其他Tab再返回原Tab时,
RecyclerView会回退展示Fragment初始化时的原始旧数据 - 调试确认Fragment内部存储的
dataItems变量已经是增删后的最新值,但界面始终渲染旧条目 - 注释掉SearchView的
onQueryTextChange过滤逻辑后,问题完全消失
问题相关代码
自定义FragmentStateAdapter实现
public class DataListAdapter extends FragmentStateAdapter { private DataListFragment[] fragments; // 其余业务代码省略 @Override public Fragment createFragment(int position) { ArrayList<DataItem> dataItems; // 实际逻辑根据条件返回不同分类数据 dataItems = dbAdapter.getDataItems(); DataListFragment frag = new DataListFragment(dataItems); fragments[position] = frag; return frag; } }
Fragment构造方法
DataListFragment(ArrayList<DataItem> dataItems) { this.dataItems.addAll(dataItems); }
Tab选中监听逻辑
dlBinding.tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { dlAdapter.refreshFragmentDataItems(tab.getPosition()); } @Override public void onTabUnselected(TabLayout.Tab tab) {} @Override public void onTabReselected(TabLayout.Tab tab) {} });
Adapter数据刷新方法
public void refreshFragmentDataItems(int position) { ArrayList<DataItem> dataItems; dataItems = dbAdapter.getRecentDataItems(null); fragments[position].setDataItems(dataItems); }
Fragment数据更新方法
public void setDataItems(ArrayList<DataItem> dataItems) { this.dataItems.clear(); this.dataItems.addAll(dataItems); recyclerAdapter.notifyDataSetChanged(); }
搜索过滤逻辑
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { InputMethodManager inm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inm.hideSoftInputFromWindow(searchView.getWindowToken(), 0); dlAdapter.getFragment(dlBinding.tabs.getSelectedTabPosition()).filterAdapter(query); return true; } @Override public boolean onQueryTextChange(String newText) { // 注释下方行后问题消失 dlAdapter.getFragment(dlBinding.tabs.getSelectedTabPosition()).filterAdapter(newText); return true; } });
过滤调用入口
public void filterAdapter(String query) { recyclerAdapter.getFilter().filter(query); }
根因分析
问题由三处不规范写法叠加导致,搜索过滤逻辑是直接触发点:
- 自行缓存Fragment实例违反
FragmentStateAdapter设计逻辑。FragmentStateAdapter会自行管理Fragment的创建、销毁、重建全流程,离屏Fragment可能被销毁视图、甚至回收实例,自定义的fragments数组很容易留存已经和界面解绑的旧Fragment实例,后续调用旧实例的更新方法,不会作用到当前可见的真实界面上。 - 给Fragment定义带参数构造方法是Android开发典型错误。系统在重建Fragment(ViewPager2回收恢复、屏幕旋转、进程后台被杀恢复等场景)时只会反射调用Fragment的无参构造,通过构造方法传入的参数会直接丢失,导致重建时加载回初始旧数据。
- 核心触发点是RecyclerView Adapter的Filter实现不规范:多数开发者写过滤逻辑时没有分离全量原始数据源和过滤后展示数据源,只在Adapter里维护一份列表,每次过滤直接修改这份列表。这种写法下,Fragment层持有的
dataItems和Adapter内部实际使用的列表是两个完全独立的对象:- 停留当前Tab时,更新Fragment的
dataItems后同步调用刷新,Adapter暂时用新数据渲染,表现正常 - 切走Tab再返回时,Fragment视图重建,加上SearchView的
onQueryTextChange会自动触发一次过滤,Filter逻辑从Adapter自己持有的旧原始列表生成展示数据,直接覆盖掉之前更新的内容,界面就回退到旧数据状态。
- 停留当前Tab时,更新Fragment的
修复方案
- 移除自定义的Fragment缓存数组,不要自行维护Fragment引用。需要获取指定位置的Fragment时,调用
FragmentStateAdapter提供的findFragmentByPosition()方法获取当前真实挂载的实例,避免更新到废弃对象。 - 删掉Fragment的带参构造方法,改用
Bundle传递初始参数,避免系统重建时数据丢失:// 正确的Fragment实例创建方式 public static DataListFragment newInstance(ArrayList<DataItem> initialData) { DataListFragment frag = new DataListFragment(); Bundle args = new Bundle(); args.putParcelableArrayList("initial_data", initialData); frag.setArguments(args); return frag; } // 在onCreate生命周期中读取参数 @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { ArrayList<DataItem> initialData = getArguments().getParcelableArrayList("initial_data"); dataItems.addAll(initialData); } } - 重构RecyclerView Adapter的Filter实现,严格分离全量原始数据和过滤展示数据,保证数据源唯一:
public class DataListRecyclerAdapter extends RecyclerView.Adapter<DataListRecyclerAdapter.VH> implements Filterable { // 全量原始数据,永远存储最新的未过滤完整数据 private final List<DataItem> originalList = new ArrayList<>(); // 当前界面展示的数据,仅在过滤结果返回后修改 private final List<DataItem> displayList = new ArrayList<>(); // 记录当前搜索关键字,数据更新后自动复现过滤状态 private String currentQuery = ""; // 全量数据更新唯一入口 public void updateFullData(List<DataItem> newData) { originalList.clear(); originalList.addAll(newData); // 数据更新后自动应用当前过滤条件 getFilter().filter(currentQuery); } @NonNull @Override public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { // 正常实现ViewHolder创建逻辑 } @Override public void onBindViewHolder(@NonNull VH holder, int position) { // 绑定数据永远从displayList取值 DataItem item = displayList.get(position); // 绑定视图逻辑 } @Override public int getItemCount() { return displayList.size(); } @Override public Filter getFilter() { return new Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { List<DataItem> filterResult = new ArrayList<>(); currentQuery = constraint == null ? "" : constraint.toString().trim(); // 永远从originalList做过滤,不要使用displayList if (TextUtils.isEmpty(currentQuery)) { filterResult.addAll(originalList); } else { String key = currentQuery.toLowerCase(Locale.getDefault()); for (DataItem item : originalList) { if (item.getContent().toLowerCase(Locale.getDefault()).contains(key)) { filterResult.add(item); } } } FilterResults results = new FilterResults(); results.values = filterResult; results.count = filterResult.size(); return results; } @SuppressWarnings("unchecked") @Override protected void publishResults(CharSequence constraint, FilterResults results) { displayList.clear(); displayList.addAll((List<DataItem>) results.values); notifyDataSetChanged(); } }; } static class VH extends RecyclerView.ViewHolder { // ViewHolder实现 public VH(@NonNull View itemView) { super(itemView); } } } - 调整数据更新逻辑:Fragment不再单独维护一份
dataItems列表,所有数据更新直接调用Adapter的updateFullData()方法,保证数据源唯一,避免多份数据不一致。
内容的提问来源于stack exchange,提问作者Shahid Thaika
相关产品推荐
相关产品推荐

