从 Android 中的 ViewPager 中删除 Fragment Page

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10396321/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 03:33:56  来源:igfitidea点击:

Remove Fragment Page from ViewPager in Android

androidandroid-viewpagerfragmentpageradapter

提问by astuetz

I'm trying to dynamically add and remove Fragments from a ViewPager, adding works without any problems, but removing doesn't work as expected.

我正在尝试从 ViewPager 动态添加和删除 Fragments,添加工作没有任何问题,但删除无法按预期工作。

Everytime I want to remove the current item, the last one gets removed.

每次我想删除当前项目时,最后一个都会被删除。

I also tried to use an FragmentStatePagerAdapter or return POSITION_NONE in the adapter's getItemPosition method.

我还尝试在适配器的 getItemPosition 方法中使用 FragmentStatePagerAdapter 或返回 POSITION_NONE 。

What am I doing wrong?

我究竟做错了什么?

Here's a basic example:

这是一个基本示例:

MainActivity.java

主活动.java

public class MainActivity extends FragmentActivity implements TextProvider {

    private Button mAdd;
    private Button mRemove;
    private ViewPager mPager;

    private MyPagerAdapter mAdapter;

    private ArrayList<String> mEntries = new ArrayList<String>();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mEntries.add("pos 1");
        mEntries.add("pos 2");
        mEntries.add("pos 3");
        mEntries.add("pos 4");
        mEntries.add("pos 5");

        mAdd = (Button) findViewById(R.id.add);
        mRemove = (Button) findViewById(R.id.remove);
        mPager = (ViewPager) findViewById(R.id.pager);

        mAdd.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                addNewItem();
            }
        });

        mRemove.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                removeCurrentItem();
            }
        });

        mAdapter = new MyPagerAdapter(this.getSupportFragmentManager(), this);

        mPager.setAdapter(mAdapter);

    }

    private void addNewItem() {
        mEntries.add("new item");
        mAdapter.notifyDataSetChanged();
    }

    private void removeCurrentItem() {
        int position = mPager.getCurrentItem();
        mEntries.remove(position);
        mAdapter.notifyDataSetChanged();
    }

    @Override
    public String getTextForPosition(int position) {
        return mEntries.get(position);
    }
    @Override
    public int getCount() {
        return mEntries.size();
    }


    private class MyPagerAdapter extends FragmentPagerAdapter {

        private TextProvider mProvider;

        public MyPagerAdapter(FragmentManager fm, TextProvider provider) {
            super(fm);
            this.mProvider = provider;
        }

        @Override
        public Fragment getItem(int position) {
            return MyFragment.newInstance(mProvider.getTextForPosition(position));
        }

        @Override
        public int getCount() {
            return mProvider.getCount();
        }

    }

}

TextProvider.java

文本提供程序

public interface TextProvider {
    public String getTextForPosition(int position);
    public int getCount();
}

MyFragment.java

我的片段

public class MyFragment extends Fragment {

    private String mText;

    public static MyFragment newInstance(String text) {
        MyFragment f = new MyFragment(text);
        return f;
    }

    public MyFragment() {
    }

    public MyFragment(String text) {
        this.mText = text;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        View root = inflater.inflate(R.layout.fragment, container, false);

        ((TextView) root.findViewById(R.id.position)).setText(mText);

        return root;
    }

}

activity_main.xml

活动_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/add"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="add new item" />

    <Button
        android:id="@+id/remove"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="remove current item" />

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1" />

</LinearLayout>

fragment.xml

片段.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/position"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textSize="35sp" />

</LinearLayout>

采纳答案by Tim Rae

The solution by Louth was not enough to get things working for me, as the existing fragments were not getting destroyed. Motivated by this answer, I found that the solution is to override the getItemId(int position)method of FragmentPagerAdapterto give a new unique ID whenever there has been a change in the expected position of a Fragment.

Louth 的解决方案不足以让事情为我工作,因为现有的碎片没有被破坏。受此答案的启发,我发现解决方案是在 Fragment 的预期位置发生变化时覆盖提供新的唯一 ID的getItemId(int position)方法FragmentPagerAdapter

Source Code:

源代码:

private class MyPagerAdapter extends FragmentPagerAdapter {

    private TextProvider mProvider;
    private long baseId = 0;

    public MyPagerAdapter(FragmentManager fm, TextProvider provider) {
        super(fm);
        this.mProvider = provider;
    }

    @Override
    public Fragment getItem(int position) {
        return MyFragment.newInstance(mProvider.getTextForPosition(position));
    }

    @Override
    public int getCount() {
        return mProvider.getCount();
    }


    //this is called when notifyDataSetChanged() is called
    @Override
    public int getItemPosition(Object object) {
        // refresh all fragments when data set changed
        return PagerAdapter.POSITION_NONE;
    }


    @Override
    public long getItemId(int position) {
        // give an ID different from position when position has been changed
        return baseId + position;
    }

    /**
     * Notify that the position of a fragment has been changed.
     * Create a new ID for each position to force recreation of the fragment
     * @param n number of items which have been changed
     */
    public void notifyChangeInPosition(int n) {
        // shift the ID returned by getItemId outside the range of all previous fragments
        baseId += getCount() + n;
    }
}

Now, for example if you delete a single tab or make some change to the order, you should call notifyChangeInPosition(1)before calling notifyDataSetChanged(), which will ensure that all the Fragments will be recreated.

现在,例如,如果您删除单个选项卡或对顺序进行一些更改,则应在调用notifyChangeInPosition(1)之前调用notifyDataSetChanged(),这将确保所有 Fragment 都将被重新创建。

Why this solution works

为什么这个解决方案有效

Overriding getItemPosition():

覆盖 getItemPosition():

When notifyDataSetChanged()is called, the adapter calls the notifyChanged()method of the ViewPagerwhich it is attached to. The ViewPagerthen checks the value returned by the adapter's getItemPosition()for each item, removing those items which return POSITION_NONE(see the source code) and then repopulating.

notifyDataSetChanged()被调用时,适配器调用notifyChanged()的方法,ViewPager它是联系在一起的。在ViewPager随后检查返回值由适配器的getItemPosition()每个项目,除去这些项目,其回报率POSITION_NONE(参见源代码),然后重新填充。

Overriding getItemId():

覆盖 getItemId():

This is necessary to prevent the adapter from reloading the old fragment when the ViewPageris repopulating. You can easily understand why this works by looking at the source codefor instantiateItem() in FragmentPagerAdapter.

这对于防止适配器在重新ViewPager填充时重新加载旧片段是必要的。你可以很容易理解为什么这个工程通过查看源代码的instantiateItem()的FragmentPagerAdapter

    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));
    }

As you can see, the getItem()method is only called if the fragment manager finds no existing fragments with the same Id. To me it seems like a bug that the old fragments are still attached even after notifyDataSetChanged()is called, but the documentation for ViewPagerdoes clearly state that:

如您所见,getItem()只有在片段管理器没有发现具有相同 Id 的现有片段时才会调用该方法。对我来说,即使在notifyDataSetChanged()调用后仍然附加旧片段似乎是一个错误,但文档ViewPager确实明确指出:

Note this class is currently under early design and development. The API will likely change in later updates of the compatibility library, requiring changes to the source code of apps when they are compiled against the newer version.

请注意,此类目前处于早期设计和开发阶段。API 可能会在兼容性库的后续更新中发生变化,需要在针对较新版本编译应用程序时更改应用程序的源代码。

So hopefully the workaround given here will not be necessary in a future version of the support library.

因此,希望在未来版本的支持库中不需要这里给出的解决方法。

回答by Louth

The ViewPager doesn't remove your fragments with the code above because it loads several views (or fragments in your case) into memory. In addition to the visible view, it also loads the view to either side of the visible one. This provides the smooth scrolling from view to view that makes the ViewPager so cool.

ViewPager 不会使用上面的代码删除您的片段,因为它会将多个视图(或您的情况下的片段)加载到内存中。除了可见视图之外,它还将视图加载到可见视图的任一侧。这提供了从一个视图到另一个视图的平滑滚动,使 ViewPager 如此酷。

To achieve the effect you want, you need to do a couple of things.

为了达到你想要的效果,你需要做几件事。

  1. Change the FragmentPagerAdapter to a FragmentStatePagerAdapter. The reason for this is that the FragmentPagerAdapter will keep all the views that it loads into memory forever. Where the FragmentStatePagerAdapter disposes of views that fall outside the current and traversable views.

  2. Override the adapter method getItemPosition (shown below). When we call mAdapter.notifyDataSetChanged();the ViewPager interrogates the adapter to determine what has changed in terms of positioning. We use this method to say that everything has changed so reprocess all your view positioning.

  1. 将 FragmentPagerAdapter 更改为 FragmentStatePagerAdapter。这样做的原因是 FragmentPagerAdapter 将永远保留它加载到内存中的所有视图。FragmentStatePagerAdapter 处理当前视图和可遍历视图之外的视图。

  2. 覆盖适配器方法 getItemPosition(如下所示)。当我们调用mAdapter.notifyDataSetChanged();ViewPager 时,它会询问适配器以确定在定位方面发生了什么变化。我们使用这种方法来说明一切都发生了变化,因此重新处理所有视图定位。

And here's the code...

这是代码......

private class MyPagerAdapter extends FragmentStatePagerAdapter {

    //... your existing code

    @Override
    public int getItemPosition(Object object){
        return PagerAdapter.POSITION_NONE;
    }

}

回答by Vasil Valchev

my working solution to remove fragment page from view pager

我从视图寻呼机中删除片段页面的工作解决方案

public class MyFragmentAdapter extends FragmentStatePagerAdapter {

    private ArrayList<ItemFragment> pages;

    public MyFragmentAdapter(FragmentManager fragmentManager, ArrayList<ItemFragment> pages) {
        super(fragmentManager);
        this.pages = pages;
    }

    @Override
    public Fragment getItem(int index) {
        return pages.get(index);
    }

    @Override
    public int getCount() {
        return pages.size();
    }

    @Override
    public int getItemPosition(Object object) {
        int index = pages.indexOf (object);

        if (index == -1)
            return POSITION_NONE;
        else
            return index;
    }
}

And when i need to remove some page by index i do this

当我需要按索引删除某些页面时,我会这样做

pages.remove(position); // ArrayList<ItemFragment>
adapter.notifyDataSetChanged(); // MyFragmentAdapter

Here it is my adapter initialization

这是我的适配器初始化

MyFragmentAdapter adapter = new MyFragmentAdapter(getSupportFragmentManager(), pages);
viewPager.setAdapter(adapter);

回答by Kathan Shah

The fragment must be already removed but the issue was viewpager save state

该片段必须已被删除,但问题是 viewpager 保存状态

Try

尝试

myViewPager.setSaveFromParentEnabled(false);

Nothing worked but this solved the issue !

没有任何效果,但这解决了问题!

Cheers !

干杯!

回答by Sven

I had the idea of simply copy the source code from android.support.v4.app.FragmentPagerAdpaterinto a custom class named CustumFragmentPagerAdapter. This gave me the chance to modify the instantiateItem(...)so that every time it is called, it removes / destroys the currently attached fragment before it adds the new fragment received from getItem()method.

我的想法是简单地将源代码复制android.support.v4.app.FragmentPagerAdpater到名为 CustumFragmentPagerAdapter. 这让我有机会修改它,instantiateItem(...)以便每次调用它时,它都会在添加从getItem()方法接收到的新片段之前删除/销毁当前附加的片段。

Simply modify the instantiateItem(...)in the following way:

只需instantiateItem(...)按以下方式修改:

@Override
public Object instantiateItem(ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);

    // remove / destroy current fragment
    if (fragment != null) {
        mCurTransaction.remove(fragment);
    }

    // get new fragment and add it
    fragment = getItem(position);
    mCurTransaction.add(container.getId(), fragment,    makeFragmentName(container.getId(), itemId));

    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
    }

    return fragment;
}

回答by user158

For future readers!

对于未来的读者!

Now you can use ViewPager2for dynamically adding, removing fragment from the viewpager.

现在您可以使用ViewPager2从 viewpager 中动态添加、删除片段。

Quoting form API reference

报价单API 参考

ViewPager2 replaces ViewPager, addressing most of its predecessor's pain-points, including right-to-left layout support, vertical orientation, modifiable Fragment collections, etc.

ViewPager2 取代了 ViewPager,解决了其前身的大部分痛点,包括从右到左的布局支持、垂直方向、可修改的 Fragment 集合等。

Take look at MutableCollectionFragmentActivity.ktin googlesample/android-viewpager2for an example of adding, removing fragments dynamically from the viewpager.

采取看看MutableCollectionFragmentActivity.ktgooglesample /机器人-viewpager2用于添加,从viewpager动态除去碎片的一个例子。



For your information:

供您参考:

Articles:

文章:

API reference

接口参考

Release notes

发行说明

Samples Repo: https://github.com/googlesamples/android-viewpager2

样本仓库:https: //github.com/googlesamples/android-viewpager2

回答by MSaudi

You can combine both for better :

您可以将两者结合使用以获得更好的效果:

private class MyPagerAdapter extends FragmentStatePagerAdapter {

    //... your existing code

    @Override
    public int getItemPosition(Object object){

      if(Any_Reason_You_WantTo_Update_Positions) //this includes deleting or adding pages
 return PagerAdapter.POSITION_NONE;
    }
else
return PagerAdapter.POSITION_UNCHANGED; //this ensures high performance in other operations such as editing list items.

}

回答by passerbywhu

Louth's answer works fine. But I don't think always return POSITION_NONE is a good idea. Because POSITION_NONE means that fragment should be destroyed and a new fragment will be created. You can check that in dataSetChanged function in the source code of ViewPager.

劳斯的回答工作正常。但我不认为总是返回 POSITION_NONE 是个好主意。因为 POSITION_NONE 意味着应该销毁片段并创建一个新片段。您可以在 ViewPager 源代码中的 dataSetChanged 函数中查看。

        if (newPos == PagerAdapter.POSITION_NONE) {
            mItems.remove(i);
            i--;
            ... not related code
            mAdapter.destroyItem(this, ii.position, ii.object);

So I think you'd better use an arraylist of weakReference to save all the fragments you have created. And when you add or remove some page, you can get the right position from your own arraylist.

所以我认为你最好使用一个weakReference的arraylist来保存你创建的所有片段。当您添加或删除某个页面时,您可以从您自己的数组列表中获取正确的位置。

 public int getItemPosition(Object object) {
    for (int i = 0; i < mFragmentsReferences.size(); i ++) {
        WeakReference<Fragment> reference = mFragmentsReferences.get(i);
        if (reference != null && reference.get() != null) {
            Fragment fragment = reference.get();
            if (fragment == object) {
                return i;
            }
        }
    }
    return POSITION_NONE;
}

According to the comments, getItemPosition is Called when the host view is attempting to determine if an item's position has changed. And the return value means its new position.

根据评论,当主机视图尝试确定项目的位置是否已更改时,会调用 getItemPosition。返回值意味着它的新位置。

But this is not enought. We still have an important step to take. In the source code of FragmentStatePagerAdapter, there is an array named "mFragments" caches the fragments which are not destroyed. And in instantiateItem function.

但这还不够。我们仍然需要迈出重要的一步。在 FragmentStatePagerAdapter 的源代码中,有一个名为“mFragments”的数组缓存未销毁的片段。并在instantiateItem函数中。

if (mFragments.size() > position) {
        Fragment f = mFragments.get(position);
        if (f != null) {
            return f;
        }
    }

It returned the cached fragment directly when it find that cached fragment is not null. So there is a problem. From example, let's delete one page at position 2, Firstly, We remove that fragment from our own reference arraylist. so in getItemPosition it will return POSITION_NONE for that fragment, and then that fragment will be destroyed and removed from "mFragments".

当发现缓存的片段不为空时,直接返回缓存的片段。所以有一个问题。例如,让我们删除位置 2 处的一页,首先,我们从我们自己的引用数组列表中删除该片段。因此,在 getItemPosition 中,它将为该片段返回 POSITION_NONE,然后该片段将被销毁并从“mFragments”中删除。

 mFragments.set(position, null);

Now the fragment at position 3 will be at position 2. And instantiatedItem with param position 3 will be called. At this time, the third item in "mFramgents" is not null, so it will return directly. But actually what it returned is the fragment at position 2. So when we turn into page 3, we will find an empty page there.

现在位置 3 处的片段将位于位置 2。并且将调用具有参数位置 3 的实例化项。此时,“mFramgents”中的第三项不为空,直接返回。但实际上它返回的是位置2的片段。所以当我们翻到第3页时,我们会发现那里有一个空页。

To work around this problem. My advise is that you can copy the source code of FragmentStatePagerAdapter into your own project, and when you do add or remove operations, you should add and remove elements in the "mFragments" arraylist.

要解决此问题。我的建议是,您可以将 FragmentStatePagerAdapter 的源代码复制到您自己的项目中,当您进行添加或删除操作时,您应该添加和删除“mFragments”数组列表中的元素。

Things will be simpler if you just use PagerAdapter instead of FragmentStatePagerAdapter. Good Luck.

如果您只使用 PagerAdapter 而不是 FragmentStatePagerAdapter,事情会更简单。祝你好运。

回答by Riad Hasan

add or remove fragment in viewpager dynamically.
Call setupViewPager(viewPager) on activity start. To load different fragment call setupViewPagerCustom(viewPager). e.g. on button click call: setupViewPagerCustom(viewPager);

动态添加或删除 viewpager 中的片段。
在活动开始时调用 setupViewPager(viewPager)。要加载不同的片段调用 setupViewPagerCustom(viewPager)。例如按钮点击调用:setupViewPagerCustom(viewPager);

    private void setupViewPager(ViewPager viewPager)
{
    ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
    adapter.addFrag(new fragmnet1(), "HOME");
    adapter.addFrag(new fragmnet2(), "SERVICES");

    viewPager.setAdapter(adapter);
}

private void setupViewPagerCustom(ViewPager viewPager)
{

    ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());

    adapter.addFrag(new fragmnet3(), "Contact us");
    adapter.addFrag(new fragmnet4(), "ABOUT US");


    viewPager.setAdapter(adapter);
}

//Viewpageradapter, handles the views

static class ViewPagerAdapter extends FragmentStatePagerAdapter
{
    private final List<Fragment> mFragmentList = new ArrayList<>();
    private final List<String> mFragmentTitleList = new ArrayList<>();

    public ViewPagerAdapter(FragmentManager manager){
        super(manager);
    }

    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }

    @Override
    public int getCount() {
        return mFragmentList.size();
    }

    @Override
    public int getItemPosition(Object object){
        return PagerAdapter.POSITION_NONE;
    }

    public void addFrag(Fragment fragment, String title){
        mFragmentList.add(fragment);
        mFragmentTitleList.add(title);
    }

    @Override
    public CharSequence getPageTitle(int position){
        return mFragmentTitleList.get(position);
    }
}

回答by Milan Laslop

I had some problems with FragmentStatePagerAdapter. After removing an item:

我有一些问题FragmentStatePagerAdapter。删除项目后:

  • there was another item used for a position (an item which did not belong to the position but to a position next to it)
  • or some fragment was not loaded (there was only blank background visible on that page)
  • 有另一个用于位置的项目(不属于该位置但属于它旁边位置的项目)
  • 或某些片段未加载(该页面上只有空白背景可见)

After lots of experiments, I came up with the following solution.

经过大量实验,我想出了以下解决方案。

public class SomeAdapter extends FragmentStatePagerAdapter {

    private List<Item> items = new ArrayList<Item>();

    private boolean removing;

    @Override
    public Fragment getItem(int position) {
        ItemFragment fragment = new ItemFragment();
        Bundle args = new Bundle();
        // use items.get(position) to configure fragment
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public int getCount() {
        return items.size();
    }

    @Override
    public int getItemPosition(Object object) {
        if (removing) {
            return PagerAdapter.POSITION_NONE;
        }

        Item item = getItemOfFragment(object);

        int index = items.indexOf(item);
        if (index == -1) {
            return POSITION_NONE;
        } else {
            return index;
        }
    }

   public void addItem(Item item) {
        items.add(item);
        notifyDataSetChanged();
    }

    public void removeItem(int position) {
        items.remove(position);

        removing = true;
        notifyDataSetChanged();
        removing = false;
    }
}

This solution only uses a hack in case of removing an item. Otherwise (e.g. when adding an item) it retains the cleanliness and performance of an original code.

此解决方案仅在删除项目时使用 hack。否则(例如,在添加项目时)它会保留原始代码的清洁度和性能。

Of course, from the outside of the adapter, you call only addItem/removeItem, no need to call notifyDataSetChanged().

当然,从适配器外部,你只调用addItem/removeItem,不需要调用notifyDataSetChanged()。