1. ホーム
  2. android

[解決済み] AndroidのViewPagerからフラグメントページを削除する

2022-05-13 23:25:43

質問

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

    }

}

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

}

アクティビティ_メイン.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>

フラグメント.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>

どのように解決するのですか?

Louth による解決策では、既存のフラグメントが破壊されないため、私の場合、物事をうまく進めるには十分ではありませんでした。動機は この回答 によって動機づけられ、私は解決策が getItemId(int position) メソッドをオーバーライドすることです。 FragmentPagerAdapter メソッドを用いて、Fragment の予想される位置が変更されるたびに新しい一意の ID を与えることができます。

ソースコードです。

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

さて、例えばタブを一つ削除したり、順番に何らかの変更を加えたりする場合、以下のように 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));
    }

ご覧のように getItem() メソッドは、フラグメントマネージャが同じ ID を持つ既存のフラグメントを見つけない場合にのみ呼び出されます。私にとっては、古いフラグメントが notifyDataSetChanged() が呼び出された後でも古いフラグメントが添付されているのはバグのように思えますが ViewPager はそのことを明確に述べています。

このクラスは現在、初期の設計と開発中であることに注意してください。API は、互換性ライブラリの将来のアップデートで変更される可能性があり、新しいバージョンに対してコンパイルするときにアプリのソース コードを変更する必要があります。

したがって、ここで示された回避策は、互換ライブラリの将来のバージョンでは必要ないことを期待します。