2012/07/11

Android:ActionBar/ActionModeのListModeを使う


ActionBarのViewControlは、アプリ内のViewを切り替えるUIとして使われます。
ViewControlは3つのActionModeを持っています。
  • Normal
  • Tab
  • List

今回はListModeについてです。

●ActionMode:ListMode

ActionModeをListModeに設定すると、ViewControl部分にドロップダウンメニューが表示
されます。



・サンプルコード
ActionBar actBar = getActionBar();
// リストに表示するアダプタを登録
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_1, new String[] {
                "List Item 1", "List Item 2" });
// Navigationコールバックを登録する
actBar.setListNavigationCallbacks(adapter,
        new ActionBar.OnNavigationListener() {
            @Override
            public boolean onNavigationItemSelected(int itemPosition,
                    long itemId) {
                return false;
            }
        });
// 最後にNavigationModeをListModeに設定
actBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);

しかし、これだとアプリのタイトル文字列とケンカして見づらいです。
ListModeを使う場合、アプリアイコンで十分アイデンティティが確保されていれば、
アプリタイトルを非表示としたほうが良い場合がありそうです。


・アプリタイトルを表示しないサンプルコード

ActionBar actBar = getActionBar();
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_1, new String[] {
                "List Item 1", "List Item 2" });
actBar.setListNavigationCallbacks(adapter,
        new ActionBar.OnNavigationListener() {
            @Override
            public boolean onNavigationItemSelected(int itemPosition,
                    long itemId) {
                return false;
            }
        });
// アプリタイトルを非表示に設定
actBar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
actBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);

●ActionBarを上下に分割して表示

ListModeを採用すると、どうしてもActionBarの領域が狭くなりがちです。
ここにActionボタンも併用すると窮屈なUIになってしまいます。

その場合、ActionBarを画面上下に分割するsplitActionBarWhenNarrowの利用を考えます。



実装方法はいたってシンプル。
AndroidManifest.xmlのActivity要素にandroid:uiOptions="splitActionBarWhenNarrow"
を追加します。

・サンプルコード

<activity
    android:name=".MainActivity"
    android:label="@string/title_activity_main"
    android:uiOptions="splitActionBarWhenNarrow" >

ただし、画面横幅が一定サイズを超える場合はsplitActionBarWhenNarrowが有効とならな
い場合があります。
http://yuki312.blogspot.jp/2012/07/androidsplitactionbarwhennarrow.html

以上です。
2012/07/10

Android:HeaderViewListAdapterでOutOfBoundsException


過去に出会った不具合について覚書。

Header付きListViewを持つDialogを表示しながら画面回転したり、
言語切替した後に復帰すると稀にエラーが発生しました。

●手順

  1. Header付きListViewを持つDialogを表示中に画面回転
または
  1. Header付きListViewを持つDialogを表示
  2. Homeボタンでアプリをバックグラウンドへ
  3. 言語切り替えを行う
  4. 手順1の画面に復帰

●試験結果

アプリが強制終了する場合がある。

エラーログ:
FATAL EXCEPTION: main
java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0
  at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:251)
  at java.util.ArrayList.get(ArrayList.java:304)
  at android.widget.HeaderViewListAdapter.isEnabled(HeaderViewListAdapter.java:164)
  at android.widget.ListView.dispatchDraw(ListView.java:3342)
  at android.view.View.draw(View.java:10999)
  at android.widget.AbsListView.draw(AbsListView.java:3591)
  at android.view.View.getDisplayList(View.java:10435)
  at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:2597)
  at android.view.View.getDisplayList(View.java:10398)
  at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:2597)
  at android.view.View.getDisplayList(View.java:10398)
  at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:2597)
  at android.view.View.getDisplayList(View.java:10398)
  at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:2597)
  at android.view.View.getDisplayList(View.java:10398)
  at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:2597)
  at android.view.View.getDisplayList(View.java:10398)
  at android.view.HardwareRenderer$GlRenderer.draw(HardwareRenderer.java:875)
  at android.view.ViewRootImpl.draw(ViewRootImpl.java:2027)
  at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1751)
  at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2559)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loop(Looper.java:137)
  at android.app.ActivityThread.main(ActivityThread.java:4475)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:511)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:559)
  at dalvik.system.NativeStart.main(Native Method)

●再現率

3/5

●原因解析

同様の現象が発生する最小構成のコードが下記。
public class HeaderListViewActivity extends Activity {
    ArrayList<String> elements = new ArrayList<String>();

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

    @Override
    protected void onDestroy() {
        super.onDestroy();
        elements.clear();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_MENU) {
            showDialog(1);
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        if (id == 1) {
            elements.add("A");
            elements.add("B");
            elements.add("C");
            ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                    android.R.layout.simple_list_item_1, elements);
            AlertDialog dialog = new AlertDialog.Builder(this)
                    .setSingleChoiceItems(adapter, 1, null).create();

            TextView header = new TextView(this);
            header.setText("header");
            dialog.getListView().addHeaderView(header, null, false);

            return dialog;
        }

        return super.onCreateDialog(id);
    }
}

問題が発生するトリガはonDestroyで実行しているelements.clear()でした。
elementsはDialogが持つListViewに紐付けられたデータセットです。
タイミングによっては、onDestroyの後にDialogのListViewがelementsへアクセスする
ようで、HeaderViewListAdapter.isEnabledで範囲外を参照してしまうようです。

また、この例外は37行目のaddHeaderViewで、第三引数にfalseを渡した場合に発生します。
# つまりtrueを設定すると再現しない

●解決策

今回のケースだと、elementsをclearする理由がなかったので、これをやめるだけで対応
できたけれど、clearする必要がある場合はどうするのだろう。。。

stackoverflowをみると「Samsung phneだと起きる」という人がいたりするけれど、
今回の結果を見ると、組む側の問題のような気がします...

# 個人的にListHeaderViewやListFooterView周辺はよく不具合が出るので、
# なるべくなら使用を避けたいところです。

以上です。