Android中如何实例化Fragment视图?TabLayout加载异常求助
问题场景
我通过Fragment实现了包含多Tab项的TabLayout,布局代码如下:
<com.google.android.material.tabs.TabLayout android:id="@+id/tabLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/greenish" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/btnSearch" app:tabBackground="@color/light_green" app:tabSelectedTextAppearance="@style/TabSelectedStyle" app:tabSelectedTextColor="@color/dark_green" app:tabTextAppearance="@style/TabStyle" app:tabTextColor="@color/white"> <com.google.android.material.tabs.TabItem android:layout_width="wrap_content" android:layout_height="match_parent" android:text="@string/details_tab_label"> </com.google.android.material.tabs.TabLayout>
每个Tab对应的Fragment布局里有用于展示搜索结果的TextView。当前流程是:Activity启动后显示搜索栏和按钮,执行搜索加载数据后,直接给Fragment内的TextView赋值会触发空指针异常——原因是未切换到对应Tab时,Fragment的布局还没被填充;但如果提前手动切换一遍所有Tab,就能正常赋值。
我曾尝试在Activity的onCreate中用以下代码强制初始化所有Fragment,但这种方式既不合理,还会导致默认激活的Tab始终是最后一个而非第一个:
// Loop and set all tabs so as to inflate their views for(int i = tabLayout.getTabCount() - 1; i >= 0; i--) { viewPager.setCurrentItem(i); try { Thread.sleep(500); } catch (InterruptedException e) { throw new RuntimeException(e); } }
正确实现方案
方案一:设置ViewPager预加载页数
ViewPager默认只会预加载当前Tab左右各1个Fragment,通过设置预加载页数为Tab总数减1,可让所有Fragment在启动时就完成初始化:
viewPager.setOffscreenPageLimit(tabLayout.getTabCount() - 1);
注意:如果Tab数量较多,这种方式会占用更多内存,仅适合Tab数量少的场景。
方案二:通过数据驱动Fragment更新UI(推荐)
不要直接在Activity中操作Fragment的View,而是将搜索数据传递给Fragment,让Fragment在自身View初始化完成后再更新UI,常用两种方式:
- 使用ViewModel共享数据
- 定义一个共享的ViewModel,Activity在加载完搜索数据后更新ViewModel中的数据
- 每个Fragment在
onViewCreated方法中观察ViewModel的数据变化,当数据更新且View已初始化时,直接更新TextView的内容
- 通过Bundle传递数据
- 在创建Fragment实例时,将搜索数据通过
setArguments()存入Bundle - Fragment在
onViewCreated方法中读取Bundle里的数据,更新UI
- 在创建Fragment实例时,将搜索数据通过
这种方式遵循MVVM架构,实现了Activity和Fragment的解耦,也不会有额外内存开销。
方案三:利用Adapter的生命周期方法传递数据
如果使用的是旧版FragmentPagerAdapter,可以重写setPrimaryItem方法,在Fragment被设置为当前项时传递数据:
@Override public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) { super.setPrimaryItem(container, position, object); if (object instanceof YourFragment) { YourFragment fragment = (YourFragment) object; // 传递搜索数据给Fragment fragment.updateData(yourSearchData); } }
不过目前更推荐使用ViewPager2搭配FragmentStateAdapter,这种方法仅适配旧版本场景。
内容的提问来源于stack exchange,提问作者Hussain Akbar

