ViewPager.setOffscreenPageLimit(0)的效果与期望不符。

108

我在我的ViewPager实例中使用的片段相当耗费资源,因此我只希望一次加载一个。 当我尝试以下操作时:

mViewPager.setOffscreenPageLimit(0);
mViewPager.setAdapter(mPagerAdapter);

当我调用 mViewPager.setOffscreenPageLimit(1) 时,我的FragmentStatePagerAdapter.getItem(int position)覆盖函数被调用了三次。我原本期望它只会被调用一次,因为我指定了0个离屏页面。

我认为我的调用是正确的,因为如果我调用mViewPager.setOffscreenPageLimit(2)FragmentStatePagerAdapter.getItem(int position) 就会被调用5次,这是我所期望的。

是否ViewPager需要至少1个离屏页面,或者我在这里做错了什么?


9
我已经提交了一个功能请求:https://code.google.com/p/android/issues/detail?id=56667 - Mark
https://dev59.com/RmIk5IYBdhLWcg3wn_j1#44405015 - Ronak Thakkar
我只有3个选项卡,设置ViewPager.setOffscreenPageLimit(2)对我有效。尝试使用ViewPager.setOffscreenPageLimit(totTabs - 1) - sibin
13个回答

126

我发现最好的方法是setUserVisibleHint函数,将其添加到你的fragment中。

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (isVisibleToUser) {
        // load data here
    }else{
       // fragment is no longer visible
    }
}

1
谢谢!在加入这段代码之后,我第一次得到了 NPL。我只是添加了 try catch 块,现在它对我来说完美运行! - Bhavin Chauhan
1
使用它时,它不会在第二个ViewPager片段上加载日期,在第二个片段上它正在加载第三个片段的数据。有人能帮我吗? - Arshad
我有什么遗漏吗?setUserVisibleHint在onCreateView之前被调用了。 - Anton Makov
3
最好的解决方案现在已经过时。 - M.Sameer
@M.Sameer,setUserVisibleHint的替代方案已经过时了吗? - Yudi karma

118

ViewPager是否需要至少1个离屏页?

是的。如果我正确阅读源代码,你应该在LogCat中收到有关此警告的信息,类似于:

Requested offscreen page limit 0 too small; defaulting to 1

8
这不是一个解决方案!我只想加载一个片段而不是两个。这肯定有办法实现吧? - HGPB
16
@Haraldo:“这肯定有办法实现吧?”-- 不可以使用ViewPagerViewPager创建这些片段使得视图存在,以便用户可以在它们之间轻扫,并显示旧视图滑动离开屏幕和新视图滑动进入屏幕的动画效果。你可以尝试编写自己的ViewPager,以实现在不存在的物品之间滑动。您可以在https://code.google.com/p/android/issues/detail?id=56667#c3了解更多信息。 - CommonsWare
2
是的,我已经阅读了这个问题。它只是不合逻辑。我期望能够将 setOffscreenPageLimit(0) 设置为 0。然后我可以自己处理后果。例如,默认的空片段可以作为占位符,直到加载完自己的片段。所有这些都可以在 UI 线程之外进行。无论如何,我还没有仔细考虑过这个问题。 - HGPB
5
“默认空片段可以作为占位符,例如直到加载完成之前的某个片段” - 你现在可以在你的片段中使用一个占位符,使用现有的“ViewPager”实现,当真正内容可用时,你可以将其替换。 - CommonsWare
2
onPageChangeListener 似乎是解决方案。 - alicanbatur
显示剩余5条评论

9

You can try like this :

public abstract class LazyFragment extends Fragment {
    protected boolean isVisible;
    /**
     * 在这里实现Fragment数据的缓加载.
     * @param isVisibleToUser
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if(getUserVisibleHint()) {
            isVisible = true;
            onVisible();
        } else {
            isVisible = false;
            onInvisible();
        }
    }
    protected void onVisible(){
        lazyLoad();
    }
    protected abstract void lazyLoad();
    protected void onInvisible(){}

protected abstract void lazyLoad();
protected void onInvisible(){}

最佳答案! - Radesh

7

首先添加

   boolean isFragmentLoaded = false;

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (isVisibleToUser && !isFragmentLoaded) {
        //Load Your Data Here like.... new GetContacts().execute();

        isFragmentLoaded = true;
    }
else{
     }
}

7
但是这个方法在onCreateView之前被调用。 - Zar E Ahmer

3

虽然这可能是一个旧的线程,但这对我来说似乎是可行的。覆盖此函数:

@Override
public void setMenuVisibility(boolean menuVisible) { 
    super.setMenuVisibility(menuVisible);

    if ( menuVisible ) {
        /**
         * Load your stuffs here.
         */
    } else  { 
        /**
         * Fragment not currently Visible.
         */
    } 
}

愉快的编码...


WebView上有什么?我建议你为此创建一个新线程。 - ralphgabb

2

在我的情况下,我想要在视图中启动一些动画,但使用setUserVisibleHint时遇到了一些问题...

我的解决方案是:

1/ 为您的适配器添加addOnPageChangeListener:

mViewPager.addOnPageChangeListener(this);

2/ 实现 OnPageChangeListener :

public class PagesFragment extends Fragment implements ViewPager.OnPageChangeListener

3/ 覆盖三个方法:

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
{

}

@Override
public void onPageSelected(int position)
{ 
}

@Override
public void onPageScrollStateChanged(int state)
{
}

4/ 在你的类中声明并初始化这个变量

private static int mTabState = 1;

注意:我在我的适配器中有三个片段,并使用mTabState进行setCurrentItem和当前适配器位置的操作,以识别哪个片段正在向用户显示...

5/ 在onPageSelected方法中添加以下代码:

if (mTabState == 0 || position == 0)
    {
        Intent intent = new Intent("animation");
        intent.putExtra("current_position", position);
        LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
    }

如果前一页或当前页是页面0(位置0的片段),则执行以下操作。
现在,在您的片段类中(适配器中位置0的片段),您必须创建广播接收器,并在onResume方法中注册它,并在onPause方法中取消注册。
BroadcastReceiver broadcastReceiver = new BroadcastReceiver()
{
    @Override
    public void onReceive(Context context, Intent intent)
    {
        if (Objects.equals(intent.getAction(), "animation"))
        {
            int currentPosition = intent.getIntExtra("current_position", 0);
            if (currentPosition == 0)
            {
                startAnimation();
                setViewsVisible();
            } else
            {
                setViewsInvisible();
            }
        }
    }
};

@Override
public void onResume()
{
    super.onResume();
    LocalBroadcastManager.getInstance(mContext).registerReceiver(broadcastReceiver, new IntentFilter("animation"));
}

@Override
public void onPause()
{
    super.onPause();
    LocalBroadcastManager.getInstance(mContext).unregisterReceiver(broadcastReceiver);
}

概述:我有一个Fragment Pager Adapter,其中显示了三个片段。我想在适配器中的位置0的片段上显示一些视图动画。为此,我使用BroadcastReceiver。当选取片段时,我启动动画方法并向用户显示视图。当片段不对用户可见时,我尝试将视图隐藏...


2

ViewPager默认会加载下一页(Fragment),这是无法通过setOffscreenPageLimit(0)更改的。但你可以进行一些hack操作。你可以在包含ViewPager的Activity中实现onPageSelected函数。在下一个不想加载的Fragment中,你编写一个名为showViewContent()的函数,将其中所有耗费资源的初始化代码放入,并在onResume()方法之前什么都不做。然后在onPageSelected中调用showViewContent()函数。希望这能帮到你。


2

仅使用一个页面的ViewPager:

这是2021年2月:我已经能够使用ViewPager、FragmentPagerAdapter、Tablayout和Fragment来添加一个页面。在我的情况下,我可以使用许多选项卡填充许多页面,或者只使用一个选项卡填充一个页面。当只有一个选项卡和一个页面时,向左或向右滑动可以管理文档的章节(我想要显示下一章)。而当有许多页面和许多选项卡时,我可以更改整本书的文档。

在主Activity Oncreate中(这是我的工作代码方法,我没有改变任何东西):

if(getIntent()!=null){
            if(getIntent().getStringExtra("ONLY_TAFHEEM")!=null)
             sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager(), suraName, suraId, ayatId, true);
            else
            sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager(), suraName, suraId, ayatId, false);
        }else {
            sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager(), suraName, suraId, ayatId, false);
        }

        final ViewPager viewPager = findViewById(R.id.view_pager);
        viewPager.setAdapter(sectionsPagerAdapter);
        tabsLayout = findViewById(R.id.tabs);
        tabsLayout.animate();

        tabsLayout.setupWithViewPager(viewPager);

在适配器中:

 @NonNull
    @Override
    public Fragment getItem(int position) {

          
    // getItem is called to instantiate the fragment for the given page.
    // Return a PlaceholderFragment (defined as a static inner class below).
    return PlaceholderFragment.sendData(mContext, postion, suraName, suraId, ayahId, ARABIC_AYAH, BENGALI_AYAH, actualDbNames[position], tafsirDisplayNames[position]);
}


@Nullable
@Override
public CharSequence getPageTitle(int position) {
   
    return tafsirDisplayNames[position];
}

@Override
public int getCount() {
   // this is the tricky part // Show pages according to array length. // this may only one // this is the tricky part : 
    return tafsirDisplayNames.length;
}

最后是碎片公共构造函数:

 public static PlaceholderFragment sendData(Context mContext, int tabIndex, String suraName, String suraId, String ayahNumber, String arabicAyah, String bengaliAyah, String actualDbName, String displayDbName) {

        Log.i("PlaceHolder", "Tafhim sendData: " + bengaliAyah);
        PlaceholderFragment fragment = new PlaceholderFragment();
        Bundle bundle = new Bundle();
        mContext_ = mContext;
        BENGALI_AYAH = bengaliAyah;
        _DISPLAY_DB_NAME = displayDbName;
        bundle.putInt(ARG_SECTION_NUMBER, tabIndex);
        bundle.putString(SURA_NAME, suraName);
        bundle.putString(SURA_ID, suraId);
        bundle.putString(AYAH_NUMBER, ayahNumber);
        bundle.putString(ARABIC_AYAH, arabicAyah);
        bundle.putString(ACTUAL_DB_NAME, actualDbName);
        bundle.putString(DISPLAY_DB_NAME, displayDbName);
        fragment.setArguments(bundle);
        return fragment;
    }

只需要将标签数组(选项卡标签)传递给适配器即可(如果只有一个元素,则仅为一页),根据我的需求,我可以填充一个或多个页面,并根据此填充一个或多个选项卡:在上面的代码中,数组是:tafsirDisplayNames。当适配器首次调用时,我也可以手动创建数组,或在重新创建MainActivity时使用+-元素重新创建数组。


1
请尝试使用此代码解决Viewpager中刷新视图的问题...
/* DO NOT FORGET! The ViewPager requires at least “1” minimum OffscreenPageLimit */
int limit = (mAdapter.getCount() > 1 ? mAdapter.getCount() - 1 : 1);
mViewPager.setOffscreenPageLimit(limit);

0

使用以下代码 // 创建用于获取数据的布尔值 private boolean isViewShown = false;

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (getView() != null) {
        isViewShown = true;
        // fetchdata() contains logic to show data when page is selected mostly asynctask to fill the data
        fetchData();
    } else {
        isViewShown = false;
    }
} 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接