从Android中的ViewPager中删除片段页面

ast*_*etz 134 android android-viewpager fragmentpageradapter

我正在尝试从ViewPager动态添加和删除片段,添加工作没有任何问题,但删除不能按预期工作.

每次我想删除当前项目时,最后一项将被删除.

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

我究竟做错了什么?

这是一个基本的例子:

MainActivity.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();
        }

    }

}
Run Code Online (Sandbox Code Playgroud)

TextProvider.java

public interface TextProvider {
    public String getTextForPosition(int position);
    public int getCount();
}
Run Code Online (Sandbox Code Playgroud)

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;
    }

}
Run Code Online (Sandbox Code Playgroud)

activity_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>
Run Code Online (Sandbox Code Playgroud)

fragment.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>
Run Code Online (Sandbox Code Playgroud)

Lou*_*uth 286

ViewPager不会使用上面的代码删除您的片段,因为它会将多个视图(或您的案例中的片段)加载到内存中.除了可见视图外,它还将视图加载到可见视图的任一侧.这提供了从视图到视图的平滑滚动,使ViewPager非常酷.

要达到您想要的效果,您需要做一些事情.

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

  2. 覆盖适配器方法getItemPosition(如下所示).当我们调用mAdapter.notifyDataSetChanged();ViewPager时,询问适配器以确定在定位方面发生了哪些变化.我们使用此方法表示所有内容都已更改,因此重新处理所有视图定位.

这是代码......

private class MyPagerAdapter extends FragmentStatePagerAdapter {

    //... your existing code

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

}
Run Code Online (Sandbox Code Playgroud)

  • 为什么总是返回POSITION_NONE而不是真正查看给定的视图/片段/对象是否仍然存在且有效?这个方法应该回答"嘿,我可以重用这个吗?"的问题. - 并且总是说"不!" 只是意味着重新创造一切!在许多情况下这是不必要的. (17认同)
  • @Louth当我们调用notifyDataSetChanged()时,viewpager会创建新的片段,但旧的片段仍在内存中.我的问题是; 他们接到了对onOptionsItemSelected事件的调用.我怎样才能摆脱旧的碎片? (4认同)

Tim*_*Rae 93

Louth的解决方案还不足以让我的工作正常,因为现有的碎片并没有被破坏.在这个答案的推动下,我发现解决方案是覆盖片段的预期位置发生变化时提供新的唯一ID 的getItemId(int position)方法FragmentPagerAdapter.

源代码:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,例如,如果删除单个选项卡或对订单进行一些更改,则应在调用notifyChangeInPosition(1)之前调用notifyDataSetChanged(),这将确保将重新创建所有片段.

为何此解决方案有效

覆盖getItemPosition():

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

覆盖getItemId():

这是防止适配器在重新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));
    }
Run Code Online (Sandbox Code Playgroud)

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

请注意,此课程目前正在进行早期设计和开发.API可能会在以后的兼容库更新中发生变化,需要在针对较新版本编译时更改应用程序的源代码.

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

  • 2016 年,仍然需要此解决方法才能使 `notifyDataSetChanged()` 工作。 (2认同)

Vas*_*hev 13

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

当我需要通过索引删除一些页面时,我这样做

pages.remove(position); // ArrayList<ItemFragment>
adapter.notifyDataSetChanged(); // MyFragmentAdapter
Run Code Online (Sandbox Code Playgroud)

这是我的适配器初始化

MyFragmentAdapter adapter = new MyFragmentAdapter(getSupportFragmentManager(), pages);
viewPager.setAdapter(adapter);
Run Code Online (Sandbox Code Playgroud)


Kat*_*hah 10

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

尝试

myViewPager.setSaveFromParentEnabled(false);
Run Code Online (Sandbox Code Playgroud)

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

干杯!