2012年3月30日金曜日

Android テーマでデフォルトの sp 単位を置き換える

“本当は、きちんと sp 単位に対応すべきです”

しかし、これまでデフォルトの文字サイズ(android:textSize や setTextSize() してない widget の文字サイズ)でアプリを作成していたが、 sp 単位のことを考慮していなかったためにレイアウトが崩れるなどの現象がおこり、どうしてもデフォルトの文字単位を一括で dp にしたいこともあるでしょう。

デフォルトの文字サイズについては Android UI Cookbook for 4. 0 の 1.2.2 タイポグラフィに書いたので、詳しくはそっちを参照してください。

例えば、TextView のテーマを見てみると、

437 <style name="Widget.TextView"> 438 <item name="android:textAppearance">?android:attr/textAppearanceSmall</item> ... 448 </style>
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/res/res/values/styles.xml#437

のように、textAppearanceSmall という attr を参照しています。

42 <style name="Theme"> ... 85 <item name="textAppearanceSmall">@android:style/TextAppearance.Small</item> ... 371 </style>
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/res/res/values/themes.xml#83

デフォルトのテーマでは textAppearanceSmall には @android:style/TextAppearance.Small が指定されており、 これは次のように 14sp になっています。

792 <style name="TextAppearance.Small"> 793 <item name="android:textSize">14sp</item> 794 <item name="android:textColor">?textColorSecondary</item> 795 </style>

よって、オリジナルテーマで textAppearanceSmall を書き換えれば TextView のデフォルトの文字サイズを sp でなくすことができます。もちろん、textAppearanceSmall を参照している他の widget も影響を受けます。

<style name="MyTheme" parent="@android:style/Theme"> <item name="textAppearanceSmall">@style/MyTextAppearanceSmall</item> </style> <style name="MyTextAppearanceSmall" parent="@android:style/TextAppearance.Small"> <item name="android:textSize">14dp</item> </style>

デフォルトテーマで TextAppearance として定義されているのは small だけではありません。かなりいろいろありますが、 http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/res/res/values/themes.xml#83

  • textAppearanceLarge
  • textAppearanceMedium
  • textAppearanceSmall
  • textAppearanceLargeInverse
  • textAppearanceMediumInverse
  • textAppearanceSmallInverse


あたりをカバーすればだいたいの widget は影響範囲に入ります。
もちろん ActionBar のタブの文字サイズなど、影響範囲に入らないものは個別にスタイルを指定することになります。


2012年3月20日火曜日

Android ActionBar の withText で文字がでる条件はどこで決まる?

ActionBar に表示する Action Item はこれまでのメニュー項目と同じように XML で定義できます。 <menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/menu_edit" android:icon="@android:drawable/ic_menu_edit" android:showAsAction="ifRoom|withText" android:title="Edit"/> <item android:id="@+id/menu_save" android:showAsAction="ifRoom" android:title="Save"/> <item android:id="@+id/menu_delete" android:icon="@android:drawable/ic_menu_delete" android:showAsAction="ifRoom" android:title="Delete"/> </menu>
android:showAsAction に ifRoom を指定すると領域があるときは Action Item として表示されます。

android:icon が指定されていないときは android:title に指定されている文字列が表示されますが、 android:icon が指定されていたらデフォルトでは android:title の文字列は表示されません。

android:showAsAction に withText を指定すると、

 ハンドセット縦画面 : アイコンだけ
 ハンドセット横画面 : アイコン + 文字列
 タブレット縦画面 : アイコン + 文字列
 タブレット横画面 : アイコン + 文字列

のような表示になります。

ハンドセット縦画面


ハンドセット横画面


この条件がどこで決まっているのか調べました。

---

まず、Menu オブジェクトの View を作っているベースクラスが BaseMenuPresentar です。このクラスは MenuPresenter インタフェースを実装しています。 このクラスのコンストラクタで指定されたリソースID (mMenuLayoutRes) が
53 public BaseMenuPresenter(Context context, int menuLayoutRes, int itemLayoutRes) { 54 mSystemContext = context; 55 mSystemInflater = LayoutInflater.from(context); 56 mMenuLayoutRes = menuLayoutRes; 57 mItemLayoutRes = itemLayoutRes; 58 }
getMenuView でインフレートされて Menu の View として返されます。
67 @Override 68 public MenuView getMenuView(ViewGroup root) { 69 if (mMenuView == null) { 70 mMenuView = (MenuView) mSystemInflater.inflate(mMenuLayoutRes, root, false); 71 mMenuView.initialize(mMenu); 72 updateMenuView(true); 73 } 74 75 return mMenuView; 76 }
この BaseMenuPresenter のインスタンスを生成しているのが ActionMenuPresenter です。
71 public ActionMenuPresenter(Context context) { 72 super(context, com.android.internal.R.layout.action_menu_layout, 73 com.android.internal.R.layout.action_menu_item_layout); 74 }
145 @Override 146 public MenuView getMenuView(ViewGroup root) { 147 MenuView result = super.getMenuView(root); 148 ((ActionMenuView) result).setPresenter(this); 149 return result; 150 }
つまり、com.android.internal.R.layout.action_menu_item_layout がメニューのレイアウトになります。

このレイアウトのルートは com.android.internal.view.menu.ActionItemMenuView になっています。

この ActionItemMenuView の updateTextButtonVisibility() メソッドで文字列の表示/非表示をセットしています。
132 private void updateTextButtonVisibility() { 133 boolean visible = !TextUtils.isEmpty(mTextButton.getText()); 134 visible &= mImageButton.getDrawable() == null || 135 (mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat)); 136 137 mTextButton.setVisibility(visible ? VISIBLE : GONE); 138 }
ここを見ると、文字列が表示される条件は

 ・文字列が空ではない
    +
 ・アイコンが指定されていない

もしくは、

 ・文字列が空ではない
    +
 ・android:showAsAction に withText がセットされている
    +
 ・mAllowTextWithIcon もしくは mExpandedFormat が true

であることがわかります。


ハンドセットの縦/横で文字列が出ない/出るの違いを決めているのは

 ・mAllowTextWithIcon もしくは mExpandedFormat が true

の部分です。
37 public class ActionMenuItemView extends LinearLayout 38 implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener, 39 ActionMenuView.ActionMenuChildView { ... 48 private boolean mAllowTextWithIcon; ... 50 private boolean mExpandedFormat;
まず、mExpandedFormat についてみると setExpandFormat() メソッドで外部から設定するしかありません。
123 public void setExpandedFormat(boolean expandedFormat) { 124 if (mExpandedFormat != expandedFormat) { 125 mExpandedFormat = expandedFormat; 126 if (mItemData != null) { 127 mItemData.actionFormatChanged(); 128 } 129 } 130 }
しかし、このメソッドを呼んでいるところがなかったので、現状では常に boolean の初期値の false になってるようです。

次に、mAllowTextWithIcon です。

この変数には、 ActionMenuItemView のコンストラクタで、com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon の値がセットされています。

60 public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) { 61 super(context, attrs, defStyle); 62 final Resources res = context.getResources(); 63 mAllowTextWithIcon = res.getBoolean( 64 com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon); 65 mShowTextAllCaps = res.getBoolean(com.android.internal.R.bool.config_actionMenuItemAllCaps); 66 }

com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon の値がどうなっているかをみると、

res/values/config.xml
708 <!-- Whether action menu items should obey the "withText" showAsAction 709 flag. This may be set to false for situations where space is 710 extremely limited. --> 711 <bool name="config_allowActionMenuItemTextWithIcon">false</bool>
res/values-w480dp/config.xml
20 <bool name="config_allowActionMenuItemTextWithIcon">true</bool>
つまり、横幅が 480dp より大きい画面の向きでは mAllowTextWithIcon が true になり、Action Item にアイコン + 文字列が表示されるということでしたー。



2012年3月19日月曜日

Kinect はじめました

Kinect for Windows SDK に入ってるもの
  • Kinect センサーデバイスを使うためのドライバ(Windows7, Windows Embedded Standard7, Window8 Developer Preview用)
  • APIs およびデバイスインタフェース
  • ソースコードサンプル

システム要件

ハードウェア:
  • 32bit(x86) もしくは 64bit(x64) プロセッサ
  • Dual-core 2.66GHz 以上のプロセッサ
  • Dedicated USB 2.0 バス
  • 2GB RAM
  • Kinect for Windows センサー(専用のUSB/電源ケーブル含む)
ソフトウェア:
---

KINECT for Windows の DEVELOP ページを開く



download sdk を開く



Kinect for Windows SDK をダウンロードしてインストール

1. Kinect センサーが接続されていないことを確認

2. 以前のバージョンの Kinect for Window SDK をインストールしてる場合はアンインストールする

3. Kinect センサー用の他のドライバーを削除

4. x86, x64 bit バージョン両方の Microsoft Speech runtim components および Kinect Language Pack を全てアンインストール

5. Visual Studio を閉じる

6. ダウンロードしたファイルをダブルクリックしてインストーラを起動(32bit, 64bit 両方で同じインストーラが動作する)

7. SDK のインストールが成功したら、Kinect センサーに電源ケーブルが接続されているのを確認し、PC の USB ポートに接続する。ドライバーは自動で読み込まれる

8. Kinect センサーが正しく動作する

9. 音声を利用した Kinect アプリケーションを開発するなら、Microsoft Speech SDK をインストールする

(インストールにかなり時間かかる。)

---

Kinect SDK Sample Browser を起動して、 Kinect Explorer サンプルを Run Sample ボタンを押して実行。



Kinect が認識されて Connected になったら OK。



Kinect for Windows は Visual Studio 2010 を使って C++, C#, Visual Basic で作成されるアプリケーションをサポートしている。

とりあえず Visual C# 2010 Express をインストール


Microsoft .NET Framework4 もインストール



Kinect 接続して認識したので、Kinect for Winodws QuickStart Videos を見るか。

*追記1 : Kinect は認識するが ColorStream や DepthStream の画像が取れないとき(Kinect Explorer で -1 FPS って表示されて真っ暗なときとか)は
 ・Windows Update を確認
 ・電源が足りてるか確認(タコ足配線につないでるとだめ?)
 ・USB ポートを変えてみる or しっかりささってるか確認
あたりをすると幸せになれるかも。


2012年3月14日水曜日

Android ICS のキーストアアクセスの一元化

元記事 Unifying Key Store Access in ICS

Android 4.0 (ICS) では、人々がパーソナルな Android デバイスを仕事に使えるようにするための多くの機能拡張が追加されています。ここでは、キーストア機能について取り上げます。

Android 1.6 (Donut) で VPN を利用するためにシステムキーストアが追加されました。のちに WiFi 認証のサポートにも拡張されましたが、アプリケーションがアクセスすることはできない状態でした。

これまで、セキュアなSSLウェブサーバーの認証や、クライアント証明書を介してサーバーにユーザー認証する必要がある場合、アプリにとって自身のキーストア管理は共通の課題でした。このための処理では、メールやブラウザなどのいくつかのアプリに渡って複数の証明書が共有されるようなエンタープライズ向けの環境では、管理上の問題があらわれる可能性があります。

ICS での新機能 : KeyChain

このギャップを埋めるために、ICS では KeyChain という名前の新しい API が追加されました。KeyChain はシステムキーストアへのアプリケーションアクセスを調整し、そこに保存されている証明へのアプリケーションのアクセスをユーザーが付与できるようにします。加えて、この API は X.509 証明書と PKCS#12 キーストアからの証明のインストールの初期化をアプリケーションからできるようにします。

KeyChain API はとてもシンプルです。キーストアもしくは証明書をインストールするには、install intent を取得して証明の raw bytes を適用し、システムインストールダイアログを起動する intent を使います。キーストアの場合、次の例のように PKCS#12 形式のデータを提供し、ユーザーがその PKCS#12 のパスワードを知っている必要があります。 byte[] keystore = . . (read from a PKCS#12 keystore) Intent installIntent = KeyChain.createInstallIntent(); installIntent.putExtra(KeyChain.EXTRA_PKCS12, keystore); startActivityForResult(installIntent, INSTALL_KEYSTORE_CODE);


install intent はユーザーにキーストアのパスワードを入力するよう求めるシステムダイアログを起動します。

これは、同じ CA によって発行された証明書をもつ non-public なサーバーへの認証が、全てのアプリケーションによって信頼されるようになる、組織的な CA 証明書のインストールにも使うことができます。

ICS では、Android はもはや system credential storage を守るための個別のパスワードを必要としません。代わりにスクリーンロックパスワードを使います。また、Android Device Administration API は中央管理でのポリシーの執行につかうことができます。これが意味することとして、例えば、セキュリティで保護された証明がデバイスに残っている限りスクリーンロックパスワードを削除することはできません。

Accessing System Key Store Credentials

一度システムキーストアが設定されると、KeyChain API は SSL サーバーでの認証に使うクライアント証明書をリクエストするなどの機能を提供します。アプリケーションが最初のアクセスをリクエストすると、ユーザーに利用できる証明書の一覧が表示され、アプリケーションにアクセスを付与する証明書を選択します。ユーザーが証明書へのアクセスを許可する選択をした場合、証明書用の文字列エイリアス名がアプリケーションに返ってきます。アプリケーションはその後の証明書へのアクセスでこのエイリアスを使うことでユーザーを煩わせずにすみます。

次のコードは、ユーザーが証明書エイリアスを選択してアプリケーションにアクセスを付与するためのプロンプトを表示する方法です。KeyChain はこの選択(同じアプリケーションが保存できる証明書エイリアスの選択)を覚えておき、その後の同じ証明書へのアクセスを持ちます。例えば、ICS 向けのメールアプリケーションではこの機能をサーバー設定画面で実装しています。 KeyChain.choosePrivateKeyAlias(this, new KeyChainAliasCallback() { public void alias(String alias) { // Credential alias selected. Remember the alias selection for future use. if (alias != null) saveAlias(alias); } }, new String[] {"RSA", "DSA"}, // List of acceptable key types. null for any null, // issuer, null for any "internal.example.com", // host name of server requesting the cert, null if unavailable 443, // port of server requesting the cert, -1 if unavailable null); // alias to preselect, null if unavailable


一度アプリケーションに証明書へのアクセスが付与されると、getPrivateKey() メソッドを通して秘密鍵にアクセスできるようになります。任意の PrivateKey オブジェクトに対してエンコーディングに関する仮定をするべきではない、ということは注目に値することです。例えば、いくつかの実装上の PrivateKey オブジェクトは単にハードウェアキーストア内に保存されたキーの不透明な表現であるかもしれません。

次は、キーストアから秘密鍵を取得するためのサンプルコードです。 PrivateKey privateKey = KeyChain.getPrivateKey(context, savedAlias); if (privateKey != null) { ... Signature signature = Signature.getInstance("SHA1withRSA"); signature.initSign(privateKey); ... } 秘密鍵の一般的な使い道は SSL クライアント認証です。これは HttpsURLConnection と、KeyChain API から取得した PrivateKey を返す独自の X509KeyManager を使って実装することができます。ICS 向けのオープンソースのメールアプリケーションは X509ExtendedKeyManager を使っています。より詳しくはこのソースコード(SSLUtils.java 内)を見てください。

この API はシステムキーストア証明書への一元的なアクセス方法を提供します。あなたのアプリケーションがクライアント認証を使う(エンタープライズ向けのメールクライアントやウェブブラウザの開発者)なら、次のアップデート用に KeyChain API を調べてみてください。

Android FragmentBreadCrumbs を使ってパン屑リストを作る

API Level 11 からパン屑リストを表示するための ViewGroup である FragmentBreadCrumbs が用意されています。

使い方は
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <android.app.FragmentBreadCrumbs android:id="@+id/bread_crumbs" android:layout_width="match_parent" android:layout_height="wrap_content" /> ... </LinearLayout>

のようにレイアウトに入れてもいいし、

private FragmentBreadCrumbs mFragmentBreadCrumbs; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mFragmentBreadCrumbs = new FragmentBreadCrumbs(this); ActionBar actionBar = getActionBar(); actionBar.setCustomView(mFragmentBreadCrumbs); actionBar.setDisplayShowCustomEnabled(true); actionBar.setDisplayShowTitleEnabled(false); ... }

のように ActionBar の CustomView として表示してもいい。

FragmentBreadCrumbs のリファレンスには ActionBar.setCustomView(View) を使って Action Bar に配置する目的のものです、とあるので Action Bar に置くのがいいのかな。
まぁでも iosched とかは普通にレイアウトに置いてるので、Action Bar に置かなくてもいいと思う。

setActivity(Activity a) で activity のインスタンスをセットして使う。

private FragmentBreadCrumbs mFragmentBreadCrumbs; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mFragmentBreadCrumbs = new FragmentBreadCrumbs(this); ActionBar actionBar = getActionBar(); actionBar.setCustomView(mFragmentBreadCrumbs); actionBar.setDisplayShowCustomEnabled(true); actionBar.setDisplayShowTitleEnabled(false); mFragmentBreadCrumbs.setActivity(this); mFragmentBreadCrumbs.setMaxVisible(3); mFragmentBreadCrumbs.setParentTitle("preParent", "preSP", null); mFragmentBreadCrumbs.setTitle("preTitle", "preST"); mFragmentBreadCrumbs.setOnBreadCrumbClickListener(new FragmentBreadCrumbs.OnBreadCrumbClickListener() { @Override public boolean onBreadCrumbClick(BackStackEntry backStack, int flags) { if(backStack != null) { Log.d("onBreadCrumbClick", backStack.getName() + ", " + flags); } return false; } });

setMaxVisible(int max) メソッドで最大表示数を指定できる。

setMaxVisible() で 3 を指定した状態


最大表示数を指定していない状態


setParentTitle(CharSequence title, CharSequence shortTitle, View.OnClickListener listener) でパン屑リストの初期位置での親エントリのタイトルをセットできる。オプショナル。
shortTitle がどこで使われるのかはよくわからなかった。
第3引数でパン屑リストのこの親タイトルがタップされたときにリスナーをセットできる。

setTitle(CharSequence title, CharSequence shortTitle) で現在の位置でのパン屑リストのタイトルをセットできる。

setOnBreadCrumbClickListener(FragmentBreadCrumbs.OnBreadCrumbClickListener listener) でパン屑リストがタップされたときのリスナーをセットできる。パン屑リストの内の親エントリのどれかがタップされると、FragmentBreadCrumbs.OnBreadCrumbClickListeneronBreadCrumbClick(BackStackEntry backStack, int flags) が呼ばれ、第1引数で関連づけられている BackStackEntry が渡される。BackStackEntry は back stack で保持されるエントリで、FragmentTransaction.addToBackStack() によって生成され、FragmentManager が管理する。FragmentManager.getBackStackEntryAt() で取得することができる。



FragmentTransaction で、back stack に入れる BackStackEntry に FragmentBreadCrumbs のタイトルをセットできる。

  • setBreadCrumbShortTitle(int res)
    このトランザクションが back stack 上にあるときにパン屑リストに表示されるショートタイトルのリソースIDをセットする
  • setBreadCrumbShortTitle(CharSequence text)
    このトランザクションが back stack 上にあるときにパン屑リストに表示されるショートタイトルをセットする デバイスの言語設定が変わった場合に、文字をあとから変えることができないので推奨されていない
  • setBreadCrumbTitle(int res)
    このトランザクションが back stack 上にあるときにパン屑リストに表示されるタイトルのリソースIDをセットする
  • setBreadCrumbTitle(CharSequence text)
    このトランザクションが back stack 上にあるときにパン屑リストに表示されるタイトルをセットする デバイスの言語設定が変わった場合に、文字をあとから変えることができないので推奨されていない


@Override public void onClick(View v) { FragmentManager manager = getFragmentManager(); FragmentTransaction ft = manager.beginTransaction(); MainFragment2 fragment = MainFragment2.getInstance(); int id = v.getId(); switch (id) { case R.id.add_btn: { ft.setBreadCrumbShortTitle("sTitle"); ft.setBreadCrumbTitle("Title"); ft.addToBackStack("MainFragment2"); ft.add(R.id.container, fragment, "MainFragment"); break; } case R.id.remove_btn: { if(manager.getBackStackEntryCount() > 0) { manager.popBackStack(); ft.remove(fragment); } break; } ... } ft.commit(); }
addToBackStack() とひもづくので戻るボタンを押して戻すと、パン屑リストもちゃんと一段もどる。

ちなみに FragmentBreadCrumbs のデフォルトのスタイルは Widget_FragmentBreadCrumbs で定義されていて、styles.xml ではこんな感じ 677 <!-- Default style for {@link android.app.FragmentBreadCrumbs} view. --> 678 <style name="Widget.FragmentBreadCrumbs"> 679 <item name="android:padding">4dp</item> 680 <item name="android:animateLayoutChanges">true</item> 681 </style> ViewGroup なので LayoutTransition でパン屑リストが変わるときにもっと複雑なアニメーションをつけることも可能。



Android FragmentTransaction のまとめ

add / remove
  • add(int containerViewId, Fragment fragment, String tag)
    ・Activity の状態に fragment を追加
    ・fragment がすでに Activity に追加されている場合なにも起こらない(detach したインスタンスを add しても表示されない)
    ・containerViewId が 0 ではない場合、fragment の onCreateView() が null でなければそれを containerViewId の ViewGroup に追加

     onAttach()
    → onCreate()
    → onCreateView()
    → onActivityCreated()
    → onStart()
    → onResume()

    * remove() 後に detach() された後の場合は onAttach() → onCreate()

    * back stack にあるインスタンスを add する場合は
     onCreateView()
    → onActivityCreated()
    → onStart()
    → onResume()

  • remove(Fragment fragment)
    ・Activity の状態から fragment を削除
    ・fragment がすでに Activity から削除されている場合なにも起こらない
    ・fragment の View が container に追加されている場合、その View は container から削除される

     onPause()
    → onStop()
    → onDestroyView()
    → onDestroy()
    → onDetach()

    * detach() された後の場合は onDestroy() → onDetach()

    * addToBackStack() を呼んでいる場合は
     onPause()
    → onStop()
    → onDestroyView()


replace
  • replace(int containerViewId, Fragment fragment, String tag)
    ・container にすでに追加されている Fragment を置き換える
    ・処理としては、同じ containerViewId に追加されている全ての Fragment に対して remove(Fragment) を呼び、それから add(containerId, fragment, tag) を呼ぶのと同じ
    ・置き換えられる Fragment と置き換える fragment が同じインスタンスの場合、onResume() まで呼ばれるが View が表示されないことがある

    置き換えられる Fragments :
     onPause()
    → onStop()
    → onDestroyView()
    → onDestroy()
    → onDetach()

    * addToBackStack() を呼んでいる場合は
     onPause()
    → onStop()
    → onDestroyView()


    置き換える fragment :
     onAttach()
    → onCreate()
    → onCreateView()
    → onActivityCreated()
    → onStart()
    → onResume()

    * back stack にあるインスタンスに置き換える場合は
     onCreateView()
    → onActivityCreated()
    → onStart()
    → onResume()


show / hide
  • show(Fragment fragment)
    ・hidden 状態になっている fragment (の View)を表示する
    ・fragment のライフサイクル状態は変わらない
    ・container に View が追加されている fragment にのみ意味がある
    ・fragment がすでに Activity から削除されている場合なにも起こらない
    ・detach が呼ばれた後でも適用される

  • hide(Fragment fragment)
    ・存在している fragment を隠す(hidden 状態にする)
    ・fragment のライフサイクル状態は変わらない
    ・container に View が追加されている fragment にのみ意味がある
    ・fragment がすでに Activity から削除されている場合でも適用されることがある、例えば同じインスタンスに対して add → remove → hide → add すると View は hidden 状態になっている、ここで show すると表示される
    ・detach が呼ばれた後でも適用される


attach / detach (API Level 13 から)
  • attach(Fragment fragment)
    ・detach() によって UI から取り外されている fragment を再度取り付ける
    ・UI に取り付けられる View 階層が再生成され、表示される
    ・fragment がすでに Activity から削除されている場合でも適用される、例えば add → remove → detach → attach の場合、onAttach() から onResume() まで呼ばれるが View は表示されない、View を表示するにはここから detach → attach をさらに呼ぶか remove → add をさらに呼ぶしかない
    ・onCreateView() は呼ばれるが onAttach(), onCreate() は呼ばれない

     onCreateView()
    → onActivityCreated()
    → onStart()
    → onResume()

  • detach(Fragment fragment)
    ・UI から fragment を取り外す
    ・back stack にある状態(fragment は UI から削除されているが fragment manager によって依然としてその状態が管理されている)と同じ
    ・この状態に入ると View の階層は破棄される
    ・fragment がすでに Activity から削除されている場合でも適用される、例えば add → remove → detach → add の場合ここでは onAttach() → onCreate() までしか呼ばれない、ここで attach() を呼ぶと onCreateView() → onActivityCreated() → onStart() → onResume() と呼ばれて View が表示される
    ・onDestroyView() は呼ばれるが onDestroy(), onDetach() は呼ばれない

     onPause()
    → onStop()
    → onDestroyView()



2012年3月13日火曜日

Android FragmentTransaction で commitAllowingStateLoss を使う

FragmentTransaction クラスの commitAllowingStateLoss() メソッドは commit() に似ていますが、Activity の状態が保存された後でないと commit が実行されません。
Activity が後でその状態から復元する必要がある場合、コミットが失われる可能性があるので注意が必要です。そのため、ユーザーによって UI 状態が不意に変更されても問題ない場合にのみ使用します。

FragmentTransaction#commitAllowingStateLoss() は FragmentManager の executePendingTransactions() メソッドと組み合わせて使うことが多いです。

@Override public void finishUpdate(ViewGroup container) { if (mCurTransaction != null) { mCurTransaction.commitAllowingStateLoss(); mCurTransaction = null; mFragmentManager.executePendingTransactions(); } }

FragmentTransaction.commit() で FragmentTransaction がコミットされたあと、これらの処理はメインスレッドで非同期に実行されるようスケジュールされます。
これら保留中の操作をただちに実行したい場合 FragmentManager の executePendingTransactions() メソッドを呼び出します。ただし呼び出せるのはメインスレッドからのみです。全ての関連するコールバックや他の振る舞いはこの呼び出し中に行われるため、呼び出し元では(ANRにならないように)注意が必要です。