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

タブレット時代のレイアウト FragmentLayout

Androidは解像度の異なる端末がいろいろ出てきて悩みどころ。
タブレット型に関してはFragments API使えばiPadっぽいレイアウトが組めるらしいけど現状3.0のみだし。


iPadiPhoneのようにマーケットが分かれていれば片方のみ対応とかできるけどAndroidはそうもいかないし...

とか、いろいろ困ってる人たちに朗報が!


GoogleAndroid 3.0の「Fragments API」で非互換性問題を解消へ
http://japan.internet.com/allnet/20110309/8.html

Android Developers Blog「Fragments For ALL」
http://android-developers.blogspot.com/2011/03/fragments-for-all.html


なんとFragments APIが1.6までの後方互換が効くようになった。

ってことでやってみよう。


まず、「Android SDK and AVD Manager」の「Available Packages」に

Android Compatibility package

が増えているのでインストール。


そうするとSDKのディレクトリにandroid-compatibilityフォルダができていて、
その下にv4/android-support-v4.jarがあるのでライブラリに追加する。
これがFragmentsの後方互換ライブラリ。


ではアプリ作成。
作るのはリストのアイテムを選択すると詳細画面が表示される、というシンプルなもの。

レイアウトファイルの作成

まず、縦向きと横向き用のレイアウトを用意

縦:layout/fragment_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <fragment
    	class="com.sample.MainActivity$TitlesFragment"
    	android:id="@+id/titles"
    	android:layout_width="fill_parent"
    	android:layout_height="fill_parent" />
</FrameLayout>

横:layout-land/fragment_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <fragment
    	class="com.sample.MainActivity$TitlesFragment"
    	android:id="@+id/titles"
    	android:layout_width="fill_parent"
    	android:layout_height="fill_parent"
    	android:layout_weight="1"/>
    <FrameLayout
    	android:id="@+id/details"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent"
	android:layout_weight="1"/>
</LinearLayout>

詳細画面のレイアウト:layout/details.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TextView
  	android:id="@+id/detail_text"
  	android:layout_width="wrap_content"
  	android:layout_height="wrap_content"
  	android:textSize="20sp"/>
</FrameLayout>

※リスト画面のレイアウトはプログラム内で作ってしまうのでいらない

アクティビティの作成

これがなかなか面倒くさい。ApiDemosにあるソースを拝借して必要な部分のみ記述した。

package com.sample;

import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends FragmentActivity {

	private static final String[] ITEMS = { "A", "B", "C" };

	private static final String[] DETAILS = {
			"ラテン文字(アルファベット)の1番目の文字。小文字は a 。ギリシャ文字のΑ(アルファ)に由来し、キリル文字のАに相当する。",
			"ラテン文字(アルファベット)の2番目の文字。ギリシャ文字のΒ(ベータ)に由来する。小文字は b 。キリル文字のБ、Вと同系である。",
			"C は、ラテン文字(アルファベット)の3番目の文字。小文字は c 。" };

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.fragment_layout);
	}

	/**
	 * ポートレイト(縦向き)のアクティビティ
	 */
	public static class DetailsActivity extends FragmentActivity {
		@Override
		protected void onCreate(Bundle savedInstanceState) {
			super.onCreate(savedInstanceState);

			if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
				// ランドスケープ(横向き)モードの場合、このアクティビティはいらない
				finish();
				return;
			}

			if (savedInstanceState == null) {
				// フラグメント(右ペイン)を作成
				DetailsFragment details = new DetailsFragment();
				details.setArguments(getIntent().getExtras());
				getSupportFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
			}
		}
	}

	/**
	 * 左のペイン
	 */
	public static class TitlesFragment extends ListFragment {

		boolean mDualPane;
		int mCurCheckPosition = 0;
		int mShownCheckPosition = -1;

		@Override
		public void onActivityCreated(Bundle savedInstanceState) {
			super.onActivityCreated(savedInstanceState);

			// リストの作成
			setListAdapter(new ArrayAdapter<String>(getActivity(),
					android.R.layout.simple_list_item_checked, ITEMS));

			// デュアルモードかどうかを確認
			View detailsFrame = getActivity().findViewById(R.id.details);
			mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;

			if (savedInstanceState != null) {
				// 前回の選択位置を取得
				mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
				mShownCheckPosition = savedInstanceState.getInt("shownChoice", -1);
			}

			if (mDualPane) {
				getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
				showDetails(mCurCheckPosition);
			}
		}

		@Override
		public void onSaveInstanceState(Bundle outState) {
			super.onSaveInstanceState(outState);
			outState.putInt("curChoice", mCurCheckPosition);
			outState.putInt("shownChoice", mShownCheckPosition);
		}

		@Override
		public void onListItemClick(ListView l, View v, int position, long id) {
			showDetails(position);
		}

		void showDetails(int index) {
			mCurCheckPosition = index;

			if (mDualPane) {
				// デュアルモードの場合は選択されているアイテムをハイライト
				getListView().setItemChecked(index, true);

				if (mShownCheckPosition != mCurCheckPosition) {
					// 別のアイテムが選択されたら対応する画面を作成
					DetailsFragment df = DetailsFragment.newInstance(index);

					// トランザクションを開始して既存のフラグメントと入れ替える
					FragmentTransaction ft = getFragmentManager()
							.beginTransaction();
					ft.replace(R.id.details, df);
					ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
					ft.commit();
					mShownCheckPosition = index;
				}
			} else {
				// デュアルモードではない場合、詳細画面を起動
				Intent intent = new Intent();
				intent.setClass(getActivity(), DetailsActivity.class);
				intent.putExtra("index", index);
				startActivity(intent);
			}
		}
	}

	/**
	 * 右のペイン
	 */
	public static class DetailsFragment extends Fragment {
		
		public static DetailsFragment newInstance(int index) {
			DetailsFragment f = new DetailsFragment();

			// リストに対応するインデックスを仕込む
			Bundle args = new Bundle();
			args.putInt("index", index);
			f.setArguments(args);

			return f;
		}

		@Override
		public View onCreateView(LayoutInflater inflater, ViewGroup container,
				Bundle savedInstanceState) {
			if (container == null) {
				// ポートレイトでリスト画面の場合は詳細画面を作らない
				return null;
			}

			View view = inflater.inflate(R.layout.details, null);
			TextView textView = (TextView) view.findViewById(R.id.detail_text);
			textView.setText(DETAILS[getArguments().getInt("index", 0)]);
			return view;
		}
	}
}

実行

まず縦向きで起動。


リストのアイテムをクリックして詳細画面表示。


ここでCtrl + F11でエミュレータの向きを変更。


おお、iPadっぽいぞ。

このレベルのソースだと、Honeycombと後方互換ライブラリの違いは

  • FragmentActivityを継承する
  • getFragmentManager()ではなくgetSupportFragmentManager()


というだけ。
ListFragmentはそのままでgetFragmentManager()が使えるんだけど、それはよく分からない。


仕事だと1.6からnewタブレットまで対応しろよボケ((((;゜Д゜)))
なんていうケースがあるので、アプリによってはこれで対応出来る部分も増えるかしら。