1page.title=順応性のある UI フローの実装 2parent.title=複数画面のデザイン 3parent.link=index.html 4 5trainingnavtop=true 6previous.title=さまざまな画面密度のサポート 7previous.link=screendensities.html 8 9@jd:body 10 11 12<!-- This is the training bar --> 13<div id="tb-wrapper"> 14<div id="tb"> 15 16<h2>このレッスンでの学習内容</h2> 17 18<ol> 19 <li><a href="#TaskDetermineCurLayout">現在のレイアウトを判別する</a></li> 20 <li><a href="#TaskReactToLayout">現在のレイアウトに合わせて応答する</a></li> 21 <li><a href="#TaskReuseFrag">他のアクティビティのフラグメントを再利用する</a></li> 22 <li><a href="#TaskHandleConfigChanges">画面設定の変更を処理する</a></li> 23</ol> 24 25<h2>関連ドキュメント</h2> 26 27<ul> 28 <li><a href="{@docRoot}guide/practices/tablets-and-handsets.html">タブレットと携帯端末のサポート</a></li> 29</ul> 30 31<h2>試してみる</h2> 32 33<div class="download-box"> 34<a href="http://developer.android.com/shareables/training/NewsReader.zip" class="button">サンプル アプリのダウンロード</a> 35<p class="filename">NewsReader.zip</p> 36</div> 37 38 39</div> 40</div> 41 42<p>アプリが現在表示しているレイアウトによって、UI フローが異なる可能性があります。たとえば、アプリがデュアルペイン モードであれば、左ペインのアイテムをクリックすると、単に右ペインにコンテンツが表示されるだけですが、シングルペイン モードであれば、コンテンツは(別のアクティビティ内の)コンテンツ専用のペインに表示される必要があります。</p> 43 44 45<h2 id="TaskDetermineCurLayout">現在のレイアウトを判別する</h2> 46 47<p>レイアウトによって実装が多少異なるので、まず、ユーザーが現在どのようなレイアウトを表示しているかを判別する必要があります。たとえば、ユーザーが表示しているレイアウトが「シングルペイン」モードなのか、「デュアルペイン」モードなのかを確認する必要があります。それは、以下のようなコードで、ある特定のビューが存在し、かつ可視になっているかを照会することで可能です:</p> 48 49<pre class="prettyprint"> 50public class NewsReaderActivity extends FragmentActivity { 51 boolean mIsDualPane; 52 53 @Override 54 public void onCreate(Bundle savedInstanceState) { 55 super.onCreate(savedInstanceState); 56 setContentView(R.layout.main_layout); 57 58 View articleView = findViewById(R.id.article); 59 mIsDualPane = articleView != null && 60 articleView.getVisibility() == View.VISIBLE; 61 } 62} 63</pre> 64 65<p>このコードにおいて「article」ペインが使用可能かどうかを照会している点に注目してください。特定のレイアウトの照会をハードコーディングするよりもはるかに柔軟性があります。</p> 66 67<p>その他にも、さまざまなコンポーネントでも対応できる方法として、コンポーネントを操作する前に使用可能かどうかを確認する方法もあります。たとえば、News Reader サンプル アプリでは、メニューを開くボタンがありますが、このボタンは Android 3.0 よりも古いバージョンで動作しているときにしか表示されません(この機能は、API レベル 11 以上の <PH>{@link android.app.ActionBar}</PH> で提供されるため)。そこで、以下のようなコードを追加して、このボタンのイベント リスナーを追加します:</p> 68 69<pre class="prettyprint"> 70Button catButton = (Button) findViewById(R.id.categorybutton); 71OnClickListener listener = /* create your listener here */; 72if (catButton != null) { 73 catButton.setOnClickListener(listener); 74} 75</pre> 76 77 78<h2 id="TaskReactToLayout">現在のレイアウトに合わせて応答する</h2> 79 80<p>現在のレイアウトによって、一部のアクションの結果が異なる可能性があります。たとえば、News Reader サンプルでは、見出しリストで見出しをクリックしたとき、デュアルペイン モードの UI の場合は右ペインに記事が表示されますが、シングルペインの UI の場合は別のアクティビティが起動します:</p> 81 82<pre> 83@Override 84public void onHeadlineSelected(int index) { 85 mArtIndex = index; 86 if (mIsDualPane) { 87 /* display article on the right pane */ 88 mArticleFragment.displayArticle(mCurrentCat.getArticle(index)); 89 } else { 90 /* start a separate activity */ 91 Intent intent = new Intent(this, ArticleActivity.class); 92 intent.putExtra("catIndex", mCatIndex); 93 intent.putExtra("artIndex", index); 94 startActivity(intent); 95 } 96} 97</pre> 98 99<p>同様に、アプリがデュアルペイン モードの場合は、ナビ用タブでアクション バーを設定し、一方、シングルペイン モードの場合は、スピナー ウィジェットでナビを設定することになります。したがって、コードでは以下のようにどちらのケースが適切かを調べることも必要です:</p> 100 101<pre> 102final String CATEGORIES[] = { "トップ ニュース 政治", "政治", "経済", "Technology" }; 103 104public void onCreate(Bundle savedInstanceState) { 105 .... 106 if (mIsDualPane) { 107 /* use tabs for navigation */ 108 actionBar.setNavigationMode(android.app.ActionBar.NAVIGATION_MODE_TABS); 109 int i; 110 for (i = 0; i < CATEGORIES.length; i++) { 111 actionBar.addTab(actionBar.newTab().setText( 112 CATEGORIES[i]).setTabListener(handler)); 113 } 114 actionBar.setSelectedNavigationItem(selTab); 115 } 116 else { 117 /* use list navigation (spinner) */ 118 actionBar.setNavigationMode(android.app.ActionBar.NAVIGATION_MODE_LIST); 119 SpinnerAdapter adap = new ArrayAdapter<String>(this, 120 R.layout.headline_item, CATEGORIES); 121 actionBar.setListNavigationCallbacks(adap, handler); 122 } 123} 124</pre> 125 126 127<h2 id="TaskReuseFrag">他のアクティビティのフラグメントを再利用する</h2> 128 129<p>複数の画面に対応するように設計する場合、あるパターンが繰り返されますが、そうしたパターンは、ある画面設定ではペインとして、別の画面設定では別のアクティビティとして実装されるインターフェースの一部に存在します。たとえば、News Reader サンプルでは、ラージ画面の場合はニュース記事のテキストが右ペインに表示されますが、それよりも小さい画面の場合は別のアクティビティになります。</p> 130 131<p>このような場合、通常、複数のアクティビティで同じ <PH>{@link android.app.Fragment}</PH> サブクラスを再利用することでコードの重複を回避できます。たとえば、<code>ArticleFragment</code> は以下のようにデュアルペイン レイアウトで使用されます:</p> 132 133{@sample development/samples/training/multiscreen/newsreader/res/layout/twopanes.xml all} 134 135<p>また、より小さな画面向けのアクティビティ レイアウト内では(レイアウトを使用せずに)再利用されます(<code>ArticleActivity</code>):</p> 136 137<pre> 138ArticleFragment frag = new ArticleFragment(); 139getSupportFragmentManager().beginTransaction().add(android.R.id.content, frag).commit(); 140</pre> 141 142<p>当然、これは XML レイアウトでフラグメントを宣言するのと同じ効果がありますが、この場合は、XML レイアウトは必要ありません。このアクティビティのコンポーネントは記事フラグメントしかないからです。</p> 143 144<p>フラグメントを設計する際に注意すべき非常に重要なポイントの 1 つとして、特定のアクティビティに対して強い結合を作成しないことがあります。通常、これは、フラグメントが自分のホスト アクティビティとやり取りするのに必要なあらゆる手段を抽象化したインターフェースを定義し、さらに、そのインターフェースをホスト アクティビティに実装することで可能になります:</p> 145 146<p>たとえば、News Reader アプリの <code>HeadlinesFragment</code> は、まさにそのようになっています:</p> 147 148<pre> 149public class HeadlinesFragment extends ListFragment { 150 ... 151 OnHeadlineSelectedListener mHeadlineSelectedListener = null; 152 153 /* Must be implemented by host activity */ 154 public interface OnHeadlineSelectedListener { 155 public void onHeadlineSelected(int index); 156 } 157 ... 158 159 public void setOnHeadlineSelectedListener(OnHeadlineSelectedListener listener) { 160 mHeadlineSelectedListener = listener; 161 } 162} 163</pre> 164 165<p>これにより、ユーザーが見出しを選択すると、フラグメントは以下のように(特定のハードコーディングされたアクティビティに通知するのではなく)ホスト アクティビティが指定したリスナーに通知します:</p> 166 167<pre> 168public class HeadlinesFragment extends ListFragment { 169 ... 170 @Override 171 public void onItemClick(AdapterView<?> parent, 172 View view, int position, long id) { 173 if (null != mHeadlineSelectedListener) { 174 mHeadlineSelectedListener.onHeadlineSelected(position); 175 } 176 } 177 ... 178} 179</pre> 180 181<p>このテクニックについては、<a 182href="{@docRoot}guide/practices/tablets-and-handsets.html">タブレットと携帯端末のサポート</a>で詳しく説明されています。</p> 183 184 185<h2 id="TaskHandleConfigChanges">画面設定の変更を処理する</h2> 186 187<p>インターフェースの各パーツを実装するのに個別のアクティビティを使用している場合、インターフェースの一貫性を維持するために、(向きの変更などの)特定の設定変更に対応できるように注意する必要があります。</p> 188 189<p>たとえば、Android 3.0 以上が動作する一般的な 7 インチ タブレットでは、News Reader サンプルがニュース記事を表示する場合、縦表示では個別のアクティビティを使用しますが、横表示では 2 ペイン レイアウトを使用します。</p> 190 191<p>つまり、縦表示のときに記事閲覧用アクティビティが画面上にある場合、画面の向きが横方向に変わったことを検出したら、コンテンツを 2 ペイン レイアウトで表示するために、そのアクティビティを終了してメインのアクティビティに戻り、適切に応答しなければなりません:</p> 192 193<pre> 194public class ArticleActivity extends FragmentActivity { 195 int mCatIndex, mArtIndex; 196 197 @Override 198 protected void onCreate(Bundle savedInstanceState) { 199 super.onCreate(savedInstanceState); 200 mCatIndex = getIntent().getExtras().getInt("catIndex", 0); 201 mArtIndex = getIntent().getExtras().getInt("artIndex", 0); 202 203 // If should be in two-pane mode, finish to return to main activity 204 if (getResources().getBoolean(R.bool.has_two_panes)) { 205 finish(); 206 return; 207 } 208 ... 209} 210</pre> 211 212 213