使用ViewPager和TabLayout切换标签时Fragment不可见且视图不更新
嘿,我看你遇到了ViewPager+TabLayout切换Fragment时的视图更新问题,核心原因其实是你同时用了两种Fragment管理方式——ViewPager的FragmentPagerAdapter已经负责创建和管理每个标签对应的Fragment实例,但你又在Activity里手动执行了add/replace Fragment的事务,这就导致你操作的Fragment和ViewPager实际显示的Fragment根本不是同一个实例,自然会出现数据能打印但视图不更新、可见性异常的情况!
第一步:移除Activity中手动管理Fragment的冗余代码
你的StepActivity里,不需要手动去add或replace Fragment,FragmentPagerAdapter会自动根据ViewPager的位置创建和管理Fragment实例。修改后的Activity代码应该是这样:
public class StepActivity extends AppCompatActivity { ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_step); // 只保留ViewPager和Adapter的初始化代码 fragmentSelectAdapter = new StepFragmentSelectAdapter(getSupportFragmentManager(), this, steps, recipe); mViewPager.setAdapter(fragmentSelectAdapter); mTabLayout.setupWithViewPager(mViewPager); // 删掉下面这段手动管理Fragment的冗余代码! // stepFragment = (StepFragment) getSupportFragmentManager().findFragmentById(R.id.step_container); // if(stepFragment == null) { // stepFragment = StepFragment.newInstance(step, recipe); // stepFragment.setArguments(bundle); // getSupportFragmentManager().beginTransaction() // .add(R.id.step_container, stepFragment) // .commit(); // } else { // stepFragment = StepFragment.newInstance(step, recipe); // stepFragment.setArguments(bundle); // getSupportFragmentManager().beginTransaction() // .replace(R.id.step_container, stepFragment) // .commit(); // } } }
第二步:优化Fragment的视图更新逻辑
onCreateView只有在Fragment第一次创建视图时才会调用,当ViewPager切换标签时,如果Fragment已经被缓存(ViewPager默认预加载左右各一个Fragment),onCreateView不会再次执行。所以你需要在Fragment可见时更新视图,比如在onViewCreated或者onResume方法里处理:
public class StepFragment extends Fragment { private TextView tvStepDescription; private Step step; private String mDescription; private static final String SELECTED_STEP = "selected_step"; public static StepFragment newInstance(Step step, Recipe recipe) { StepFragment fragment = new StepFragment(); Bundle args = new Bundle(); args.putParcelable(SELECTED_STEP, step); // 可以把recipe也放进参数里 fragment.setArguments(args); return fragment; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.step_fragment, container, false); ButterKnife.bind(this, view); return view; } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // 在视图创建完成后更新数据 updateStepData(); } @Override public void onResume() { super.onResume(); // 每次Fragment可见时,再次检查数据并更新视图 updateStepData(); } private void updateStepData() { if (getArguments() != null) { step = getArguments().getParcelable(SELECTED_STEP); if (step != null) { mDescription = step.getDescription(); Log.e(TAG, "updateStepData: "+mDescription); tvStepDescription.setText(mDescription); } } } }
第三步:处理Adapter的数据更新(如果需要动态修改数据)
如果后续你需要更新steps列表的数据,记得调用Adapter的notifyDataSetChanged(),并且重写FragmentPagerAdapter的getItemPosition方法,让Adapter重新创建Fragment实例(默认情况下Adapter会缓存Fragment,不更新实例):
public class StepFragmentSelectAdapter extends FragmentPagerAdapter { private List<Step> steps; private Recipe recipe; ... // 提供更新数据的方法 public void updateSteps(List<Step> newSteps) { this.steps = newSteps; notifyDataSetChanged(); } @Override public Fragment getItem(int position) { Log.e(TAG, "getItem: \nDecr: "+steps.get(position).getDescription()+"\nVideo: "+steps.get(position).getVideoURL()+"\nImage: "+steps.get(position).getThumbnailURL()); return StepFragment.newInstance(steps.get(position), recipe); } @Override public int getCount() { return steps != null ? steps.size() : 0; } // 重写这个方法,让Adapter知道所有位置的Fragment都需要重新创建 @Override public int getItemPosition(@NonNull Object object) { return POSITION_NONE; } }
关于Fragment可见性和生命周期的说明
- ViewPager默认会预加载当前Fragment左右各一个的Fragment,所以这些预加载的Fragment虽然已经创建,但处于不可见状态。你可以通过
setUserVisibleHint(boolean isVisibleToUser)方法来判断Fragment是否真正对用户可见(不过这个方法在AndroidX的Fragment里已经被onResume/onPause结合isVisible()替代了)。 - 你之前遇到的“Fragment始终不可见”,就是因为你手动添加的Fragment并没有被ViewPager显示,ViewPager显示的是Adapter创建的Fragment,而你检查的是手动添加的那个实例的可见性,自然不对。
这样修改后,ViewPager会正确管理每个Fragment的生命周期,切换标签时Fragment会正常显示为可见,视图也会随着数据更新而刷新啦!
内容的提问来源于stack exchange,提问作者sbj1811

