2012年10月26日金曜日

ActionBarのタブを使用する

ActionBarのタブはFragmentの切り替えなどに使用される非常に重要な機能です。
 

NavigationMode

ActionBarでタブを表示する為には、NavigationModeをNAVIGATION_MODE_TABSにする必要があります。

NavigationModeをNAVIGATION_MODE_TABSにするにはActionBar#setNavigationModeメソッドを使用します。

1
2
3
4
// AcionBarを取得
ActionBar bar = getActionBar();
// NvigationModeをNAVIGATION_MODE_TABSに設定
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

NavigationModeにはNAVIGATION_MODE_TABS以外のものもあります。

以下にNavigationModeの一覧を記述します。

NAVIGATION_MODE_LIST リスト構造のNavigationを提供します
NAVIGATION_MODE_STANDARD 一般的なNavigationです
NAVIGATION_MODE_TABS タブのNavigationを提供します

タブを追加する

NavigationModeの設定をすれば、後はタブを追加していくだけです。

タブの追加にはActionBar#addTabメソッドを使用します。

1
bar.addTab(bar.newTab().setText("Tab1"));

上記のコードではActionBar#newTabメソッドで新しいタブを作成し、ActionBar.Tab#setTextメソッドでタブに表示するテキストを指定しています。

ActionBar.TabListener

ActionBar.TabListenerを使用すると、タブが選択された場合、タブの選択が外れた場合、タブが2度目以降に選択
された場合のそれぞれの状態に処理を実行させることができます。

以下にActionBar.TabListenerを実装した場合に必ず実装しなければならないメソッドの一覧を記述します。

onTabSelected このメソッド内に記述された処理は、タブが選択された場合に実行されます
onTabUnselected このメソッド内に記述された処理は、タブの選択が外れた場合に実行されます
onTabReselected このメソッド内に記述された処理は、タブが2度目以降に選択された場合に実行されます

ActionBar.TabListenerはActionBar#setTabListenerを使用して、ActionBarに設定します。

1
bar.setTabListener("TabListenerが実装されたクラス");

Fragmentを使ったスマートフォン/タブレット向けUI両対応の方法

Android 3.0以降でタブレット向けに追加されたFragmentが、Android 4.0でスマートフォンでも利用出来るように拡張されました。
今まではTabletの画面構成の一部をフラグメント化(断片化)し、再利用に利用することが多かったと思われますが、
スマートフォン/タブレット共通のプラットフォームとなったことでUIの両対応に利用できるようになりました。

本エントリでは、リスト表示するアプリを例にスマートフォン、タブレットのUI両対応を行います。
フラグメントをつかうことでスマホ/タブレットで共通パーツをつかって効率的な開発をすることができます。


■図1:スマートフォンとタブレットのList表示と詳細表示

上記の図は、List(緑色パーツ)と詳細表示(オレンジパーツ)の2つのUI部品を用意し、
タブレットでは2カラムで同時に表示しています。
スマートフォンでは画面が狭いため、緑→オレンジの順で1つずつ見せています。
通常なら別々にActivityを用意するところですが、フラグメントを利用することで、
緑(List)とオレンジ(詳細表示)は共通パーツとして作成することができます。

それではつづきで紹介していきます。

画面サイズのチェック

UIを切り替えるためには、アプリケーションの動作する端末がスマートフォンかタブレットか知る必要があります。
スマートフォンとタブレットの画面サイズを判断する方法として、本エントリのサンプルでは「res/values/bools.xml」を利用します。

Androidアプリケーションは、res以下のディレクトリは画面解像度毎に細かく分類することができます。
例えば、drawable-mdpiなら160dpの端末、drawable-hdpiなら240dpの端末、と実行環境で使用するリソースをOSが自動で切り替えます。
これを利用し、values/bools.xmlにスマートフォンである設定を、values-sw600dp(横方向の解像度が600dp以上)にタブレットの設定を設けることで、サイズチェックに利用します。
※この値は、Android-4.0のソースコードがSplitActionBarの設定ON/OFFに使用している閾値です


※追記 2012/01/28
values-w480dpは、端末のLandscape(横向き)/Portrait(縦向き)に依存します。
values-w480dpにタブレットの設定を設けた場合、Galaxy Nexusでタブレット判定がされることをご指摘いただきました。
(SplitActionBarはLandscapeの場合OFFになる仕様のようです。)

Androidでは端末の縦横の状態に依存しないvaluesディレクトリとして、values-sw600dp(解像度は任意)があります。
values-sw600dpにbools.xmlを置く事で縦横の状態に依存しない設定とすることができます。
参考リンク:Android Developers 「Supporting Multiple Screens

■res/values/bools.xml

1
2
3
<resources>
    <bool name="is_tablet">false</bool>
</resources>

■res/values-sw480dp/bools.xml

1
2
3
<resources>
    <bool name="is_tablet">true</bool>
</resources>

上記設定bools.xmlの値を読み込む為のメソッドを用意し、簡易にタブレット/スマートフォンを判別できるように
メソッドを追加しておきます。
本メソッドを呼び出すと、OSが実行環境の解像度に合わせて(横方向の解像度480dp以上でタブレット)、設定値をよみだしてくれます。

■src/MultiFragmentSampleActivity.java

1
2
3
private boolean isTabletMode(){
    return getResources().getBoolean(R.bool.is_tablet);
}

UIの切り替え

サンプルとして作成する、図1の構成は以下の通りです。

    スマートフォンのUI:「リストを選択すると次画面にその詳細が表示」
    タブレットのUI:「左ペインのリストを選択すると右ペインに詳細を表示」

このような画面構成の場合、共通している項目が「リスト」「詳細表示」の項目です。
エントリ冒頭でも触れましたが、共通している項目をFragmentを利用し、部品化しておくことで開発を効率化することができます。
共通項目を部品化することで、タブレット/スマートフォンで部品の表示方法を変更するだけで両対応を行うことができます。

本エントリでは、リスト表示にSampleListFragmentクラスを、詳細表示にSampleDetailFragmentクラスを作成しました。
これらは、アプリケーション起動時に呼び出されるActivityで以下の様に呼び出しています。
例としてSampleListFragmentの呼び出し時のコードを引用します。

■src/MultiFragmentSampleActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    // tablet/phoneで読み込むlayoutを変更する
    if(isTabletMode()){
        setContentView(R.layout.main_tab);
    }else{
        setContentView(R.layout.main_sp);
    }
 
}
 
@Override
protected void onResume() {
    super.onResume();
 
    //listの作成
    SampleListFragment listFragment = new SampleListFragment();
 
    //fragmentを配置する
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    if(isTabletMode()){
        ft.add(R.id.list_container,listFragment);
    }else{
        ft.add(R.id.phone_container,listFragment);
    }
    ft.commit();
 
}

Fragmentを追加する場所の構成が異なるため、
onCreate時に読み込む、Activityのレイアウトをタブレットの場合とスマートフォンの場合で変更しています。

22行目〜28行目において、FragmentをActivityに追加しています。
タブレットの判定を行うメソッドを利用し、タブレットの場合とスマートフォンの場合で追加する場所を変動させています。

ActivityのイベントをFragmentに通知する

Fragmentを利用する場合に、Fragment内で発生したボタンクリックなどのイベントをActivityに通知したいことが多々あります。
しかし、FragmentのAPIにはActivityにメッセージを通知するAPIは存在しません。
その為、Fragmentにリスナーを実装し、Activityでリスナー登録することでイベントを通知することができます。

TechBoosterでは「独自リスナーを作成する」でより汎用的な内容を紹介していますので、詳細な実装方法はそちらの記事をご覧になってください。

本エントリのサンプルでは、SampleListFragmentのアイテムクリックイベントをActivityに通知するListenerを作成し、
通知されたActivityでSampleDetailFragmentの読み込みに利用しています。

■src/SampleListFragment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class SampleListFragment extends ListFragment {
    private onFragmentListClickedListener listener;
 
    ......省略
 
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // TODO Auto-generated method stub
        super.onListItemClick(l, v, position, id);
        listener.onFragmentListClick(rows[position]);
    }
 
    /**
     * ListのClick情報を通知するListener
     *
     */
    public interface onFragmentListClickedListener {
        public void onFragmentListClick(String select);
    }
 
    /**
     * Interfaceを登録する
     *
     */
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        listener = (onFragmentListClickedListener)activity;
    }
 
}

18行目でListenerを作成、26行目でListenerを登録しています。
Listenerを登録する場合に、Activityに該当するListenerがimplementされていない場合には、エラーとなってしまうため注意が必要です

10行目でListenerを呼び出しています。
SampleListFragmentでクリックされたアイテムの情報をSampleDetailFragmentに通知するため、
クリックされたアイテム情報を引数にしています。

次に、Activityに用意したListenerの実体のサンプルです。
Listenerは、Fragment内のListViewを選択した時に呼び出され、詳細表示のFragmentを読み込みます。
ただし、例に習いタブレット/スマートフォンでの両対応を行う為に、Fragmentの読み込み部分は
bools.xmlの値をチェックし動的切り換え可能な作りとしています。

■src/MultiFragmentSampleActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MultiFragmentSampleActivity extends Activity implements onFragmentListClickedListener{
    ......省略
 
    /**
     * SampleListFragmentでListClickされるとよびだされる
     * ここでdetailFragmentを呼び出す
     */
    @Override
    public void onFragmentListClick(String select) {
        //detailFragmentの作成
        SampleDetailFragment detail = new SampleDetailFragment(select);
 
        //fragmentを配置する
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        if(isTabletMode()){
            ft.add(R.id.detail_container,detail);
        }else{
            ft.add(R.id.phone_container,detail);
        }
        ft.addToBackStack(null);
        ft.commit();
    }

1行目でActivityにSampleListFragmentで作成したinterfaceをimplementしています。
繰り返しになりますが、忘れるとエラーになるため注意しましょう。

9行目以降がSampleListFragmentでアイテムが押下された時に呼び出されるメソッドです。
このメソッドの中で、SampleListFragmentで押下した情報を元にSampleDetailFragmentを読み込んでいます。

最後にサンプルの実行スクリーンショットを掲載します。
タブレット環境での実行結果は以下図の通りです。
左ペインで押下した内容が右ペインに表示される構成になっています。

■図2:タブレットでのサンプル実行結果

スマートフォン環境での実行結果は以下図の通りです。
図3(リスト画面)で選択した内容が図4(詳細画面)に反映されています。

■図3:スマートフォンでのサンプル実行結果(List)


■図4:スマートフォンでのサンプル実行結果(詳細画面)