Android Fragments 按需加载片段UI

wlwcrazw  于 2023-05-18  发布在  Android
关注(0)|答案(8)|浏览(214)

问题:

我目前遇到了一个问题,我的应用程序在第一次打开时试图加载太多的片段。
我有BottomNavigationViewViewPager,它加载了4个片段-每个Fragment都包含TabLayoutViewPager,以加载至少2个片段。
可以想象,这是一个非常大的UI渲染(10多个片段)--尤其是当这些片段中包含一些重要的组件,如日历、条形图等时。

目前建议的解决方案:

当需要片段时控制UI加载-所以直到用户第一次访问该片段,才有理由加载它。
这似乎是绝对可能的,因为许多应用程序,包括Play商店,正在这样做。请参见示例here
在上面的视频示例中-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.RESUMED状态。所有其他片段都在Lifecycle. State. STARTED处封顶。
这似乎很有用,因为只有当Fragment对用户可见时,才会调用FragmentonResume()。我可以使用这个指示以某种方式加载UI吗?

u2nhd7ah

u2nhd7ah1#

您的应用在启动时加载多个Fragment的原因很可能是您要一次初始化所有Fragment。相反,您可以在需要时初始化它们。然后从窗口使用show`hideattach\detach,而无需重新填充整个布局。 简单解释一下:一旦用户点击BottomNavigationView的项目,您就可以创建Fragment。在单击的项目上,您将检查是否未创建和未添加Fragment,然后创建它并添加。如果它已经创建,则使用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的数量,否则将从开始通过Fragment的所有生命周期事件(如果视图是Fragment)被销毁和创建。在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视图没有膨胀。

fcg9iug3

fcg9iug32#

这取决于您如何在Activity中初始化片段。可能你是在Activity的onCreate方法中初始化所有片段,而不是在选择BottomNavigation item时初始化它,如下所示:

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)

请试试这个。

vmjh9lq9

vmjh9lq93#

我是用同一种应用程序,有多个标签,也标签有多个内部标签。
我使用了ViewPager方法的概念,其中有一个onPageSelected()方法,用于获取页面位置。
通过使用这个位置,我们正在检查当前的Fragment,并调用我们在该片段中创建的自定义方法,如在该片段中定义的onPageSelected()。
使用Fragment中的这个自定义方法onPageSelected(),我们检查列表是否可用,如果列表有数据,那么我们就不调用API,否则我们调用Api并加载该列表。
我认为你有同样的要求,如果你的标签有内部标签或viewpager,你可以遵循同样的概念,所以如果你的viewpager方法onpageSelected的当前片段在你的viewpager片段初始化的时候调用。
你必须调用像数据绑定或视图初始化这样的初始化,需要在onCreate()方法中调用,其他列表附件和API调用将由基于ViewPager onPageSelected调用的自定义方法onPageSelected管理。
让我知道如果你需要任何帮助。

yquaqz18

yquaqz184#

您可以尝试只在ViewPager中拥有Fragment s和FrameLayout s。实际的Fragment s可以添加到onResume()中的FrameLayout(在检查此Fragment是否尚未连接之后)。如果BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT按预期工作,它应该可以工作。

dnph8jn4

dnph8jn45#

我建议您在需要时使用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;
        }
    }

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

ymzxtsji

ymzxtsji6#

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

sy5wg1nm

sy5wg1nm7#

你试过片段的setUserVisibleHint()方法吗

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

只有当用户看到片段时才会调用此函数

7z5jn7bk

7z5jn7bk8#

你只维护一个ViewPager怎么样?听起来很疯狂?在这种情况下,只需在底部选项卡之间切换时更改PagerAdapter的数据集。让我们看看你是怎么做到的
正如您提到的,您有4个片段,每个片段被分配到底部导航视图的一个单独的选项卡。每个都执行一些冗余工作,即拿着一个带有tab布局的viewPager并设置相同类型的适配器。因此,如果我们可以将这4个冗余任务合并为一个,那么我们将能够摆脱4个片段。由于只有一个viewPager和一个适配器,因此如果我们将offScreenPageLimit设置为1,我们将能够将片段加载计数从~10减少到2。让我们看一些例子,

activity.xml应该是这样的

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

这是可选的,但我建议创建一个带有抽象方法getTabTitle()PagerFragment抽象基类

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

现在是时候创建我们的PagerAdapter类了

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.我认为,通过我的解决方案,您可以实现与我在项目中所做的完全相同的视频行为。在我的解决方案中,如果你设置offset page limit为1,那么只有相邻的fragment会被提前创建。因此,片段创建将由适配器和视图页管理器处理,您无需担心它。
1.在我上面的解决方案中,你应该在onCreateView()中创建UI。

相关问题