按需加载片段 UI



问题:

我目前遇到一个问题,我的应用程序在第一次打开时试图加载太多片段。

BottomNavigationView了加载 4 个片段的ViewPager- 每个Fragment都包含TabLayoutViewPager加载至少2 个片段。

可以想象,这是大量的UI渲染(10+片段) - 特别是当其中一些片段包含繁重的组件,如日历,条形图等时。

目前提出的解决方案:

在需要片段时控制 UI 加载 - 因此,在用户首次访问该片段之前,没有理由加载它。

似乎绝对有可能,因为包括Play商店在内的许多应用程序都在这样做。请在此处查看示例

在上面的视频示例中 - UI 组件在导航到选项卡完成后加载。它甚至有一个嵌入的加载符号。

1)我正在尝试弄清楚如何做到这一点 - 在什么时候我会知道需要创建这个片段UI而不是已经创建它?

2) 另外,我将在其中启动 UI 创建过程的片段生命周期回调是什么?onResume()意味着 UI 对用户可见,因此加载 UI 将出现滞后和延迟。

希望这足够清楚。


编辑:
我已经使用FragmentStatePagerAdapter作为ViewPager适配器。我注意到构造函数中的super(fm)方法现在已弃用:

ViewPagerAdapter(FragmentManager fm) {
super(fm); // this is deprecated
}

所以我把它改成:

ViewPagerAdapter(FragmentManager fm) {
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
}

BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT:表示只有当前片段将处于生命周期.状态.已恢复状态。所有其他片段的上限为 Lifecycle.State.STARTED。

这似乎很有用,因为只有当用户看到Fragment时,才会调用FragmentonResume()。那么我可以以某种方式使用此指示来加载 UI 吗?

您的应用程序在启动时加载多个片段的原因很可能是一次初始化它们。相反,您可以在需要时初始化它们。然后使用show\hide从窗口attach\detach,而无需重新膨胀整个布局。

简单解释:用户点击BottomNavigationView项目后,您将创建片段。在单击的项目上,您将检查是否未创建和添加片段,然后创建并添加它。如果已创建,则使用show()方法显示已可用的片段,并使用hide()隐藏BottomNavigationView的所有其他片段。

根据您的情况show()/hideadd()/replace更好,因为正如您所说,当您想要显示它们时,您不想重新膨胀片段

public class MainActivity extends AppCompatActivity{ 
FragmentOne frg1;
FragmentTwo frg2;
@Override
public boolean onNavigationItemSelected(MenuItem item){
switch(item.getId()){
case R.id.fragment_one:
if (frg2 != null && frg2.isAdded(){
fragmentManager.beginTransaction().hide(frg2).commit();
}
if(frg1 != null && !frg1.isAdded){
frg1 = new FragmenOne();
fragmentManager.beginTransaction().add(R.id.container, frg1).commit();
}else if (frg1 != null && frg1.isAdded) {
fragmentManager.beginTransaction().show(frg1).commit();
}
return true;
case R.id.fragment_two:
// Reverse of what you did for FragmentOne
return true;
}
}
}

对于您的ViewPager,您可以从您所指的示例中看到;PlayStore 正在使用setOffscreenPageLimit。这将允许您选择应保持活动状态的View数量,否则将被销毁并从头开始创建,遍历片段的所有生命周期事件(如果视图是片段)。在PlayStore应用程序的情况下,这可能是4-5,这就是为什么当您重新选择"编辑选择"选项卡时它再次开始加载的原因。如果您执行以下操作,则仅选定且相邻(右侧一个)片段将处于活动状态,屏幕外的其他片段将被销毁。

public class FragmentOne extends Fragment{ 
ViewPager viewPager;
@Override
public void onCreateView(){
viewPager = .... // Initialize
viewpAger.setOffscreenPageLimit(1); // This will keep only 2 Fragments "alive"
}
}

回答这两个问题

如果您使用show/hide则无需知道何时膨胀视图。它将自动处理并且不会滞后,因为它只是attaching/detaching视图而不会膨胀。

这取决于您在活动中初始化片段的方式。可能是您正在onCreate活动的方法中初始化所有片段,而不是在选择项目时初始化它BottomNavigation如下所示:

Fragment one,two,three,four;
@Override
public boolean onNavigationItemSelected(MenuItem item){
Fragment fragment;

switch(item.getId()){
case R.id.menu_one:{
if(one==null)
one = Fragment()
fragment = one;
break;
}

case R.id.menu_two:{
if(two==null)
two = Fragment()
fragment = two;
break;
}

}
getFragmentManager().beginTransaction().replace(fragment).commit();
}

要确定一次在查看寻呼机中加载多少页,您可以使用:setOffscreenPageLimit.

viewPager.setOffscreenPageLimit(number)

请尝试此。

我使用的是相同类型的应用程序,有多个选项卡,并且选项卡有多个内部选项卡。

我使用了ViewPager方法的概念,其中有一个onPageSelected()方法,对于该方法,我们得到了页面位置。

通过使用此位置,我们正在检查当前片段,并调用我们在该片段中创建的自定义方法,例如在该片段中定义的 onPageSelected()。

在片段中的PageSelected()上使用此自定义方法,我们检查了列表是否可用,如果列表有数据,那么我们不会调用Api,否则我们将调用Api并加载该列表。

我认为如果您的选项卡具有内部选项卡或查看页,则可以遵循相同的要求,因此,如果您当前的视图页方法片段onpageSelected在当时调用了您的查看页片段片段初始化。

您必须调用初始化,例如需要在onCreate()方法中调用数据绑定或视图初始化,并且其他列表附件和api调用由自定义方法onPageSelected管理,该方法将根据ViewPager在PageSelected上调用。

如果您需要任何帮助,请告诉我。

您可以尝试仅在ViewPager中将Fragments 与FrameLayouts 一起使用。实际的Fragment可以在onResume()中添加到FrameLayout(在检查此Fragment是否尚未附加后)。如果BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT按预期工作,它应该可以工作。

我建议您在需要时使用BottomNavigationView.setOnNavigationItemSelectedListener在片段UI之间切换。

navigationView.setOnNavigationItemSelectedListener(item -> {
switch(item.getItemId()) {
case R.id.item1:
// you can replace the code findFragmentById() with findFragmentByTag("dashboard");
// if you only have one framelayout to hold the fragment
fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment == null) {
fragment = new ExampleFragment();
// if the fragment is identified by tag, add another 
// argument to this method: 
//   replace(R.id.fragment_container, fragment, "dashboard")
getSupportFragmentManager().begintransaction()
.replace(R.id.fragment_container, fragment)
.commit();
}
break;
}
}

这个想法很简单,当用户滑动或选择不同的选项卡时,可见的片段将被新片段替换。

只需一个接一个地加载片段。创建包含许多占位符和存根的主片段布局,然后按您喜欢的顺序加载它们。 在主片段加载后使用 FragmentTransaction.replace()。

您是否尝试过片段的setUserVisibleHint()方法

override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if(isVisibleToUser){
// Do you stuff here
}
}

仅当片段对用户可见时,才会调用此内容

你只维护一个ViewPager怎么样?听起来很疯狂?在这种情况下,您只需在底部选项卡之间切换时更改PagerAdapter的数据集。让我们看看如何实现这一点,

如您所提到的,您有 4 个片段,每个片段都分配给底部导航视图的单个选项卡。每个都执行一些冗余工作,即持有具有选项卡布局的viewPager并设置相同类型的适配器。因此,如果我们可以将这 4 个冗余任务合并为一个,那么我们将能够摆脱 4 个片段。由于只有一个适配器只有一个viewPager,因此如果我们将offScreenPageLimit片段加载计数设置为 1,我们将能够将片段加载计数从 ~10 减少到 2。让我们看一些例子,

活动.xml应如下所示

<LinearLayout>
<TabLayout />
<ViewPager />
<BottomNavigationView />
</LinearLayout>

它是可选的,但我建议使用抽象方法创建一个基本的PagerFragment抽象类getTabTitle()

public abstract class PagerFragment extends Fragment {
public abstract String getTabTitle();
}

现在是时候制作我们的寻呼机适配器类了

public class SectionsPagerAdapter extends FragmentStatePagerAdapter {
public Map<Integer, List<PagerFragment>> map = ...; // If you are concerned about memory then I could recommend to store DataObject instead of PagerFragment and instantiate fragment on demand using that data.
public int currentTabId = R.id.first_bottom_tab_id;
private List<PagerFragment> getCurrentFragments() {
return map.get(currentTabId);
}
public void setCurrentTabId(int tabId) {
this.currentTabId = tabId;
}
public SectionsPagerAdapter(FragmentManager manager) {
super(manager);
}
@Override
public Fragment getItem(int position) {
return getCurrentFragments().get(position);
}
@Override
public int getCount() {
return getCurrentFragments().size();
}
@Override
public int getItemPosition(@NonNull Object object) {
return POSITION_NONE;
}
@Override
public CharSequence getPageTitle(int position) {
return getCurrentFragments().get(position).getTabTitle();
}
}

最后,在活动中

SectionsPagerAdapter pagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(pagerAdapter);
viewPager.setOffscreenPageLimit(1);
viewPagerTab.setViewPager(viewPager);
bottomNavigationView.setOnNavigationItemSelectedListener(menuItem -> {
pagerAdapter.setCurrentTabId(menuItem.getItemId())
pagerAdapter.notifyDataSetChanged();
viewPagerTab.setViewPager(viewPager);
}

这是基本思想。您可以将自己的一些想法与它混合在一起,以产生出色的结果。让我知道它是否有用?

更新

回答您的问题,

  1. 我认为使用我的解决方案,您可以实现与我在项目中已经完成的视频完全相同的行为。在我的解决方案中,如果您将偏移页限制设置为 1,则只会提前创建相邻片段。因此,片段创建将由适配器和查看页处理,您无需担心。
  2. 我上面的解决方案中,您应该在onCreateView()中创建 UI。
<小时 />

最新更新