【android】ViewPagerでタブ毎に戻れる画面を実装する方法

はじめに

今回やりたかったのはこんな画面。
f:id:yoppy0066:20170131000025g:plain:w250

なんでもiOSと比較するのはあれだけど、iOSだとUiNavigationControllerを持つUITabbarControllerみたいなイメージ。
今回はタブは表示していないけどViewPagerをタブ表示するサンプルはたくさんあるので問題なかった。
戻るボタンでの戻り先をタブごとに保持しているようなイメージ。

クラス構成としてはこんなイメージ。
Activityは1つでタブごとにRootFragmentを持たせて、実際に画面に表示するフラグメントを操作するイメージ(ここではFirstFragmentとSecondFragment)。

f:id:yoppy0066:20170131000304p:plain

実装

MainActivityの実装

ここでやることはViewPagerとRootFragmentを紐づける処理。
あとは戻るボタンされた時に選択中のタブのバックスタックから1つ取り出して戻す処理。
戻るボタンされたときに選択中のタブがわかるようにタブ選択されるたびに選択中のタブ番号を保持する。

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private ViewPager mPager;
    private String[] mTitles = {"タブ1", "タブ2"};

    // 選択中のタブ番号を保持
    private int selected;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
        setTitle("タブ1");
	mPager = (ViewPager)findViewById(R.id.viewpager);

        mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
		selected = position;
		setTitle(mTitles[position]);
            }
            ・・・
        });

        FragmentPagerAdapter adapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
            @Override
            public Fragment getItem(int position) {
                return RootFragment.newInstance(mTags[position]);
            }

            @Override
            public int getCount() {
                return 2;
            }
            ・・・
        };
        mPager.setAdapter(adapter);
    }

    // 戻るボタン
    @Override
    public void onBackPressed() {

        // 選択中のタブのRootFragmentにバックスタックがあれば戻る処理
        FragmentManager fm = getSupportFragmentManager();
        List<Fragment> fragments = fm.getFragments();
        Fragment fragment = fragments.get(selected);
        FragmentManager fragmentManager = fragment.getChildFragmentManager();
        if (0 < fragmentManager.getBackStackEntryCount()) {
            fragmentManager.popBackStack();
        }
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:id="@+id/activity_main"
                ・・・
                />

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

</RelativeLayout>
RootFragmentの実装

ここではFirstFragmentを表示するだけ。それならRootFragmentなんて不要と思って最初実装していた。
けど、ViewPagerの中のFragmentをreplaceしても中身がレイアウトの表示が変わらない問題に直面。で

http://www.pineappslab.com/post/fragments-viewpager/
ここの記事を発見。ここの通りにやらせていただきました。

RootFragment.java

public class RootFragment extends Fragment {

    public static RootFragment newInstance() {
        RootFragment fragment = new RootFragment();
        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_root, container, false);
        FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.root_frame, FirstFragment.newInstance());
        fragmentTransaction.commit();
        return view;
    }
}

fragment_root.xml

<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>
FirstFragmentの実装

FirstFragment.java

public class FirstFragment extends Fragment {

    public static FirstFragment newInstance(String tag) {
        FirstFragment fragment = new FirstFragment();
            Bundle args = new Bundle();
            return fragment;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_first, container, false);
            Button button = (Button) view.findViewById(R.id.button);
            button.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                FragmentTransaction fragmentTransaction = getParentFragment().getChildFragmentManager().beginTransaction();
                fragmentTransaction.replace(R.id.root_frame, SecondFragment.newInstance());
                fragmentTransaction.addToBackStack();
                fragmentTransaction.commit();
            }
        });
         return view;
    }
}

fragment_first.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

  <TextView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_marginBottom="10sp"
      android:text="FirstFragment"
      android:textSize="30sp"/>

  <Button android:id="@+id/button"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:background="#ffff00"
          android:text="次へ"
          android:textSize="18sp"/>

</LinearLayout>

SecondFragmentは画面表示するだけだからいいかな。。。

おわりに

よくわからないで調べながらやってたからこの画面実装するポイントは他にもけっこうあるはずなんだけど出てこない。。。
もうちょっと内容を分割して整理した方が良さそう。以上です