ViewPager内のフラグメントを置き換える


268

を使用してフラグメントを使用しようとしViewPagerていFragmentPagerAdapterます。私が達成しようとしているのは、の最初のページに配置されたフラグメントViewPagerを別のフラグメントに置き換えることです。

ページャーは2つのページで構成されています。1つ目は、FirstPagerFragment2つ目はSecondPagerFragmentです。最初のページのボタンをクリックする。をFirstPagerFragmentNextFragmentに置き換えます。

以下に私のコードがあります。

public class FragmentPagerActivity extends FragmentActivity {

    static final int NUM_ITEMS = 2;

    MyAdapter mAdapter;
    ViewPager mPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.fragment_pager);

        mAdapter = new MyAdapter(getSupportFragmentManager());

        mPager = (ViewPager) findViewById(R.id.pager);
        mPager.setAdapter(mAdapter);

    }


    /**
     * Pager Adapter
     */
    public static class MyAdapter extends FragmentPagerAdapter {
        public MyAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public int getCount() {
            return NUM_ITEMS;
        }

        @Override
        public Fragment getItem(int position) {

            if(position == 0) {
                return FirstPageFragment.newInstance();
            } else {
                return SecondPageFragment.newInstance();
            }

        }
    }


    /**
     * Second Page FRAGMENT
     */
    public static class SecondPageFragment extends Fragment {

        public static SecondPageFragment newInstance() {
            SecondPageFragment f = new SecondPageFragment();
            return f;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            //Log.d("DEBUG", "onCreateView");
            return inflater.inflate(R.layout.second, container, false);

        }
    }

    /**
     * FIRST PAGE FRAGMENT
     */
    public static class FirstPageFragment extends Fragment {

        Button button;

        public static FirstPageFragment newInstance() {
            FirstPageFragment f = new FirstPageFragment();
            return f;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            //Log.d("DEBUG", "onCreateView");
            View root = inflater.inflate(R.layout.first, container, false);
            button = (Button) root.findViewById(R.id.button);
            button.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    FragmentTransaction trans = getFragmentManager().beginTransaction();
                                    trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
                    trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
                    trans.addToBackStack(null);
                    trans.commit();

                }

            });

            return root;
        }

        /**
     * Next Page FRAGMENT in the First Page
     */
    public static class NextFragment extends Fragment {

        public static NextFragment newInstance() {
            NextFragment f = new NextFragment();
            return f;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            //Log.d("DEBUG", "onCreateView");
            return inflater.inflate(R.layout.next, container, false);

        }
    }
}

...そしてここにxmlファイル

fragment_pager.xml

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

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

</LinearLayout>

first.xml

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

  <Button android:id="@+id/button"
     android:layout_width="wrap_content" android:layout_height="wrap_content"
     android:text="to next"/>

</LinearLayout>

今問題...私はどのIDで使用すべきですか

trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());

を使用したR.id.first_fragment_root_id場合、置換は機能しますが、Hierarchy Viewerは以下のような奇妙な動作を示します。

最初の状況は

交換後の状況は

何か問題があることがわかりますので、フラグメントを交換した後、最初の写真と同じ状態が見つかるはずです。


どういうわけかそのレイアウト図を生成しましたか、それとも手動で行いましたか?
Jeroen Vannevel

@麺:私も同じようにする必要があります。あなたの質問は多くの異なる答えを持つ長いスレッドを生成しました。最終的に何が最も効果的でしたか?どうもありがとう!
2016

1
@JeroenVannevelあなたはすでにそれを理解しているかもしれませんが、そのレイアウト図はdeveloper.android.com/tools/performance/hierarchy-viewer/…です
Ankit Popli

いくつかの問題があなたの助けを必要しまっstackoverflow.com/questions/40149039/...
アルバート・

回答:


162

ViewPagerおよびのソースコードを変更する必要のない別のソリューションがあり、作成者が使用する基本クラスFragmentStatePagerAdapterで動作しFragmentPagerAdapterます。

まず、著者が使用するIDに関する質問に回答することから始めたいと思います。これはコンテナのID、つまりビューページャー自体のIDです。ただし、おそらくお気づきのように、コードでそのIDを使用しても何も起こりません。理由を説明します:

まずViewPager、ページを再作成するにnotifyDataSetChanged()は、アダプタの基本クラスにあるを呼び出す必要があります。

次に、抽象メソッドをViewPager使用して、getItemPosition()破棄するページと保持するページを確認します。この関数のデフォルトの実装は常にを返しますPOSITION_UNCHANGED。これによりViewPager、現在のすべてのページが保持され、その結果、新しいページが添付されなくなります。したがって、フラグメントの置換を機能させるにgetItemPosition()は、アダプターでオーバーライドする必要がありPOSITION_NONE、古い非表示のフラグメントを引数として呼び出されたときに復帰する必要があります。

これは、アダプターが常に、位置0 FirstPageFragmentまたは位置に表示する必要があるフラグメントを認識する必要があることも意味しますNextFragment。これを行う1つの方法はFirstPageFragment、を作成するときにリスナーを提供することです。これは、フラグメントを切り替えるときに呼び出されます。私は、あなたのフラグメントアダプタハンドルをできるように、すべてのフラグメントがスイッチとの呼び出しけれども、これは良い事だと思うViewPagerFragmentManager

3番目にFragmentPagerAdapter、使用されたフラグメントを位置から派生​​した名前でキャッシュします。そのため、位置0にフラグメントがあった場合、クラスが新しい場合でもフラグメントは置き換えられません。2つの解決策がありますが、最も簡単なのは、のremove()関数を使用することですFragmentTransaction。これにより、タグも削除されます。

それはたくさんのテキストでした、ここにあなたのケースでうまくいくはずのコードがあります:

public class MyAdapter extends FragmentPagerAdapter
{
    static final int NUM_ITEMS = 2;
    private final FragmentManager mFragmentManager;
    private Fragment mFragmentAtPos0;

    public MyAdapter(FragmentManager fm)
    {
        super(fm);
        mFragmentManager = fm;
    }

    @Override
    public Fragment getItem(int position)
    {
        if (position == 0)
        {
            if (mFragmentAtPos0 == null)
            {
                mFragmentAtPos0 = FirstPageFragment.newInstance(new FirstPageFragmentListener()
                {
                    public void onSwitchToNextFragment()
                    {
                        mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();
                        mFragmentAtPos0 = NextFragment.newInstance();
                        notifyDataSetChanged();
                    }
                });
            }
            return mFragmentAtPos0;
        }
        else
            return SecondPageFragment.newInstance();
    }

    @Override
    public int getCount()
    {
        return NUM_ITEMS;
    }

    @Override
    public int getItemPosition(Object object)
    {
        if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)
            return POSITION_NONE;
        return POSITION_UNCHANGED;
    }
}

public interface FirstPageFragmentListener
{
    void onSwitchToNextFragment();
}

これが誰にも役立つことを願っています!


24
私も動作しますが、最初のフラグメントを表示するために戻るボタンを実装するにはどうすればよいですか?
user1159819 2012

6
すばらしい説明ですが、これの完全なソースコード、具体的にはFirstPageFragmentクラスとSecondPageFragmentクラスを投稿してください。
georgiecasey

6
FirstInstanceFragmentListenerをnewInstance()のバンドルにどのように追加しますか?
ミゲル14

4
FirstPageFragment.newInstance()リスナーパラメータで処理するコードを追加できますか?
MiguelHincapieC 2016年

6
これは、フラグメントを削除するときにcommit()ではなくcommitNow()を呼び出した場合にのみ機能します。なぜ私のユースケースが違うのかはわかりませんが、うまくいけば誰かの時間を節約できます。
Ian Lovejoy 2016年

37

2012年11月13日の時点で、ViewPagerでフラグメントを再配置することがはるかに簡単になったようです。GoogleはネストされたフラグメントをサポートするAndroid 4.2をリリースしました。これは新しいAndroid Support Library v11でもサポートされるため、これは1.6まで完全に機能します

getChildFragmentManagerを使用する以外は、フラグメントを置き換える通常の方法と非常に似ています。ネストされたフラグメントのバックスタックがユーザーが[戻る]ボタンをクリックしたときにポップされないことを除いて、動作するようです。そのリンクされた質問の解決策に従って、フラグメントの子マネージャーでpopBackStackImmediate()を手動で呼び出す必要があります。したがって、ViewPagerの現在のフラグメントを取得し、その上でgetChildFragmentManager()。popBackStackImmediate()を呼び出すViewPagerアクティビティのonBackPressed()をオーバーライドする必要があります。

現在表示されているフラグメントの取得も少しハックです。このダーティな「android:switcher:VIEWPAGER_ID:INDEX」ソリューションを使用しました、このページの 2番目のソリューション説明されているように、ViewPagerのすべてのフラグメントを自分で追跡することもできます

ユーザーが行をクリックしたときに詳細ページがViewPagerに表示され、戻るボタンが機能している4つのリストビューを持つViewPagerのコードは次のとおりです。簡潔にするために関連するコードのみを含めようとしたので、アプリ全体をGitHubにアップロードする場合はコメントを残してください。

HomeActivity.java

 public class HomeActivity extends SherlockFragmentActivity {
FragmentAdapter mAdapter;
ViewPager mPager;
TabPageIndicator mIndicator;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    mAdapter = new FragmentAdapter(getSupportFragmentManager());
    mPager = (ViewPager)findViewById(R.id.pager);
    mPager.setAdapter(mAdapter);
    mIndicator = (TabPageIndicator)findViewById(R.id.indicator);
    mIndicator.setViewPager(mPager);
}

// This the important bit to make sure the back button works when you're nesting fragments. Very hacky, all it takes is some Google engineer to change that ViewPager view tag to break this in a future Android update.
@Override
public void onBackPressed() {
    Fragment fragment = (Fragment) getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":"+mPager.getCurrentItem());
    if (fragment != null) // could be null if not instantiated yet
    {
        if (fragment.getView() != null) {
            // Pop the backstack on the ChildManager if there is any. If not, close this activity as normal.
            if (!fragment.getChildFragmentManager().popBackStackImmediate()) {
                finish();
            }
        }
    }
}

class FragmentAdapter extends FragmentPagerAdapter {        
    public FragmentAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) {
        case 0:
            return ListProductsFragment.newInstance();
        case 1:
            return ListActiveSubstancesFragment.newInstance();
        case 2:
            return ListProductFunctionsFragment.newInstance();
        case 3:
            return ListCropsFragment.newInstance();
        default:
            return null;
        }
    }

    @Override
    public int getCount() {
        return 4;
    }

 }
}

ListProductsFragment.java

public class ListProductsFragment extends SherlockFragment {
private ListView list;

public static ListProductsFragment newInstance() {
    ListProductsFragment f = new ListProductsFragment();
    return f;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View V = inflater.inflate(R.layout.list, container, false);
    list = (ListView)V.findViewById(android.R.id.list);
    list.setOnItemClickListener(new OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View view,
            int position, long id) {
          // This is important bit
          Fragment productDetailFragment = FragmentProductDetail.newInstance();
          FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
          transaction.addToBackStack(null);
          transaction.replace(R.id.products_list_linear, productDetailFragment).commit();
        }
      });       
    return V;
}
}

7
コードをgithubにアップロードすることは可能ですか?ありがとう。
Gautham 2013年

4
@georgiecasey:getChildFragmentManager()の使用を開始できるAPI 17 Android 4.2をダウンロードしました。しかし、画面上で古いフラグメントと新しいフラグメントが重なっているため、ソリューションから何かが欠けているに違いありません。注:私はからGoogleの例TabsAdapterコードと協調してあなたのソリューションを使用しようとしているdeveloper.android.com/reference/android/support/v4/view/...。提案および/または参照コードをありがとう。
gcl1 2013

27
R.id.products_list_linearは何を参照していますか?提供されたコードにリンクしてください。
Howettl 2013

1
@howettl外部IDメンバーへのID参照。この問題を解決するための鍵は、ネストされたフラグメントを使用することです(ビューページャーアダプターを操作する必要がないように)。
IndrekKõue2013年

2
これでgithubになりましたか?
バルク

32

@wizeの回答に基づいて、役立つとエレガントに思ったので、部分的に必要なことを達成できました。交換すると、最初のフラグメントに戻る機能が必要になったためです。私は彼のコードを少し変更してそれを達成しました。

これはFragmentPagerAdapterです。

public static class MyAdapter extends FragmentPagerAdapter {
    private final class CalendarPageListener implements
            CalendarPageFragmentListener {
        public void onSwitchToNextFragment() {
            mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
                    .commit();
            if (mFragmentAtPos0 instanceof FirstFragment){
                mFragmentAtPos0 = NextFragment.newInstance(listener);
            }else{ // Instance of NextFragment
                mFragmentAtPos0 = FirstFragment.newInstance(listener);
            }
            notifyDataSetChanged();
        }
    }

    CalendarPageListener listener = new CalendarPageListener();;
    private Fragment mFragmentAtPos0;
    private FragmentManager mFragmentManager;

    public MyAdapter(FragmentManager fm) {
        super(fm);
        mFragmentManager = fm;
    }

    @Override
    public int getCount() {
        return NUM_ITEMS;
    }

    @Override
    public int getItemPosition(Object object) {
        if (object instanceof FirstFragment && mFragmentAtPos0 instanceof NextFragment)
            return POSITION_NONE;
        if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstFragment)
            return POSITION_NONE;
        return POSITION_UNCHANGED;
    }

    @Override
    public Fragment getItem(int position) {
        if (position == 0)
            return Portada.newInstance();
        if (position == 1) { // Position where you want to replace fragments
            if (mFragmentAtPos0 == null) {
                mFragmentAtPos0 = FirstFragment.newInstance(listener);
            }
            return mFragmentAtPos0;
        }
        if (position == 2)
            return Clasificacion.newInstance();
        if (position == 3)
            return Informacion.newInstance();

        return null;
    }
}

public interface CalendarPageFragmentListener {
    void onSwitchToNextFragment();
}

置換を実行するには、型の静的フィールドを定義し、対応するフラグメントのメソッドをCalendarPageFragmentListener介して初期化して、newInstance呼び出し、FirstFragment.pageListener.onSwitchToNextFragment()またはNextFragment.pageListener.onSwitchToNextFragment()明示的に呼び出します。


交換はどのように行いますか?onSwitchToNextメソッドをオーバーライドせずにリスナーを初期化することはできません。
Leandros 2012

1
デバイスを回転させた場合、mFragmentAtPos0に保存された状態が失われませんか?
12

@seb、私は実際にこのソリューションを改善してmFragmentAtPos0、アクティビティの状態を保存するときに参照を保存しました。これは最もエレガントなソリューションではありませんが、機能します。
mdelolmo 2012年

私の答えを見ることができます。フラグメントを置き換え、最初のフラグメントに戻ります。
Lyd

@mdelolmo ViewPagerに関するこの質問を見ていただけますか?stackoverflow.com/questions/27937250/…
Hoa Vu

21

私はソリューションを実装しました:

  • タブ内の動的フラグメント置換
  • タブごとの履歴の維持
  • 向きの変更の操作

これを達成するための秘訣は次のとおりです。

  • notifyDataSetChanged()メソッドを使用してフラグメント置換を適用します
  • フラグメントマネージャーはバックステージにのみ使用し、フラグメントの交換には使用しない
  • 記憶パターン(スタック)を使用して履歴を維持する

アダプタコードは次のとおりです。

public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar.TabListener, ViewPager.OnPageChangeListener {

/** The sherlock fragment activity. */
private final SherlockFragmentActivity mActivity;

/** The action bar. */
private final ActionBar mActionBar;

/** The pager. */
private final ViewPager mPager;

/** The tabs. */
private List<TabInfo> mTabs = new LinkedList<TabInfo>();

/** The total number of tabs. */
private int TOTAL_TABS;

private Map<Integer, Stack<TabInfo>> history = new HashMap<Integer, Stack<TabInfo>>();

/**
 * Creates a new instance.
 *
 * @param activity the activity
 * @param pager    the pager
 */
public TabsAdapter(SherlockFragmentActivity activity, ViewPager pager) {
    super(activity.getSupportFragmentManager());
    activity.getSupportFragmentManager();
    this.mActivity = activity;
    this.mActionBar = activity.getSupportActionBar();
    this.mPager = pager;
    mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
}

/**
 * Adds the tab.
 *
 * @param image         the image
 * @param fragmentClass the class
 * @param args          the arguments
 */
public void addTab(final Drawable image, final Class fragmentClass, final Bundle args) {
    final TabInfo tabInfo = new TabInfo(fragmentClass, args);
    final ActionBar.Tab tab = mActionBar.newTab();
    tab.setTabListener(this);
    tab.setTag(tabInfo);
    tab.setIcon(image);

    mTabs.add(tabInfo);
    mActionBar.addTab(tab);

    notifyDataSetChanged();
}

@Override
public Fragment getItem(final int position) {
    final TabInfo tabInfo = mTabs.get(position);
    return Fragment.instantiate(mActivity, tabInfo.fragmentClass.getName(), tabInfo.args);
}

@Override
public int getItemPosition(final Object object) {
    /* Get the current position. */
    int position = mActionBar.getSelectedTab().getPosition();

    /* The default value. */
    int pos = POSITION_NONE;
    if (history.get(position).isEmpty()) {
        return POSITION_NONE;
    }

    /* Checks if the object exists in current history. */
    for (Stack<TabInfo> stack : history.values()) {
        TabInfo c = stack.peek();
        if (c.fragmentClass.getName().equals(object.getClass().getName())) {
            pos = POSITION_UNCHANGED;
            break;
        }
    }
    return pos;
}

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

@Override
public void onPageScrollStateChanged(int arg0) {
}

@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}

@Override
public void onPageSelected(int position) {
    mActionBar.setSelectedNavigationItem(position);
}

@Override
public void onTabSelected(final ActionBar.Tab tab, final FragmentTransaction ft) {
    TabInfo tabInfo = (TabInfo) tab.getTag();
    for (int i = 0; i < mTabs.size(); i++) {
        if (mTabs.get(i).equals(tabInfo)) {
            mPager.setCurrentItem(i);
        }
    }
}

@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
}

@Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}

public void replace(final int position, final Class fragmentClass, final Bundle args) {
    /* Save the fragment to the history. */
    mActivity.getSupportFragmentManager().beginTransaction().addToBackStack(null).commit();

    /* Update the tabs. */
    updateTabs(new TabInfo(fragmentClass, args), position);

    /* Updates the history. */
    history.get(position).push(new TabInfo(mTabs.get(position).fragmentClass, mTabs.get(position).args));

    notifyDataSetChanged();
}

/**
 * Updates the tabs.
 *
 * @param tabInfo
 *          the new tab info
 * @param position
 *          the position
 */
private void updateTabs(final TabInfo tabInfo, final int position) {
    mTabs.remove(position);
    mTabs.add(position, tabInfo);
    mActionBar.getTabAt(position).setTag(tabInfo);
}

/**
 * Creates the history using the current state.
 */
public void createHistory() {
    int position = 0;
    TOTAL_TABS = mTabs.size();
    for (TabInfo mTab : mTabs) {
        if (history.get(position) == null) {
            history.put(position, new Stack<TabInfo>());
        }
        history.get(position).push(new TabInfo(mTab.fragmentClass, mTab.args));
        position++;
    }
}

/**
 * Called on back
 */
public void back() {
    int position = mActionBar.getSelectedTab().getPosition();
    if (!historyIsEmpty(position)) {
        /* In case there is not any other item in the history, then finalize the activity. */
        if (isLastItemInHistory(position)) {
            mActivity.finish();
        }
        final TabInfo currentTabInfo = getPrevious(position);
        mTabs.clear();
        for (int i = 0; i < TOTAL_TABS; i++) {
            if (i == position) {
                mTabs.add(new TabInfo(currentTabInfo.fragmentClass, currentTabInfo.args));
            } else {
                TabInfo otherTabInfo = history.get(i).peek();
                mTabs.add(new TabInfo(otherTabInfo.fragmentClass, otherTabInfo.args));
            }
        }
    }
    mActionBar.selectTab(mActionBar.getTabAt(position));
    notifyDataSetChanged();
}

/**
 * Returns if the history is empty.
 *
 * @param position
 *          the position
 * @return  the flag if empty
 */
private boolean historyIsEmpty(final int position) {
    return history == null || history.isEmpty() || history.get(position).isEmpty();
}

private boolean isLastItemInHistory(final int position) {
    return history.get(position).size() == 1;
}

/**
 * Returns the previous state by the position provided.
 *
 * @param position
 *          the position
 * @return  the tab info
 */
private TabInfo getPrevious(final int position) {
    TabInfo currentTabInfo = history.get(position).pop();
    if (!history.get(position).isEmpty()) {
        currentTabInfo = history.get(position).peek();
    }
    return currentTabInfo;
}

/** The tab info class */
private static class TabInfo {

    /** The fragment class. */
    public Class fragmentClass;

    /** The args.*/
    public Bundle args;

    /**
     * Creates a new instance.
     *
     * @param fragmentClass
     *          the fragment class
     * @param args
     *          the args
     */
    public TabInfo(Class fragmentClass, Bundle args) {
        this.fragmentClass = fragmentClass;
        this.args = args;
    }

    @Override
    public boolean equals(final Object o) {
        return this.fragmentClass.getName().equals(o.getClass().getName());
    }

    @Override
    public int hashCode() {
        return fragmentClass.getName() != null ? fragmentClass.getName().hashCode() : 0;
    }

    @Override
    public String toString() {
        return "TabInfo{" +
                "fragmentClass=" + fragmentClass +
                '}';
    }
}

すべてのタブを初めて追加するときは、メソッドcreateHistory()を呼び出して初期履歴を作成する必要があります

public void createHistory() {
    int position = 0;
    TOTAL_TABS = mTabs.size();
    for (TabInfo mTab : mTabs) {
        if (history.get(position) == null) {
            history.put(position, new Stack<TabInfo>());
        }
        history.get(position).push(new TabInfo(mTab.fragmentClass, mTab.args));
        position++;
    }
}

フラグメントを特定のタブに置き換えるたびに、次のように呼び出します:replace(final int position、final Class fragmentClass、final Bundle args)

/* Save the fragment to the history. */
    mActivity.getSupportFragmentManager().beginTransaction().addToBackStack(null).commit();

    /* Update the tabs. */
    updateTabs(new TabInfo(fragmentClass, args), position);

    /* Updates the history. */
    history.get(position).push(new TabInfo(mTabs.get(position).fragmentClass, mTabs.get(position).args));

    notifyDataSetChanged();

バックプレス時に、back()メソッドを呼び出す必要があります。

public void back() {
    int position = mActionBar.getSelectedTab().getPosition();
    if (!historyIsEmpty(position)) {
        /* In case there is not any other item in the history, then finalize the activity. */
        if (isLastItemInHistory(position)) {
            mActivity.finish();
        }
        final TabInfo currentTabInfo = getPrevious(position);
        mTabs.clear();
        for (int i = 0; i < TOTAL_TABS; i++) {
            if (i == position) {
                mTabs.add(new TabInfo(currentTabInfo.fragmentClass, currentTabInfo.args));
            } else {
                TabInfo otherTabInfo = history.get(i).peek();
                mTabs.add(new TabInfo(otherTabInfo.fragmentClass, otherTabInfo.args));
            }
        }
    }
    mActionBar.selectTab(mActionBar.getTabAt(position));
    notifyDataSetChanged();
}

ソリューションは、シャーロックアクションバーとスワイプジェスチャーで動作します。


それは素晴らしい!あなたはそれをgithubに置くことができますか?
Nicramus 2014年

この実装は、ここで提供されるすべての中で最もクリーンであり、すべての問題に対処します。それは受け入れられる答えであるべきです。
dazedviper 14

誰かこの実装で問題がありましたか?私はそれらの2つを持っています。1.最初のバックが登録されない-登録するには、2回クリックする必要があります。2.タブaを(2回)クリックしてタブbに移動した後、タブaに戻ると、タブbのコンテンツが表示されます
yaronbic '19年

7

tl; dr:ホストされているコンテンツを置き換える責任があるホストフラグメントを使用し、(ブラウザーのように)戻るナビゲーション履歴を追跡します。

ユースケースは一定量のタブで構成されているので、私のソリューションはうまく機能します。アイデアは、ViewPagerにカスタムクラスのインスタンスを入力するHostFragmentことです。これは、ホストされているコンテンツを置き換え、独自の戻るナビゲーション履歴を保持できます。ホストされたフラグメントを置き換えるには、メソッドを呼び出しますhostfragment.replaceFragment()

public void replaceFragment(Fragment fragment, boolean addToBackstack) {
    if (addToBackstack) {
        getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).addToBackStack(null).commit();
    } else {
        getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).commit();
    }
}

このメソッドが行うことは、フレームレイアウトR.id.hosted_fragmentを、メソッドに提供されたフラグメントを持つid で置き換えることです。

詳細とGitHubでの完全な実用例については、このトピックに関する私のチュートリアルをチェックしてください


これは不格好に見えますが、実際には(構造とメンテナンスの容易さの観点から)これを行う最良の方法だと思います。
Sira Lam

私は結局、NPEに突き当たる。:/置き換えられるビューが見つからない
Fariz Fakkel

6

提示されたソリューションのいくつかは問題を部分的に解決するのに大いに役立ちましたが、いくつかのケースではフラグメントコンテンツの代わりに予期しない例外とブラックページコンテンツを生成するソリューションに欠けている重要なことがまだ1つあります。

問題は、FragmentPagerAdapterクラスがアイテムIDを使用して、キャッシュされたフラグメントをFragmentManagerに格納していることです。このような理由から、あなたもオーバーライドする必要がありgetItemId(int型の位置)、それは例えば返すようにする方法を位置をトップレベルのページとするために100 +位置の詳細ページ用。それ以外の場合は、詳細レベルのフラグメントではなく、以前に作成されたトップレベルのフラグメントがキャッシュから返されます。

さらに、ここでは、ViewPagerを使用したFragmentページと、RadioGroupを使用したタブボタンを使用したタブのようなアクティビティを実装する方法の完全な例を紹介します。この実装は1レベルのバックスタッキング(アイテムリスト-アイテムの詳細)のみをサポートしますが、マルチレベルのバックスタッキングの実装は簡単です。この例は、2番目のページなどに切り替えて最初のページのフラグメントを変更し(表示されていない場合)、最初のページに戻る場合にNullPointerExceptionをスローすることを除いて、通常のケースではかなりうまくいきます。この問題の解決策がわかり次第、投稿します。

public class TabsActivity extends FragmentActivity {

  public static final int PAGE_COUNT = 3;
  public static final int FIRST_PAGE = 0;
  public static final int SECOND_PAGE = 1;
  public static final int THIRD_PAGE = 2;

  /**
   * Opens a new inferior page at specified tab position and adds the current page into back
   * stack.
   */
  public void startPage(int position, Fragment content) {
    // Replace page adapter fragment at position.
    mPagerAdapter.start(position, content);
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Initialize basic layout.
    this.setContentView(R.layout.tabs_activity);

    // Add tab fragments to view pager.
    {
      // Create fragments adapter.
      mPagerAdapter = new PagerAdapter(pager);
      ViewPager pager = (ViewPager) super.findViewById(R.id.tabs_view_pager);
      pager.setAdapter(mPagerAdapter);

      // Update active tab in tab bar when page changes.
      pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int index, float value, int nextIndex) {
          // Not used.
        }

        @Override
        public void onPageSelected(int index) {
          RadioGroup tabs_radio_group = (RadioGroup) TabsActivity.this.findViewById(
            R.id.tabs_radio_group);
          switch (index) {
            case 0: {
              tabs_radio_group.check(R.id.first_radio_button);
            }
            break;
            case 1: {
              tabs_radio_group.check(R.id.second_radio_button);
            }
            break;
            case 2: {
              tabs_radio_group.check(R.id.third_radio_button);
            }
            break;
          }
        }

        @Override
        public void onPageScrollStateChanged(int index) {
          // Not used.
        }
      });
    }

    // Set "tabs" radio group on checked change listener that changes the displayed page.
    RadioGroup radio_group = (RadioGroup) this.findViewById(R.id.tabs_radio_group);
    radio_group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
      @Override
      public void onCheckedChanged(RadioGroup radioGroup, int id) {
        // Get view pager representing tabs.
        ViewPager view_pager = (ViewPager) TabsActivity.this.findViewById(R.id.tabs_view_pager);
        if (view_pager == null) {
          return;
        }

        // Change the active page.
        switch (id) {
          case R.id.first_radio_button: {
            view_pager.setCurrentItem(FIRST_PAGE);
          }
          break;
          case R.id.second_radio_button: {
            view_pager.setCurrentItem(SECOND_PAGE);
          }
          break;
          case R.id.third_radio_button: {
            view_pager.setCurrentItem(THIRD_PAGE);
          }
          break;
        }
      });
    }
  }

  @Override
  public void onBackPressed() {
    if (!mPagerAdapter.back()) {
      super.onBackPressed();
    }
  }

  /**
   * Serves the fragments when paging.
   */
  private class PagerAdapter extends FragmentPagerAdapter {

    public PagerAdapter(ViewPager container) {
      super(TabsActivity.this.getSupportFragmentManager());

      mContainer = container;
      mFragmentManager = TabsActivity.this.getSupportFragmentManager();

      // Prepare "empty" list of fragments.
      mFragments = new ArrayList<Fragment>(){};
      mBackFragments = new ArrayList<Fragment>(){};
      for (int i = 0; i < PAGE_COUNT; i++) {
        mFragments.add(null);
        mBackFragments.add(null);
      }
    }

    /**
     * Replaces the view pager fragment at specified position.
     */
    public void replace(int position, Fragment fragment) {
      // Get currently active fragment.
      Fragment old_fragment = mFragments.get(position);
      if (old_fragment == null) {
        return;
      }

      // Replace the fragment using transaction and in underlaying array list.
      // NOTE .addToBackStack(null) doesn't work
      this.startUpdate(mContainer);
      mFragmentManager.beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
        .remove(old_fragment).add(mContainer.getId(), fragment)
        .commit();
      mFragments.set(position, fragment);
      this.notifyDataSetChanged();
      this.finishUpdate(mContainer);
    }

    /**
     * Replaces the fragment at specified position and stores the current fragment to back stack
     * so it can be restored by #back().
     */
    public void start(int position, Fragment fragment) {
      // Remember current fragment.
      mBackFragments.set(position, mFragments.get(position));

      // Replace the displayed fragment.
      this.replace(position, fragment);
    }

    /**
     * Replaces the current fragment by fragment stored in back stack. Does nothing and returns
     * false if no fragment is back-stacked.
     */
    public boolean back() {
      int position = mContainer.getCurrentItem();
      Fragment fragment = mBackFragments.get(position);
      if (fragment == null) {
        // Nothing to go back.
        return false;
      }

      // Restore the remembered fragment and remove it from back fragments.
      this.replace(position, fragment);
      mBackFragments.set(position, null);
      return true;
    }

    /**
     * Returns fragment of a page at specified position.
     */
    @Override
    public Fragment getItem(int position) {
      // If fragment not yet initialized, create its instance.
      if (mFragments.get(position) == null) {
        switch (position) {
          case FIRST_PAGE: {
            mFragments.set(FIRST_PAGE, new DefaultFirstFragment());
          }
          break;
          case SECOND_PAGE: {
            mFragments.set(SECOND_PAGE, new DefaultSecondFragment());
          }
          break;
          case THIRD_PAGE: {
            mFragments.set(THIRD_PAGE, new DefaultThirdFragment());
          }
          break;
        }
      }

      // Return fragment instance at requested position.
      return mFragments.get(position);
    }

    /**
     * Custom item ID resolution. Needed for proper page fragment caching.
     * @see FragmentPagerAdapter#getItemId(int).
     */
    @Override
    public long getItemId(int position) {
      // Fragments from second level page hierarchy have their ID raised above 100. This is
      // important to FragmentPagerAdapter because it is caching fragments to FragmentManager with
      // this item ID key.
      Fragment item = mFragments.get(position);
      if (item != null) {
        if ((item instanceof NewFirstFragment) || (item instanceof NewSecondFragment) ||
          (item instanceof NewThirdFragment)) {
          return 100 + position;
        }
      }

      return position;
    }

    /**
     * Returns number of pages.
     */
    @Override
    public int getCount() {
      return mFragments.size();
    }

    @Override
    public int getItemPosition(Object object)
    {
      int position = POSITION_UNCHANGED;
      if ((object instanceof DefaultFirstFragment) || (object instanceof NewFirstFragment)) {
        if (object.getClass() != mFragments.get(FIRST_PAGE).getClass()) {
          position = POSITION_NONE;
        }
      }
      if ((object instanceof DefaultSecondragment) || (object instanceof NewSecondFragment)) {
        if (object.getClass() != mFragments.get(SECOND_PAGE).getClass()) {
          position = POSITION_NONE;
        }
      }
      if ((object instanceof DefaultThirdFragment) || (object instanceof NewThirdFragment)) {
        if (object.getClass() != mFragments.get(THIRD_PAGE).getClass()) {
          position = POSITION_NONE;
        }
      }
      return position;
    }

    private ViewPager mContainer;
    private FragmentManager mFragmentManager;

    /**
     * List of page fragments.
     */
    private List<Fragment> mFragments;

    /**
     * List of page fragments to return to in onBack();
     */
    private List<Fragment> mBackFragments;
  }

  /**
   * Tab fragments adapter.
   */
  private PagerAdapter mPagerAdapter;
}

うーん、このコードには別の問題があります。削除して追加したフラグメントからアクティビティを開始すると、「Failure Saving state:active NewFirstFragment {405478a0} has clear index:-1 "(full stack trace:nopaste.info/547a23dfea.html)。Androidのソースを見ると、バックスタックと関係があることがわかりましたが、今のところそれを修正する方法がわかりません。誰か手がかりはありますか?
Blackhex 2012

2
<pre> mFragmentManager.beginTransaction()。setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN).remove()の代わりに<code> mFragmentManager.beginTransaction()。detach(old_fragment).attach(fragment).commit(); </ code>を使用してクール上記の例のold_fragment).add(mContainer.getId()、fragment).commit(); </ pre>は、両方の問題を修正するようです:-)。
Blackhex 2012

1
この方法を使用する場合は、デバイスを回転させるときにAndroidにアクティビティの再作成を許可しないようにしてください。そうしないと、PagerAdapterが再作成され、mBackFragmentsが失われます。これもFragmentManagerを深刻に混乱させます。BackStackを使用すると、PagerAdapterのより複雑な実装を犠牲にして、この問題を回避(あなたはFragmentPagerAdapterから継承することはできませんつまり。)
ノーマン

6

インデックス2と3の3つの要素と2つのサブ要素を持つViewPagerを作成しました。

ここに画像の説明を入力してください

StackOverFlowからの以前の質問と回答の助けを借りてこれを実装しました。ここにリンクがあります。

ViewPagerChildFragments


デバイスを回転させると、プロジェクトは失敗します。
Dory

6

内部のフラグメントを置き換えるにViewPagerViewPagerPagerAdapterFragmentStatePagerAdapterクラスのソースコードをプロジェクトに移動し、次のコードを追加します。

ViewPager

public void notifyItemChanged(Object oldItem, Object newItem) {
    if (mItems != null) {
            for (ItemInfo itemInfo : mItems) {
                        if (itemInfo.object.equals(oldItem)) {
                                itemInfo.object = newItem;
                        }
                    }
       }
       invalidate();
    }

FragmentStatePagerAdapter:

public void replaceFragmetns(ViewPager container, Fragment oldFragment, Fragment newFragment) {
       startUpdate(container);

       // remove old fragment

       if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
       int position = getFragmentPosition(oldFragment);
        while (mSavedState.size() <= position) {
            mSavedState.add(null);
        }
        mSavedState.set(position, null);
        mFragments.set(position, null);

        mCurTransaction.remove(oldFragment);

        // add new fragment

        while (mFragments.size() <= position) {
            mFragments.add(null);
        }
        mFragments.set(position, newFragment);
        mCurTransaction.add(container.getId(), newFragment);

       finishUpdate(container);

       // ensure getItem returns newFragemtn after calling handleGetItemInbalidated()
       handleGetItemInbalidated(container, oldFragment, newFragment);

       container.notifyItemChanged(oldFragment, newFragment);
    }

protected abstract void handleGetItemInbalidated(View container, Fragment oldFragment, Fragment newFragment);
protected abstract int  getFragmentPosition(Fragment fragment);

handleGetItemInvalidated()それが次に呼び出された後、getItem()newFragment getFragmentPosition()がアダプタ内のフラグメントの位置を返すことを保証します。

今、フラグメントを置き換えるには

mAdapter.replaceFragmetns(mViewPager, oldFragment, newFragment);

サンプルプロジェクトに興味がある場合は、ソースを尋ねてください。


メソッドgetFragmentPosition()およびhandleGetItemInbalidated()はどのようにする必要がありますか?私は.. NewFragmentを返すためのgetItem()を更新することができません助けてください..

こんにちは!このリンクでテストプロジェクトを見つけてくださいfiles.mail.ru/eng?back=%2FEZU6G4
AndroidTeam At Mail.Ru

1
デバイスを回転すると、テストプロジェクトが失敗します。
2012年

1
@ AndroidTeamAtMail.Ruリンクの有効期限が切れているようです。コードを共有してください
Sunny

私の答えを見ることができます。フラグメントを置き換え、最初のフラグメントに戻ります。
Lyd

4

AndroidTeamのソリューションで問題なく動作しますが、戻るように戻す機能が必要であることがわかりましたが、FrgmentTransaction.addToBackStack(null) これを追加するだけで、ViewPagerに通知せずにフラグメントが置き換えられるだけです。提供されたソリューションとこの小さな拡張機能を組み合わせると、アクティビティのonBackPressed()メソッドをオーバーライドするだけで前の状態に戻ることができます。最大の欠点は、一度に1つしか戻らないため、複数回のバッククリックが発生する可能性があることです。

private ArrayList<Fragment> bFragments = new ArrayList<Fragment>();
private ArrayList<Integer> bPosition = new ArrayList<Integer>();

public void replaceFragmentsWithBackOut(ViewPager container, Fragment oldFragment, Fragment newFragment) {
    startUpdate(container);

    // remove old fragment

    if (mCurTransaction == null) {
         mCurTransaction = mFragmentManager.beginTransaction();
     }
    int position = getFragmentPosition(oldFragment);
     while (mSavedState.size() <= position) {
         mSavedState.add(null);
     }

     //Add Fragment to Back List
     bFragments.add(oldFragment);

     //Add Pager Position to Back List
     bPosition.add(position);

     mSavedState.set(position, null);
     mFragments.set(position, null);

     mCurTransaction.remove(oldFragment);

     // add new fragment

     while (mFragments.size() <= position) {
         mFragments.add(null);
     }
     mFragments.set(position, newFragment);
     mCurTransaction.add(container.getId(), newFragment);

    finishUpdate(container);

    // ensure getItem returns newFragemtn after calling handleGetItemInbalidated()
    handleGetItemInvalidated(container, oldFragment, newFragment);

    container.notifyItemChanged(oldFragment, newFragment);
 }


public boolean popBackImmediate(ViewPager container){
    int bFragSize = bFragments.size();
    int bPosSize = bPosition.size();

    if(bFragSize>0 && bPosSize>0){
        if(bFragSize==bPosSize){
            int last = bFragSize-1;
            int position = bPosition.get(last);

            //Returns Fragment Currently at this position
            Fragment replacedFragment = mFragments.get(position);               
            Fragment originalFragment = bFragments.get(last);

            this.replaceFragments(container, replacedFragment, originalFragment);

            bPosition.remove(last);
            bFragments.remove(last);

            return true;
        }
    }

    return false;       
}

これが誰かを助けることを願っています。

また、限りでgetFragmentPosition()は、それはほぼgetItem()逆です。どのフラグメントがどこに移動するかがわかっているので、それが正しい位置に戻ることを確認してください。以下に例を示します。

    @Override
    protected int getFragmentPosition(Fragment fragment) {
            if(fragment.equals(originalFragment1)){
                return 0;
            }
            if(fragment.equals(replacementFragment1)){
                return 0;
            }
            if(fragment.equals(Fragment2)){
                return 1;
            }
        return -1;
    }

3

あなたのonCreateViewメソッドでcontainerは、実際にはViewPagerインスタンスです。

だから、電話するだけ

ViewPager vpViewPager = (ViewPager) container;
vpViewPager.setCurrentItem(1);

の現在のフラグメントが変更されますViewPager


1
何時間もの検索から、すべてのタブを正しく機能させるために必要だったのは、この1行のコードでした。 vpViewPager.setCurrentItem(1);。私は一人一人の例を経験しましたが、最終的にあなたのところにたどり着くまでは何も起こりませんでした。ありがとうございました。
ブランドン、

1
心が吹き飛ばされました...これは素晴らしいです!! コンテナー変数がビューページャー自体であることを知りませんでした。この回答は、他のすべての長い回答よりも先に検討する必要があります。さらに、リスナー変数がフラグメントコンストラクターに渡されないため、構成が変更されたときにAndroidがデフォルトの引数なしコンストラクターを使用してフラグメントを自動再作成するときに、すべての実装が簡素化されます。
Kevin Lee

3
これは既存のタブに変わるだけではありませんか?これはフラグメントの置き換えとどのように関連していますか?
2016年

2
@behelitに同意します。これは質問の答えにはなりません。単にViewPager別のページに移動するだけで、フラグメントは置き換えられません。
Yoann Hercouet

2

この問題に対する比較的簡単な解決策を次に示します。前者は未使用のフラグメントを削除し、後者はインスタンスを保持するため、このソリューションの鍵はのFragmentStatePagerAdapter代わりにFragmentPagerAdapter使用することです。2つ目は、POSITION_NONEgetItem()での使用です。私はフラグメントを追跡するために単純なリストを使用しました。私の要件は、フラグメントのリスト全体を一度に新しいリストに置き換えることでしたが、以下は個々のフラグメントを置き換えるように簡単に変更できます。

public class MyFragmentAdapter extends FragmentStatePagerAdapter {
    private List<Fragment> fragmentList = new ArrayList<Fragment>();
    private List<String> tabTitleList = new ArrayList<String>();

    public MyFragmentAdapter(FragmentManager fm) {
        super(fm);
    }

    public void addFragments(List<Fragment> fragments, List<String> titles) {
        fragmentList.clear();
        tabTitleList.clear();
        fragmentList.addAll(fragments);
        tabTitleList.addAll(titles);
        notifyDataSetChanged();
    }

    @Override
    public int getItemPosition(Object object) {
        if (fragmentList.contains(object)) {
            return POSITION_UNCHANGED;
        }
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int item) {
        if (item >= fragmentList.size()) {
            return null;
        }
        return fragmentList.get(item);
    }

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

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

2

Stacksで動作するソリューションも作成しました。これはよりモジュール化されたアプローチなので、で各フラグメントと詳細フラグメントを指定する必要はありませんFragmentPagerAdapter。これは、ActionbarSherlockの例の上に構築されており、私がGoogleデモアプリから正しいかどうかを判断します。

/**
 * This is a helper class that implements the management of tabs and all
 * details of connecting a ViewPager with associated TabHost.  It relies on a
 * trick.  Normally a tab host has a simple API for supplying a View or
 * Intent that each tab will show.  This is not sufficient for switching
 * between pages.  So instead we make the content part of the tab host
 * 0dp high (it is not shown) and the TabsAdapter supplies its own dummy
 * view to show as the tab content.  It listens to changes in tabs, and takes
 * care of switch to the correct paged in the ViewPager whenever the selected
 * tab changes.
 * 
 * Changed to support more Layers of fragments on each Tab.
 * by sebnapi (2012)
 * 
 */
public class TabsAdapter extends FragmentPagerAdapter
        implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener {
    private final Context mContext;
    private final TabHost mTabHost;
    private final ViewPager mViewPager;

    private ArrayList<String> mTabTags = new ArrayList<String>();
    private HashMap<String, Stack<TabInfo>> mTabStackMap = new HashMap<String, Stack<TabInfo>>();

    static final class TabInfo {
        public final String tag;
        public final Class<?> clss;
        public Bundle args;

        TabInfo(String _tag, Class<?> _class, Bundle _args) {
            tag = _tag;
            clss = _class;
            args = _args;
        }
    }

    static class DummyTabFactory implements TabHost.TabContentFactory {
        private final Context mContext;

        public DummyTabFactory(Context context) {
            mContext = context;
        }

        @Override
        public View createTabContent(String tag) {
            View v = new View(mContext);
            v.setMinimumWidth(0);
            v.setMinimumHeight(0);
            return v;
        }
    }

    public interface SaveStateBundle{
        public Bundle onRemoveFragment(Bundle outState);
    }

    public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager) {
        super(activity.getSupportFragmentManager());
        mContext = activity;
        mTabHost = tabHost;
        mViewPager = pager;
        mTabHost.setOnTabChangedListener(this);
        mViewPager.setAdapter(this);
        mViewPager.setOnPageChangeListener(this);
    }

    /**
     * Add a Tab which will have Fragment Stack. Add Fragments on this Stack by using
     * addFragment(FragmentManager fm, String _tag, Class<?> _class, Bundle _args)
     * The Stack will hold always the default Fragment u add here.
     * 
     * DON'T ADD Tabs with same tag, it's not beeing checked and results in unexpected
     * beahvior.
     * 
     * @param tabSpec
     * @param clss
     * @param args
     */
    public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args){
        Stack<TabInfo> tabStack = new Stack<TabInfo>();

        tabSpec.setContent(new DummyTabFactory(mContext));
        mTabHost.addTab(tabSpec);
        String tag = tabSpec.getTag();
        TabInfo info = new TabInfo(tag, clss, args);

        mTabTags.add(tag);                  // to know the position of the tab tag 
        tabStack.add(info);
        mTabStackMap.put(tag, tabStack);
        notifyDataSetChanged();
    }

    /**
     * Will add the Fragment to Tab with the Tag _tag. Provide the Class of the Fragment
     * it will be instantiated by this object. Proivde _args for your Fragment.
     * 
     * @param fm
     * @param _tag
     * @param _class
     * @param _args
     */
    public void addFragment(FragmentManager fm, String _tag, Class<?> _class, Bundle _args){
        TabInfo info = new TabInfo(_tag, _class, _args);
        Stack<TabInfo> tabStack = mTabStackMap.get(_tag);   
        Fragment frag = fm.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + mTabTags.indexOf(_tag));
        if(frag instanceof SaveStateBundle){
            Bundle b = new Bundle();
            ((SaveStateBundle) frag).onRemoveFragment(b);
            tabStack.peek().args = b;
        }
        tabStack.add(info);
        FragmentTransaction ft = fm.beginTransaction();
        ft.remove(frag).commit();
        notifyDataSetChanged();
    }

    /**
     * Will pop the Fragment added to the Tab with the Tag _tag
     * 
     * @param fm
     * @param _tag
     * @return
     */
    public boolean popFragment(FragmentManager fm, String _tag){
        Stack<TabInfo> tabStack = mTabStackMap.get(_tag);   
        if(tabStack.size()>1){
            tabStack.pop();
            Fragment frag = fm.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + mTabTags.indexOf(_tag));
            FragmentTransaction ft = fm.beginTransaction();
            ft.remove(frag).commit();
            notifyDataSetChanged();
            return true;
        }
        return false;
    }

    public boolean back(FragmentManager fm) {
        int position = mViewPager.getCurrentItem();
        return popFragment(fm, mTabTags.get(position));
    }

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

    @Override
    public int getItemPosition(Object object) {
        ArrayList<Class<?>> positionNoneHack = new ArrayList<Class<?>>();
        for(Stack<TabInfo> tabStack: mTabStackMap.values()){
            positionNoneHack.add(tabStack.peek().clss);
        }   // if the object class lies on top of our stacks, we return default
        if(positionNoneHack.contains(object.getClass())){
            return POSITION_UNCHANGED;
        }
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int position) {
        Stack<TabInfo> tabStack = mTabStackMap.get(mTabTags.get(position));
        TabInfo info = tabStack.peek();
        return Fragment.instantiate(mContext, info.clss.getName(), info.args);
    }

    @Override
    public void onTabChanged(String tabId) {
        int position = mTabHost.getCurrentTab();
        mViewPager.setCurrentItem(position);
    }

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

    @Override
    public void onPageSelected(int position) {
        // Unfortunately when TabHost changes the current tab, it kindly
        // also takes care of putting focus on it when not in touch mode.
        // The jerk.
        // This hack tries to prevent this from pulling focus out of our
        // ViewPager.
        TabWidget widget = mTabHost.getTabWidget();
        int oldFocusability = widget.getDescendantFocusability();
        widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
        mTabHost.setCurrentTab(position);
        widget.setDescendantFocusability(oldFocusability);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
    }

}

これをMainActivityの戻るボタン機能に追加します。

@Override
public void onBackPressed() {
  if (!mTabsAdapter.back(getSupportFragmentManager())) {
    super.onBackPressed();
  }
}

削除されたときにフラグメント状態保存したい場合。フラグメントにSaveStateBundle、保存状態のバンドルを関数に返すインターフェースを実装させます。によるインスタンス化後にバンドルを取得しますthis.getArguments()

次のようにタブをインスタンス化できます

mTabsAdapter.addTab(mTabHost.newTabSpec("firstTabTag").setIndicator("First Tab Title"),
                FirstFragmentActivity.FirstFragmentFragment.class, null);

タブスタックの上にフラグメントを追加する場合も同様に機能します。 重要:2つのタブの上に同じクラスの2つのインスタンスを配置したい場合、機能しないと思います。私はこのソリューションを一緒にすばやく実行したので、それを経験することなく共有することしかできません。


戻るボタンを押すとnullpointer例外が発生し、置換されたフラグメントも表示されません。だから私は何か間違っているのかもしれません:フラグメントを正しく変更するための正しい呼び出しは何ですか?
Jeroen、2012年

更新:ほとんどのものを修正しましたが、新しいフラグメントを追加した後に戻るボタンを押して別のタブに移動して戻ると、nullpointer例外が発生します。だから私は2つの質問があります:1:タブの履歴を削除して、最新のフラグメント(スタックの最上部)だけがまだ存在していることを確認するにはどうすればよいですか?2:nullpointer例外を取り除くにはどうすればよいですか?
Jeroen、2012年

@Jeroen 1:uはmTabStackMapから適切なスタックを取得し、スタックの一番下にあるすべてのTabInfoオブジェクトを削除できます。2:言うのは非常に難しいです。あなたのコードの失敗ではありませんか?これは、直接の近所(右または左)にあるタブに切り替えた場合(その後戻る場合)にも発生しますか?
12

OK、私は新しい問題に遭遇しました:画面を回転させると、スタックは再び空のように見えますか?以前のアイテムのトラックが緩んでいるようです...
Jeroen

1
@sebありがとう。私はこのコードを使用していましたが、ネストされたフラグメントが可能になり、問題はなくなりました。:)
Garcon

2

ビューページャーでフラグメントを置き換えることはかなり複雑ですが、非常に可能で非常に滑らかに見えることがあります。まず、フラグメントの削除と追加をビューページャー自体に処理させる必要があります。SearchFragment内でフラグメントを置き換えると、ビューページャーはフラグメントビューを保持します。そのため、SearchFragmentを置き換えようとするとSearchFragmentが削除されるため、最終的に空白のページになります。

解決策は、ビューページャーの内部でリスナーの外部で行われた変更を処理するリスナーを作成することです。そのため、最初にこのコードをアダプターの下部に追加します。

public interface nextFragmentListener {
    public void fragment0Changed(String newFragmentIdentification);
}

次に、フラグメントを変更したいときにリスナーになるビューページャーにプライベートクラスを作成する必要があります。たとえば、次のようなものを追加できます。作成されたばかりのインターフェースを実装していることに注意してください。したがって、このメソッドを呼び出すたびに、以下のクラス内のコードが実行されます。

private final class fragmentChangeListener implements nextFragmentListener {


    @Override
    public void fragment0Changed(String fragment) {
        //I will explain the purpose of fragment0 in a moment
        fragment0 = fragment;
        manager.beginTransaction().remove(fragAt0).commit();

        switch (fragment){
            case "searchFragment":
                fragAt0 = SearchFragment.newInstance(listener);
                break;
            case "searchResultFragment":
                fragAt0 = Fragment_Table.newInstance(listener);
                break;
        }

        notifyDataSetChanged();
    }

ここで指摘すべき主なことが2つあります。

  1. fragAt0は「柔軟な」フラグメントです。それはあなたがそれを与えるどんなフラグメントタイプをとることもできます。これにより、位置0のフラグメントを目的のフラグメントに変更するのに最適です。
  2. 「newInstance(listener)」コンストラクタに配置されたリスナーに注意してください。これらは、fragment0Changed(String newFragmentIdentification) `を呼び出す方法です。次のコードは、フラグメント内にリスナーを作成する方法を示しています。

    static nextFragmentListener listenerSearch;

        public static Fragment_Journals newInstance(nextFragmentListener listener){
                listenerSearch = listener;
                return new Fragment_Journals();
            }

あなたの内部の変化を呼ぶことができます onPostExecute

private class SearchAsyncTask extends AsyncTask<Void, Void, Void>{

    protected Void doInBackground(Void... params){
        .
        .//some more operation
        .
    }
    protected void onPostExecute(Void param){

        listenerSearch.fragment0Changed("searchResultFragment");
    }

}

これにより、ビューページャー内のコードがトリガーされ、フラグメントが位置0のfragAt0に切り替わり、新しいsearchResultFragmentになります。それが機能的になる前に、viewpagerに追加する必要がある2つの小さなピースがあります。

1つは、viewpagerのgetItemオーバーライドメソッドにあります。

@Override
public Fragment getItem(int index) {

    switch (index) {
    case 0:
        //this is where it will "remember" which fragment you have just selected. the key is to set a static String fragment at the top of your page that will hold the position that you had just selected.  

        if(fragAt0 == null){

            switch(fragment0){
            case "searchFragment":
                fragAt0 = FragmentSearch.newInstance(listener);
                break;
            case "searchResultsFragment":
                fragAt0 = FragmentSearchResults.newInstance(listener);
                break;
            }
        }
        return fragAt0;
    case 1:
        // Games fragment activity
        return new CreateFragment();

    }

この最後の部分がなくても、空白のページが表示されます。ちょっとラメですが、それはviewPagerの不可欠な部分です。ビューページャーのgetItemPositionメソッドをオーバーライドする必要があります。通常、このメソッドはPOSITION_UNCHANGEDを​​返します。これは、すべてを同じに保つようにビューページャーに指示するため、新しいフラグメントをページに配置するためにgetItemが呼び出されることはありません。これはあなたができることの例です

public int getItemPosition(Object object)
{
    //object is the current fragment displayed at position 0.  
    if(object instanceof SearchFragment && fragAt0 instanceof SearchResultFragment){
        return POSITION_NONE;
    //this condition is for when you press back
    }else if{(object instanceof SearchResultFragment && fragAt0 instanceof SearchFragment){
        return POSITION_NONE;
    }
        return POSITION_UNCHANGED
}

私が言ったように、コードは非常に複雑になりますが、基本的には状況に応じてカスタムアダプターを作成する必要があります。私が言及したことは、フラグメントを変更することを可能にします。私が忍耐するようにすべてを浸すのにはおそらく長い時間がかかるでしょうが、それはすべて理にかなっています。本当に洗練されたアプリケーションを作成できるので、時間をかけて完全に価値があります。

これが戻るボタンを処理するためのナゲットです。これをMainActivityの中に入れます

 public void onBackPressed() {
    if(mViewPager.getCurrentItem() == 0) {
        if(pagerAdapter.getItem(0) instanceof FragmentSearchResults){
            ((Fragment_Table) pagerAdapter.getItem(0)).backPressed();
        }else if (pagerAdapter.getItem(0) instanceof FragmentSearch) {
            finish();
        }
    }

FragmentSearchResults内に、fragment0changedを呼び出すbackPressed()というメソッドを作成する必要があります。これは、前に示したコードと並行して、戻るボタンを押すことを処理します。ビューページャーを変更するために、コードで頑張ってください。それは多くの作業を必要とし、私が見つけた限りでは、迅速な適応はありません。私が言ったように、あなたは基本的にカスタムviewpagerアダプターを作成しており、リスナーを使用して必要なすべての変更を処理させる


この解決策をありがとう。しかし、画面を回転させた後に何か問題がありましたか?画面を回転して同じタブで別のフラグメントをリロードしようとすると、アプリがクラッシュします。
AnteGemini 2018

私はAndroid開発を行ってからしばらく時間が経過しましたが、アプリが頼りにしていないデータの一部があると私は推測しています。回転すると、アクティビティ全体が再起動し、必要なすべてのデータなしで以前の状態をリロードしようとするとクラッシュする可能性があります(たとえばonPause、onResumeのバンドルから)
user3331142

2

これはそれを達成するための私の方法です。

まず、ボタンクリックイベントを実装するRoot_fragment内部viewPagerタブを追加しますfragment。例;

@Override
public Fragment getItem(int position) {
  if(position==0)
      return RootTabFragment.newInstance();
  else
      return SecondPagerFragment.newInstance();
}

まず、フラグメント変更のためRootTabFragmentに含める必要がありますFragmentLayout

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/root_frame"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
</FrameLayout>

その後、内部のRootTabFragment onCreateView実装fragmentChangeのためのあなたFirstPagerFragment

getChildFragmentManager().beginTransaction().replace(R.id.root_frame, FirstPagerFragment.newInstance()).commit();

その後、onClick内部にボタンのイベントを実装し、そのFirstPagerFragmentようにフラグメントを変更します。

getChildFragmentManager().beginTransaction().replace(R.id.root_frame, NextFragment.newInstance()).commit();

これがあなたを助けることを願っています。


1

途中で新しいフラグメントを追加したり、現在のフラグメントを置き換えたりする場合でも問題なく機能する簡単な解決策を見つけました。私のソリューションでgetItemId()は、フラグメントごとに一意のIDを返すようにオーバーライドする必要があります。デフォルトの位置ではありません。

そこにあるよ:

public class DynamicPagerAdapter extends FragmentPagerAdapter {

private ArrayList<Page> mPages = new ArrayList<Page>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

public DynamicPagerAdapter(FragmentManager fm) {
    super(fm);
}

public void replacePage(int position, Page page) {
    mPages.set(position, page);
    notifyDataSetChanged();
}

public void setPages(ArrayList<Page> pages) {
    mPages = pages;
    notifyDataSetChanged();
}

@Override
public Fragment getItem(int position) {
    if (mPages.get(position).mPageType == PageType.FIRST) {
        return FirstFragment.newInstance(mPages.get(position));
    } else {
        return SecondFragment.newInstance(mPages.get(position));
    }
}

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

@Override
public long getItemId(int position) {
    // return unique id
    return mPages.get(position).getId();
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment fragment = (Fragment) super.instantiateItem(container, position);
    while (mFragments.size() <= position) {
        mFragments.add(null);
    }
    mFragments.set(position, fragment);
    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);
    mFragments.set(position, null);
}

@Override
public int getItemPosition(Object object) {
    PagerFragment pagerFragment = (PagerFragment) object;
    Page page = pagerFragment.getPage();
    int position = mFragments.indexOf(pagerFragment);
    if (page.equals(mPages.get(position))) {
        return POSITION_UNCHANGED;
    } else {
        return POSITION_NONE;
    }
}
}

注意:この例FirstFragmentSecondFragmentは、抽象クラスPageFragment を拡張していgetPage()ます。


1

私はwizeと同じようなことをしていますが、私の答えでは、いつでも2つのフラグメントを切り替えることができます。そして、賢明な答えでは、画面の向きをそのように変更するときにいくつかの問題があります。これはPagerAdapterが次のように見えることです:

    public class MyAdapter extends FragmentPagerAdapter
{
    static final int NUM_ITEMS = 2;
    private final FragmentManager mFragmentManager;
    private Fragment mFragmentAtPos0;
     private Map<Integer, String> mFragmentTags;
     private boolean isNextFragment=false;

    public MyAdapter(FragmentManager fm)
    {
        super(fm);
        mFragmentManager = fm;
         mFragmentTags = new HashMap<Integer, String>();
    }

    @Override
    public Fragment getItem(int position)
    {
        if (position == 0)
        {


            if (isPager) {
                mFragmentAtPos0 = new FirstPageFragment();
            } else {
                mFragmentAtPos0 = new NextFragment();
            }
            return mFragmentAtPos0;
        }
        else
            return SecondPageFragment.newInstance();
    }

    @Override
    public int getCount()
    {
        return NUM_ITEMS;
    }


 @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object obj = super.instantiateItem(container, position);
        if (obj instanceof Fragment) {
            // record the fragment tag here.
            Fragment f = (Fragment) obj;
            String tag = f.getTag();
            mFragmentTags.put(position, tag);
        }
        return obj;
    }


    public void onChange(boolean isNextFragment) {

        if (mFragmentAtPos0 == null)
            mFragmentAtPos0 = getFragment(0);
        if (mFragmentAtPos0 != null)
            mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();


        if (!isNextFragment) {
            mFragmentAtFlashcards = new FirstPageFragment();
        } else {
            mFragmentAtFlashcards = new NextFragment();
        }

        notifyDataSetChanged();


    }


    @Override
    public int getItemPosition(Object object)
    {
        if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)
            return POSITION_NONE;
         if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstPageFragment)
            return POSITION_NONE;
        return POSITION_UNCHANGED;
    }


    public Fragment getFragment(int position) {
        String tag = mFragmentTags.get(position);
        if (tag == null)
            return null;
        return mFragmentManager.findFragmentByTag(tag);
    }
}

アダプターコンテナーアクティビティに実装したリスナーは、アタッチするときにフラグメントに配置します。これがアクティビティです。

    public class PagerContainerActivity extends AppCompatActivity implements ChangeFragmentListener {

//...

  @Override
    public void onChange(boolean isNextFragment) {
        if (pagerAdapter != null)
            pagerAdapter.onChange(isNextFragment);


    }

//...
}

次に、呼び出しをアタッチするときにリスナーを配置するフラグメントで:

public class FirstPageFragment extends Fragment{


private ChangeFragmentListener changeFragmentListener;


//...
 @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        changeFragmentListener = ((PagerContainerActivity) activity);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        changeFragmentListener = null;
    }
//...
//in the on click to change the fragment
changeFragmentListener.onChange(true);
//...
}

そして最後にリスナー:

public interface changeFragmentListener {

    void onChange(boolean isNextFragment);

}

1

私は@wize@mdelolmoの回答に従って、解決策を見つけました。トンありがとう。しかし、メモリ消費を改善するためにこれらのソリューションを少し調整しました。

私が観察した問題:

Fragment置き換えられたインスタンスを保存します。私の場合、それは保持するフラグメントでありMapView、私はそれが高価であると思いました。それで、私はそれ自体のFragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED)代わりに維持していFragmentます。

これが私の実装です。

  public static class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {

    private SwitchFragListener mSwitchFragListener;
    private Switch mToggle;
    private int pagerAdapterPosChanged = POSITION_UNCHANGED;
    private static final int TOGGLE_ENABLE_POS = 2;


    public DemoCollectionPagerAdapter(FragmentManager fm, Switch toggle) {
        super(fm);
        mToggle = toggle;

        mSwitchFragListener = new SwitchFragListener();
        mToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                mSwitchFragListener.onSwitchToNextFragment();
            }
        });
    }

    @Override
    public Fragment getItem(int i) {
        switch (i)
        {
            case TOGGLE_ENABLE_POS:
                if(mToggle.isChecked())
                {
                    return TabReplaceFragment.getInstance();
                }else
                {
                    return DemoTab2Fragment.getInstance(i);
                }

            default:
                return DemoTabFragment.getInstance(i);
        }
    }

    @Override
    public int getCount() {
        return 5;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return "Tab " + (position + 1);
    }

    @Override
    public int getItemPosition(Object object) {

        //  This check make sures getItem() is called only for the required Fragment
        if (object instanceof TabReplaceFragment
                ||  object instanceof DemoTab2Fragment)
            return pagerAdapterPosChanged;

        return POSITION_UNCHANGED;
    }

    /**
     * Switch fragments Interface implementation
     */
    private final class SwitchFragListener implements
            SwitchFragInterface {

        SwitchFragListener() {}

        public void onSwitchToNextFragment() {

            pagerAdapterPosChanged = POSITION_NONE;
            notifyDataSetChanged();
        }
    }

    /**
     * Interface to switch frags
     */
    private interface SwitchFragInterface{
        void onSwitchToNextFragment();
    }
}

ここにデモリンクがあります。https://youtu.be/l_62uhKkLyM

デモの目的で、2つのフラグメントTabReplaceFragmentと2 DemoTab2Fragment番目の位置を使用しました。他のすべてのケースでは、DemoTabFragmentインスタンスを使用しています。

説明:

私は渡しているSwitchにActivityからDemoCollectionPagerAdapter。このスイッチの状態に基づいて、正しいフラグメントを表示します。スイッチチェックが変更されると、SwitchFragListeneronSwitchToNextFragmentメソッドが呼び出され、pagerAdapterPosChanged変数の値がに変更されPOSITION_NONEます。POSITION_NONEの詳細を確認してください。これはgetItemを無効にし、正しいフラグメントをそこにインスタンス化するロジックがあります。説明が少し厄介な場合は申し訳ありません。

元のアイデアについて@wizeと@mdelolmoに再度感謝します。

これがお役に立てば幸いです。:)

この実装に欠陥があるかどうか教えてください。それは私のプロジェクトに大いに役立ちます。


0

調査後、短いコードで解決策を見つけました。まず、フラグメントでパブリックインスタンスを作成し、フラグメントが方向の変更時に再作成されない場合は、onSaveInstanceStateでフラグメントを削除します。

 @Override
public void onSaveInstanceState(Bundle outState) {
    if (null != mCalFragment) {
        FragmentTransaction bt = getChildFragmentManager().beginTransaction();
        bt.remove(mFragment);
        bt.commit();
    }
    super.onSaveInstanceState(outState);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.