2011年2月5日土曜日

The Android 3.0 Fragments API

多少意訳も入ってます。
厳密な意味をしりたい方は、ぜひ原文を参照ください。

-----

The Android 3.0 Fragments API

■ Introducing the Fragment

 Android 3.0 では Fragment と呼ばれる新しいクラスをつかってアプリケーションのインターフェースを調整することができます。Fragment は自身の UI と lifecycle を含んだコンポーネントで、必要な UI や特定のデバイスや画面に応じて、アプリケーション UI の異なるパーツで再利用が可能です。

 Fragment を mini-Activity のように思うかもしれませんが、それ自身独立で動くことはできず適切な Activity にホストされなければなりません。事実、Fragment API の導入では Activity の開発者が直面してきた多くの欠点を明らかにする機会となりました。

* ActivityGroup を通して Activities を組み込む方法はいいアイディアでしたが、他の Activity と限定的にインタラクトする代わりに独立したコンポーネントとして設計された Activity を扱うには常に困難がありました。

* Activity のインスタンスにわたってデータを保持する方法は Acitivity.onRetainNonConfigurationInstance() を使って実現できていますが、この方法はかっこ悪いうえ非自明です。Fragment は flag をセットすることで Fragment インスタンス全体を保持することが可能で、それによりこのメカニズムを置き換えます。

* DialogFragment と呼ばれる特別な Fragment を使うと、Activity lifecycle の一部として管理される Dialog を簡単に表示できます。これは Activity の "managed dialog" APIs を置き換えます。

* ListFragment と呼ばれる特別な Fragment を使うと、データのリストを簡単に表示できます。これはすでに存在している ListActivity に似たものですが、いくつかの他のデータと一緒にリストを表示する方法を提供します。

* 現在の Activity に割り当てられている全ての Fragment の情報は framework によって Activity の saved instance state に保存され、再スタート時にリストアされます。これは、自身で書く必要があった保存とリストアのコードを大幅に減らします。

* framework は Fragment オブジェクトの back-stack を管理するための build-in サポートを持っています。これにより、既存の activity back stack を統合したような振る舞いをする intra-activity Back button を簡単に提供することができます。この状態でも保存とリストアは自動で行われます。

■ Getting started

Fragment を使ったマルチUI の例です。最初に横向き用レイアウトをデザインします。左側にはアイテムのリストを表示し、右側のその詳細を表示するようにします。

このようなレイアウトを作成します。


Activityのコードはさして面白くありません。次のレイアウトXMLファイルを setContentView() で指定しているだけです。


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">

<fragment class="com.example.android.apis.app.TitlesFragment"
android:id="@+id/titles" android:layout_weight="1"
android:layout_width="0px"
android:layout_height="match_parent" />

<FrameLayout android:id="@+id/details" android:layout_weight="1"
android:layout_width="0px"
android:layout_height="match_parent" />
</LinearLayout>


<fragment> tag を使うことによって、自動的に Fragment サブクラスを生成し、view 階層にインストールすることができます。ここで実装されている Fragment は ListFragment を継承したものです。ListFragment はユーザーが選択できるようにアイテムのリストを表示・管理します。以下の実装では、UI レイアウトに応じて詳細情報の表示場所を、リストと同じ場所に置くか、分割 Activity に置くか変えています。画面の向きを変えるなど configuration change が起こっても Fragment の状態が保存されていることに注目してください。


public static class TitlesFragment extends ListFragment {
boolean mDualPane;
int mCurCheckPosition = 0;

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

// 静的に定義されたリストアイテムのリストを生成
setListAdapter(new ArrayAdapter(getActivity(),
R.layout.simple_list_item_checkable_1,
Shakespeare.TITLES));

// 詳細情報用の fragment があるかどうかチェック
View detailsFrame = getActivity().findViewById(R.id.details);
mDualPane = detailsFrame != null
&& detailsFrame.getVisibility() == View.VISIBLE;

if (savedState != null) {
// 最後にチェックしていた場所をリストア
mCurCheckPosition = savedState.getInt("curChoice", 0);
}

if (mDualPane) {
// dual-pane モードで、リストの選択された行をハイライトする
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
// 選択された位置の詳細情報を表示する
showDetails(mCurCheckPosition);
}
}

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

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

/**
* Helper function to show the details of a selected item, either by
* displaying a fragment in-place in the current UI, or starting a
* whole new activity in which it is displayed.
*/
void showDetails(int index) {
mCurCheckPosition = index;

if (mDualPane) {
// リストの選択された位置をハイライト
getListView().setItemChecked(index, true);

// 表示されている Fragment をチェックし、必要なら入れ替える
DetailsFragment details = (DetailsFragment)
getFragmentManager().findFragmentById(R.id.details);
if (details == null || details.getShownIndex() != index) {
// 新しい Fragment を生成
details = DetailsFragment.newInstance(index);

// transaction を実行し、フレーム内に存在する
// Fragment を置き換える
FragmentTransaction ft
= getFragmentManager().beginTransaction();
ft.replace(R.id.details, details);
ft.setTransition(
FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}

} else {
// 1画面の場合は、詳細情報用の別 Activity を起動する
Intent intent = new Intent();
intent.setClass(getActivity(), DetailsActivity.class);
intent.putExtra("index", index);
startActivity(intent);
}
}
}


選択されたアイテムのテキスト表示するための TextView を持った DetailsFragment クラスを用意します。

public static class DetailsFragment extends Fragment {
/**
* Create a new instance of DetailsFragment, initialized to
* show the text at 'index'.
*/
public static DetailsFragment newInstance(int index) {
DetailsFragment f = new DetailsFragment();

// Supply index input as an argument.
Bundle args = new Bundle();
args.putInt("index", index);
f.setArguments(args);

return f;
}

public int getShownIndex() {
return getArguments().getInt("index", 0);
}

@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
if (container == null) {
// container が無いレイアウトなので、view は生成しない
return null;
}

ScrollView scroller = new ScrollView(getActivity());
TextView text = new TextView(getActivity());
int padding = (int)TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
4, getActivity().getResources().getDisplayMetrics());
text.setPadding(padding, padding, padding, padding);
scroller.addView(text);
text.setText(Shakespeare.DIALOGUE[getShownIndex()]);
return scroller;
}
}


縦向き用のレイアウトを追加します。2列にする幅がないので、リストだけを表示します。




<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment class="com.example.android.apis.app.TitlesFragment"
android:id="@+id/titles"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>


詳細表示用の container がないので、TilesFragment はリストだけを表示します。リストのアイテムをタップすると、別の Activity で詳細を表示します。



すでに実装されている DetailsFragment があるので、新しい Activity は上記と同じ DetailsFragment を再利用します。


public static class DetailsActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if (getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE) {
// If the screen is now in landscape mode, we can show the
// dialog in-line so we don't need this activity.
finish();
return;
}

if (savedInstanceState == null) {
// During initial setup, plug in the details fragment.
DetailsFragment details = new DetailsFragment();
details.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction().add(
android.R.id.content, details).commit();
}
}
}


これらをすべてまとめると、実行時の画面の向きだけでなく、画面のコンフィグレーションの変化にも応じて、根本から UI flow を変えるアプリケーションができます。

ここで示したのは、UI を調節するために Fragment を使う1つの方法です。アプリケーションのデザインによって別のアプローチがいいこともあります。例えば、アプリケーション全体を状態の変化に応じて Fragment の構造を変える1つの Activity に入れたい場合もあるでしょう。その場合 fragment back stack が便利です。

より詳しい情報は Android 3.0 SDK ドキュメント内の Fragment と FragmentManager APIs を見てください。また ApiDemos の Resources tab にはたくさんの Fragment デモが入っています。これらは alternative UI flow, dialog, list, populationg menu, activity instance をまたいだ保存, back stack などをカバーしています。

■ Fragmentation for all!

Android 3.0 向けにデザインされたタブレット用アプリケーションの開発者にとって、Fragment API は大画面からもたらされる多くのデザインシチュエーションを実現するのに役立ちます。Fragment を合理的に使うことで、将来でてくるデバイス(電話やTVやその他 Android ならなんでも)の要求に対してアプリケーションのUIを簡単に調節できます。

しかし多くの開発者にとって今必要なのは、現在あるデバイスに提供できるアプリケーションを、タブレット上でのユーザーインタフェースがよりよくなるようにデザインすることでしょう。Fragment は Android 3.0 でみ使用可能ですが、それを打ち消すぐらい shorter-term utility がすばらしいです。

この問題を解決するために、我々はここで記述した fragment APIs (と同じような新しい LoaderManager)と同じものを古い Android のバージョン (1.6までもどれるように試しています) でも static library として使用できるように計画しています。事実、ここのコードと Android 3.0 SDK のコードを比べると、いくつかの違いがあります。ここのコードは以前のバーション用の static library fragment クラスを使っています。スクリーンショットが Android 2.3 なのがわかると思います。我々のゴールはこれらの API をほぼ同一にすることです。そうなれば、これらを今から使うことができ、将来 Android 3.0 を最小バージョンにスイッチするときに、アプリケーション内を数カ所変更するだけで移植することができます。

このライブラリが使用可能になる時期を明言することはできませんが、比較的すぐなはずです。その間に、 Android 3.0 上で Fragment を使った開発を始めることができます。Fragment がどのように動き、どのような効果があるかを理解することはこのライブラリでも活かすことが出来ます。


 

0 件のコメント:

コメントを投稿