読者です 読者をやめる 読者になる 読者になる

ほげほげ(仮)

仮死状態

FragmentでタブUIを簡単にできるライブラリ作った

Android

今更な感じですが、TabActivityとかActivityGroupがdeprecatedになっちゃってタブ画面を作るにはどうすればいいか試行錯誤した結果です。
そもそもTabActivityとかActivityGroupは非常に使いにくいものでしたが…

作ったのはGitHubにあげてあります。(名前は良いのがまったく思いつかず…)


使い方はREADMEにそれっぽく書いてますがソース見ればだいたい分かると思います。
これを使うとFragmentのほうは特に意識せず使うことができると思います。

これを作るために色々試したことを書いておきます。

FragmentTabHost

今はFragmentを使うのが普通ですが、TabHostをFragmentに対応させるためのものがSupportLibraryにあります。
FragmentTabHost | Android Developers
レイアウトファイルも含めたサンプルコードはTabActivity | Android DevelopersのClass Overviewに書いてあります。

これを使うとdeprecatedになっているものを使わずにタブ画面を作成することができます。

使い方自体は非常に簡単でしたが、次のことが微妙なところでした。

  • TabWidgetの表示位置が変更できない
    • 上固定
    • ちゃんと調べれば解決策あるかも…
  • タブ内でのFragment切り替えをしたい場合ややこしい
    • バックキーの動作とか、タブ切り替え時の状態保持とか


タブ内でのFragment切り替えが必要ない場合とTabWidgetが上で良い場合はFragmentTabHostを使うのが良いかと思います。

個人的には仕様変更で何かでてきたら結構大変なことになりそうだと思いますが…

getChildFragmentManager

FragmentにgetChildFragmentManagerというメソッドがあります。(最近追加されたものだと思います

今まではFragmentの管理はActivity単位が基本だったと思いますが、getChildFragmentManagerを使うことでFragment内での子Fragment管理をすることができます。

TabHost + Fragment + getChildFragmentManager

こやつらをうまく使うことで柔軟なタブ画面を作ることが可能になります。

  • タブ内でのFragment切り替え
  • TabWidgetはレイアウトファイルを変更するだけでOK
  • タブ内でのFragmentを切り替えた時の状態維持


考え方としてタブに割り当てるFragmentをルートとしてその中で更にFragmentを管理しています。
軽く説明を書いてみましたが、逆に分かりにくいかもです。コード見たほうが早いです。


レイアウトファイルはFragmentTabHostではなく普通のTabHostを使います。
@android:id/tabcontentは使用せず@+id/realtabcontentをFragmentで使用します。

        <!-- TabHostの仕様上必要 -->
        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0"/>
        <!-- 実際のコンテンツ領域(Fragment) -->
        <FrameLayout
            android:id="@+id/realtabcontent"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>


ActivityはFragmentとタブの制御をしています。
仕様上必要な@android:id/tabcontentは使わないのでダミーのTabContentFactoryを使います。

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

        DummyTabFactory(Context context) {
            mContext = context;
        }
        @Override
        public View createTabContent(String tag) {
            View v = new View(mContext);
            return v;
        }
    }

実際に使う@+id/realtabcontentのほうにはonTabChangedでFragment切り替えを行なっています。
変数mMapTaInfoでタブに割り当てるFragmentがtagIdで管理されてます。

    @Override
    public void onTabChanged(String tabId) {
        TabInfo newTab = mMapTaInfo.get(tabId);

        if (mLastTabInfo != newTab) {
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
            if (mLastTabInfo != null) {
                fragmentTransaction.detach(mLastTabInfo.fragment);
            }
            if (newTab.fragment == null) {
                newTab.fragment = createTabRootFragment(newTab);
                fragmentTransaction.add(R.id.realtabcontent, newTab.fragment);
            } else {
                fragmentTransaction.attach(newTab.fragment);
            }

            mLastTabInfo = newTab;

            fragmentTransaction.commit();
        }
    }


各タブごとにルートとなるFragmentでは引数で実際に表示するFragmentクラスをもらってFragmentを設定しているだけです。
ここで使うFragmentManagerはgetChildFragmentManagerで取得してこのFragment内でのFragment管理をできるようにしています。

    private void initFragment() {

        Bundle args = getArguments();
        String rootClass = args.getString(TabNyanActivity.ROOT_FRAGMENT_ARGS);

        FragmentManager fragmentManager = getChildFragmentManager();
        if (fragmentManager.findFragmentById(R.id.fragment) != null) {
            return;
        }

        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        Fragment fragment = Fragment.instantiate(getActivity(), rootClass, args);
        fragmentTransaction.replace(R.id.fragment, fragment);

        fragmentTransaction.commit();
    }

まとめ

ややこしい…あぁ…ややこしい
ただ、これを使うとTabActivityよりも管理が楽になると思います。

注意点としてはまだ使い込んだものではないので予期せぬものがあるかもしれません。もし見つけたら気軽にPullRequest、Issueへどうぞ。

あと、こんなことしなくてこっちのほうが楽だぞ的なものがあれば教えてください。