2012年11月20日火曜日

Android ActionBarとFragmentを使用してTab画面を表示する(Android 2.x)

Android 3.0(API level 11)から導入されたActionBarとFragmentを使用して、Android 2.x系でもタブ画面を表示する方法です。

タブ画面関係の過去記事
Android TabActivityとTabHostを使用してTab画面を表示する
Android ActionBarとFragmentを使用してTab画面を表示する(Android 4.0以上)


対象:
Build SDK:Android 4.0 (API 14) / Min SDK:Android 2.1 (API 7)

Android 2.xでFragmentを使用できるようにする

eclipseメニューの「ウインドウ」→「Android SDK マネージャー」を起動します。
「Extras」の「Android Support Library」をインストールします。

Android 2.xでActionBarを使用できるようにする

Android Android2.xでActionBarを使用する を参考にして、ActionBarSherlockライブラリを参照に追加します。

タブ画面を表示するためのコード

まずメインとなるアクティビティを作成します。
Android 3.0以降はandroid.app.Activityを継承して作成しますが、Android3.0以前はcom.actionbarsherlock.app.SherlockFragmentActivityを継承して作成します。
自動で生成されるonCreateOptionMenuメソッドはSherlockFragmentActivityではfinalメソッドでOverrideできないのでコメントアウトします。
またonCreateメソッドではをsetCpmtemtView setContentViewメソッドより前にテーマを設定する必要があります。
テーマを設定しないと予期せぬエラーで落ちます。
import com.actionbarsherlock.app.SherlockFragmentActivity;

import android.os.Bundle;

public class MainActivity extends SherlockFragmentActivity {   
  
     @Override  
     public void onCreate(Bundle savedInstanceState) {   
          super.onCreate(savedInstanceState);
          //setCpmtemtViewメソッドより前にテーマを設定する。   
          setTheme(R.style.Theme_Sherlock_Light); 
          setContentView(R.layout.activity_main);   
     }   
  
//     @Override  
//     public boolean onCreateOptionsMenu(Menu menu) {   
//          getMenuInflater().inflate(R.menu.activity_main, menu);   
//          return true;   
//     }   
  
}  
MainActivityのレイアウトです。
res/layout/activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/container"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

</FrameLayout>

次に各タブのコンテンツとなるFragmentを作成します。
Android 3.0以降は android.app.Fragmentを継承して作成しますが、Android3.0以前はandroid.support.v4.app.Fragmentを継承して作成します。
レイアウトはそれぞれの違いがわかるように適当に変更しておいてください。
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Tab1Fragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // 第3引数のbooleanは"container"にreturnするViewを追加するかどうか
        //trueにすると最終的なlayoutに再度、同じView groupが表示されてしまうのでfalseでOKらしい
        return inflater.inflate(R.layout.fragment_tab1, container, false);
    }
}
今回は面倒なので2タブにします。
同じように2タブめに表示するFragmentを作成します。
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Tab2Fragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // 第3引数のbooleanは"container"にreturnするViewを追加するかどうか
        //trueにすると最終的なlayoutに再度、同じView groupが表示されてしまうのでfalseでOKらしい
        return inflater.inflate(R.layout.fragment_tab2, container, false);
    }
}

次にタブを変更したときのリスナーを作成します。
com.actionbarsherlock.app.ActionBar.TabListenerを実装します。
Android3.0以前でFragment関係のクラスを使用する場合は、android.support.v4.appパッケージのものを使用します。
Android3.0以前でActionBar関係のクラスを使用する場合は、 com.actionbarsherlock.appパッケージのものを使用します。
このクラスの各メソッドの引数「FragmentTransaction」はバグっており使用できません。
SherlockFragmentActivityからFragmentManagerオブジェクトを取得するメソッドはgetFragmentManagerメソッドではなくgetSupportFragmentManagerメソッドになります。
2012/12/11追記
縦横を切り替えたときFragment#OnCreateが2回走る不具合があり、
コンストラクタでFragmentを探すように変更し
onTabSelectedでもdetachされていないときだけattachするよう変更しました。
詳しくは・・・
Android ActionBarとFragmentを利用したTab画面でFragment#OnCreateが2回走る
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;

import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.ActionBar.Tab;
import com.actionbarsherlock.app.SherlockFragmentActivity;

public class TabListener<T extends Fragment> implements ActionBar.TabListener {

 private Fragment mFragment;
    private final SherlockFragmentActivity mActivity;
    private final String mTag;
    private final Class mClass;
    
    /**
     * コンストラクタ
     * @param activity
     * @param tag
     * @param clz
     */
    public TabListener(SherlockFragmentActivity activity, String tag, Class clz) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
        //FragmentManagerからFragmentを探す。  2012/12/11 追記 
    mFragment = mActivity.getSupportFragmentManager().findFragmentByTag(mTag);   
    }

    /**
     * @brief  タブが選択されたときの処理
     */
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        //ftはnullではないが使用するとNullPointExceptionで落ちる
        if (mFragment == null) {
            mFragment = Fragment.instantiate(mActivity, mClass.getName());
            FragmentManager fm = mActivity.getSupportFragmentManager();
            fm.beginTransaction().add(R.id.container, mFragment, mTag).commit();
        } else {
            //detachされていないときだけattachするよう変更   2012/12/11 変更
            //FragmentManager fm = mActivity.getSupportFragmentManager();
            //fm.beginTransaction().attach(mFragment).commit();
            if (mFragment.isDetached()) {   
                FragmentManager fm = mActivity.getSupportFragmentManager();   
                fm.beginTransaction().attach(mFragment).commit();   
             } 
        }
    }
    /**
     * @brief  タブの選択が解除されたときの処理
     */
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        //ftはnullではないが使用するとNullPointExceptionで落ちる
        if (mFragment != null) {
            FragmentManager fm = mActivity.getSupportFragmentManager();
            fm.beginTransaction().detach(mFragment).commit();
       }    
    }
    /**
     * @brief タブが2度目以降に選択されたときの処理
     */
    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }
}

それではメインアクティビティに戻ります。
メインアクティビティではonCreateメソッドでActionBarを取得し、NavigationModeをタブにします。
ActionBarを取得するメソッドはgetActionBarメソッドではなくgetSupportActionBarメソッドになります。
後はActionBarにタブのコンテンツとなるFragmentを追加していくだけです。
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockFragmentActivity;

import android.os.Bundle;

public class MainActivity extends SherlockFragmentActivity {   
   
    @Override  
    public void onCreate(Bundle savedInstanceState) {   
        super.onCreate(savedInstanceState);   
        //setCpmtemtViewメソッドより前にテーマを設定する。   
        setTheme(R.style.Theme_Sherlock_Light);   
        setContentView(R.layout.activity_main);   
        
        // Set up the action bar.
        ActionBar actionBar = getSupportActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        actionBar.addTab(actionBar.newTab() 
                   .setText("ページ1") 
                   .setTabListener(new TabListener<Tab1Fragment>( 
                              this, "tag1", Tab1Fragment.class))); 
        actionBar.addTab(actionBar.newTab() 
                   .setText("ページ2") 
                   .setTabListener(new TabListener<Tab2Fragment>( 
                              this, "tag2", Tab2Fragment.class))); 

    }   
  
//    SherlockActivityではOverrideできない。   
//    @Override   
//    public boolean onCreateOptionsMenu(Menu menu) {   
//        getMenuInflater().inflate(R.menu.activity_main, menu);   
//        return true;   
//    }   
}  

Android 2.1 (API 7) のエミュレータで実行した結果です。

2 件のコメント:

匿名 さんのコメント...

setCpmtemtViewって
setContentViewの事ですよね?

yan さんのコメント...

ご指摘ありがとうございます。
setContentViewの間違いです(^^);