Quantcast
Channel: アカベコマイリ » Android
Viewing all articles
Browse latest Browse all 10

Android のタブを使いこなす

$
0
0

仕事の Android 開発でタブを使ったレイアウトが必要になったので、サンプルを作りながら使用方法を学んでみる。

もくじ

タブの約束事

タブを使った画面を作る場合、レイアウト指定には約束事がある。例えば画面の上側にタブのつくレイアウトの場合、以下のように指定する。

<?xml version="1.0" encoding="utf-8"?>
<TabHost
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:background="#0094FF"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

        <!-- タブ -->
        <TabWidget
            android:id="@android:id/tabs"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />

        <!-- セパレータ -->
        <FrameLayout
            android:background="#222222"
            android:layout_width="fill_parent"
            android:layout_height="1dp" />

        <!-- タブの内容 -->
        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_weight="1"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" />
    </LinearLayout>
</TabHost>

規定の id を指定している 4、16、28 行目をハイライトしておいた。

タブ画面の大枠は TabHost という要素になる。id には android:id/tabhost を指定する必要がある。この中に各種要素を配置してゆく。

まず、タブとそれに対応する内容のレイアウトを定義する。これはタブの位置を考慮して決める。

通常は LinearLayout を指定しておくのがよいだろう。タブが内容へ重なるように表示したい場合は RelativeLayout などを使う。

タブは TabWidget という要素で定義して、id は @android:id/tabsタブの内容は FrameLayout 要素で定義し、id に @android:id/tabcontent を指定する。

このルールだけ守れば、後のレイアウトは自由である。

例えば前述の定義のように、タブと内容の間へセパレータを入れたり、タブ画面全体のメニュー的なものをつけてもよい。

次にタブの追加だが、これはタブを定義したレイアウト XML に対応する TabActivity 派生クラスでおこなう。

TabActivity.getHost メソッドを呼び出すと、タブを管理している TabHost インスタンスへの参照を得られる。これの addTab メソッドに、タブの内容を設定した TabSpec インスタンスを指定すれば、それがタブとして追加される。

規定のアイコンとテキストで構成されるタブを利用する場合は、以下のようになる。

@Override
public void onCreate( Bundle savedInstanceState ) {
    super.onCreate( savedInstanceState );
    this.setContentView( R.layout.tab_top );

    TabHost host = this.getTabHost();
    TabSpec spec = host.newTabSpec( "タブ 1" );

    // アイコンとテキストを指定
    {
        Resources r = this.getResources();
        spec.setIndicator( "タブ 1", r.getDrawable( R.id.tab_icon ) );
    }

    // 内容となる画面 ( Activity ) の指定
    {
        Intent intent = new Intent();
        intent.setClass( this, TestActivity.class );
        spec.setContent( intent );
    }

    host.addTab( spec );
}

カスタマイズ

TabSpec.setIndicator メソッドには View を引数に取るオーバーロードがある。これを利用すれば、独自の表示内容を指定できる。

例えば以下のようにレイアウトを定義しておいて、

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="@drawable/selector_tab_v"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <!-- セパレータ -->
    <FrameLayout
        android:id="@+id/tab_item_separator"
        android:background="#222222"
        android:layout_width="fill_parent"
        android:layout_height="1dp" />

    <!-- タブ -->
    <LinearLayout
        android:orientation="vertical"
        android:gravity="center"
        android:padding="8dp"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

        <!-- アイコン -->
        <ImageView
            android:id="@+id/tab_item_icon"
            android:layout_marginBottom="4dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <!-- テキスト -->
        <TextView
            android:id="@+id/tab_item_text"
            android:textColor="#FFFFFF"
            android:textStyle="bold"
            android:textSize="12dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>
</LinearLayout>

TabActivity 派生クラスに以下のようなメソッドを定義しておけば、

/**
 * タブを生成します。
 *
 * @param owner   タブ画面。
 * @param icon    アイコンのリソース識別子。
 * @param text    テキスト。
 * @param layout  タブのレイアウトを示すリソース識別子。
 * @param isFirst はじめのタブなら true。それ以外は false。
 *
 * @return 生成されたタブ情報。
 */
private TabSpec createTabSpec( TabHost host, int icon, String text, int layout, boolean isFirst ) {
    View v = LayoutInflater.from( this ).inflate( layout, null );

    // 始点なら区切り線を消す
    if( isFirst ) {
        v.findViewById( R.id.tab_item_separator ).setBackgroundColor( 0 );
    }

    ( ( ImageView )v.findViewById( R.id.tab_item_icon ) ).setImageResource( icon );
    ( ( TextView  )v.findViewById( R.id.tab_item_text ) ).setText( text );

    TabSpec spec = host.newTabSpec( text );
    spec.setIndicator( v );

    Intent intent = new Intent();
    intent.setClass( this, TestActivity.class );
    spec.setContent( intent );

    return spec;
}

以下のように呼び出せる。

@Override
public void onCreate( Bundle savedInstanceState ) {
    super.onCreate( savedInstanceState );
    this.setContentView( R.layout.tab_top );

    TabHost host   = this.getTabHost();
    int     layout = R.layout.tab_item_h;

    host.addTab( this.createTabSpec( host, R.drawable.tab_icon_1, "タブ 1", layout, true  ) );
    host.addTab( this.createTabSpec( host, R.drawable.tab_icon_2, "タブ 2", layout, false ) );
    host.addTab( this.createTabSpec( host, R.drawable.tab_icon_3, "タブ 3", layout, false ) );
    host.addTab( this.createTabSpec( host, R.drawable.tab_icon_4, "タブ 4", layout, false ) );
}

状態によって描画方法を変える

Android のリソースには selector という仕組みがあり、状態に依存して描画方法を変更できるようになっている。

例えば drawable として icon_1.png と icon_1a.png という二つの画像があるとする。これらをタブが非選択・選択の時に分けて表示する場合、まずは drawable に以下のような selector を定義する。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@drawable/icon_1"
        android:state_selected="false" />

    <item
        android:drawable="@drawable/icon_1a"
        android:state_selected="true" />
</selector>

selector の子として item を定義し、その中に android:state_~ 属性を設定すると、それに応じた android:drawable が反映される。

この仕組みを利用すると、レイアウトは共通でコンテンツ部分だけ可変、といった設計を実現しやすい。コントロールをカスタム描画する時は、真っ先に検討すべき方法だと思う。

ただし現時点の Eclipse/ADT では、selector や shape の XML 編集でインテリセンスが効かないうえ、間違った記述をしてもエラーにならない場合があるので注意する。

例えば selector の item を、itam のようにスペルミスしても無視される。

一方、android:drawable を android:drawabla にした場合はきちんとエラーになるため、この動きを想定して前者のミスをすると、思わぬハマリに繋がる。

ちなみに item をスペルミスした状態でビルドされたアプリを実行すると、item の定義が無視されるようだ。しかし将来の ADT で厳密なチェックがサポートされる可能性もあるため、この動作に期待しないこと。

Android のサポートしている属性と値については、以下が参考になる。

タブを画面の左右に置く

Android の TabWidget は水平方向のレイアウトで実装されているため、画面の上下には置けるけれど、左右の場合は都合が悪い。

しかし端末の向き変更に対応した場合、縦の時は下にタブを置き、横では右に配置したくなるかもしれない。そのような要求に応えるためにも、TabWidget で垂直方向のレイアウトをサポートする方法を調べておこうと思った。

もしかして、既にそれを実現している人がいるかもしれない、とググってみたら、まさにドンぴしゃな記事を見つけた。

ここに書かれている方法を試したところ、見事に垂直レイアウトなタブを実現できた。

ただ、なぜか私の環境では ViewGroup.LayoutParams.MATCH_PARENT がエラーになるので、代わりに FILL_PARENT を使ってみた。

前述の記事には Android の TabWidget 実装についても説明しているので、原理が非常に分かりやすい。要約すると、このコントロールは水平前提で実装されているが、派生クラスの工夫でそれを変更できる、といった感じか。

サンプル プログラム

ここまで学んだ内容を使って、簡単なサンプルを作ってみる。仕様は以下のようになる。

  • タブをカスタム描画する
  • タブを上下左右に配置する
  • タブの内容は Activity にする

まず、タブの上下左右を選ぶ画面を作成する。

トップ画面

トップ画面

選ばれたレイアウトに応じて、個別の画面を表示する。

Top/Bottom は一般的なレイアウト。

上下 ( 水平 ) タブ

上下 ( 水平 ) タブ

Left/Right は左右に垂直なタブを表示するレイアウト。

左右 ( 垂直 ) タブ

左右 ( 垂直 ) タブ

工夫した点として、タブ画面は TabHostActivity というひとつのクラスで担当させ、レイアウトの分岐はリソース選択としている。

実際、タブ画面が複数あるアプリを作成する場合でも、それらが大きく異なることは考えにくいので、このような感じの実装になるのではなかろうか。

最後に、サンプル プログラムのプロジェクトを公開しておく。

サンプル プロジェクト
TestTabActivity.zip 43.3KB

Android 2.1 update 1 ( API Level 7 ) でビルドし、エミュレータと初代 Xperia ( SO-01B ) にて動作確認をおこなった。

また、オマケとして今回のサンプルで使用したアイコンの SVG ファイルをつけておいた。

もしサンプルのアイコンが小さい・大きいと思ったら、これらを Inkscape などで開き、好みのサイズで PNG ファイルとしてエクスポートし、それを利用する。


Viewing all articles
Browse latest Browse all 10

Trending Articles