プログラマってこんなかんじ??

アプリ作ったり歌ったりしてます

Android UI Cookbook for 4.0 輪講会 vol.1

Androidの会福岡支部メンバー数人で輪講会をはじめました。

今回のお題は通称 yanzm本2 と言われている、
Android UI Cookbook for 4.0 ICSアプリ開発術 です。

f:id:dai4649:20120326215613j:image

実はちょっと前に Effective Java 第2版輪講をやってみたのですが、
それがまたなかなかおもしろくて。

  • 「人に伝えること」を前提にして熟読+理解
  • 実際に説明
  • 議論

をするとこれがまた異常に充実感があるというか。
みんなでやいやい言いながら興味のあることを勉強するのはやっぱり楽しいですね。
「おい、勉強くらい本読んで1人でやれよ」と言われたらそれまでなんだけどもw


というわけで、こんな感じで輪講会を進めてます。

進め方

  • 各自予習必須
  • 説明担当の人が本を音読しながら説明
  • サンプルアプリ動かしながら内容確認
  • 説明してるときに気になった点、疑問点などを皆で議論
  • 次回どこまでやるか+次回の説明担当決め(説明担当は毎回ローテーション)

第1回(2012/3/26)

メンバー

  • @yuuto
  • @shikajiro
  • @kenz_firespeed
  • @gatpai
  • 自分


それでは、議論や話題になった点をずらずらと列挙してみます。


1.1.1 UI上のバー

  • なんで [履歴ボタン] がわざわざ新規追加されたん?
  • 今まで通り [HOME]長押し でいいんじゃね?
    • 「長押しをやめる」というGoogle側の方針?
  • [メニュー用コントロール] って押しにくい。なんで右下?右上?
    • 謎。

1.1.2 システムUIの状態

  • ↓の使い分けは?
    • SYSTEM_UI_FLAG_LOW_PROFILE(システムナビゲーションバー控えめ)
    • SYSTEM_UI_FLAG_HIDE_NAVIGATION(システムナビゲーション非表示)
  • 画面タッチしたときに画面伸縮が気になるか気にならないかがポイント?
    • 例えば「頻繁に画面タッチするようなアプリ(ゲームとか)」は 前者 の設定よね。
    • 前者だと システムナビバー領域は常に固定なので画面伸縮は発生しない。
    • 後者だと システムナビバー領域が表示/非表示切替される(画面伸縮)。
  • 電子書籍リーダとか動画プレイヤーは前者推奨されているが、後者もあり??
    • 「なるべく大画面で見たい」という欲求もあるし。
    • ページめくり、早送り等の操作したいときだけナビバー領域動的表示。

コラム Activityの再生成抑止(android:configChanges)

  • そういえば orientation、screenSize 以外にも keyboard、keyboardHidden、fontScale な属性あるよね。
  • ガラスマはパカパカだったりスライダーだったりで、Activity再生成イベントいろいろ飛んでくるよね。

1.2.1 48dp単位のレイアウト

  • 「48dp=約9mm」 って 某iOSアプリのガイドラインで見たことあるねw
  • エレメント間隔は 「4dp もしくは 8dp単位」 が推奨(48dpの約数なので統一感出る)

1.2.2 タイポグラフィ

  • テキストサイズに 「sp指定」 ってICS以前からできなかったっけ?
  • テキストサイズ=10sp は 10dpにシステム全体のテキスト拡大率を掛けた大きさ

1.3.1 Device.Defaultテーマ

  • Theme.DeviceDefault は各メーカがシステムdefaultとしてカスタマイズしてるテーマ。
  • ICSデフォルトを使いたいなら 「Theme.Holo」 を明示的に指定しよう。
  • 指定しとかないと端末ごとに微妙に色見が異なる、などが起こる可能性も。

1.4.1 GridLayoutとSpace

  • GridLayoutはtableタグのようなレイアウト向け
  • table構造を LinearLayout の入れ子で作るくらいならこっちを使おう
  • view階層が浅くなるのでパフォーマンスよくなるはず
  • ただし、静的レイアウト向け
  • 子view幅高さ、要素数が動的に変わるようなレイアウトだと普通のViewGroup系よりコストかかる

1.5 ハンドセット/タブレット両対応

  • supports-screensタグ
    • Market上で画面サイズごとにDL可否フィルタリングできたり。
    • あくまで Marketでのフィルタリング用(Market介さない野良apkだと普通に実行可能)

1.6 Multi-paneレイアウト

  • 縦横切替したときの見え方に注意
    • 縦表示時に2つのパネル表示してたら、横にしても2つのままにする
    • このとき、縦/横で各パネルの幅を変更して見え方を調整

1.7 スワイプビュー

  • リスト画面(10件)から開いた詳細画面(要素1)でスワイプすると次の詳細画面(要素2)を表示するとか


とりあえず今回はここまで。


あとは、ABC2012Spring に参加した 支部長(@yuuto) からABC総括とか。
@yanzmさん のustとスライドって公開されないのかなー。
行けなかったのが悔やまれる。。


というわけで、参加メンバーの皆様お疲れ様でした。


次回は 第2章フラグメント。アツい。
今度は自分が説明担当なので予習頑張ります。


AsyncTask の挙動(AsyncTask#executeOnExecutor) その2

前回のエントリで AsyncTask#execute の挙動がAPI Levelによって違う、とわかりました。


では、どこで切り替えているのか。


てなことを twitter上 でぎゃーぎゃー言ってたら
@zaki50さん から情報をいただきました!
(@zaki50さん、ありがとうございます!)

参考

日本Androidの会 - API LEVEL 11以上のAsyncTaskの振る舞いについて


というわけで ICSの ActivityThread.java を読んでみました。
ソースの該当箇所はココ。

// android.app.ActivityThread
private void handleBindApplication(AppBindData data) {

    // 抜粋

    // If the app is Honeycomb MR1 or earlier, switch its AsyncTask
    // implementation to use the pool executor.  Normally, we use the
    // serialized executor as the default. This has to happen in the
    // main thread so the main looper is set right.
    if (data.appInfo.targetSdkVersion <= 12) {
        AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }
}

思いっきり書いてあります。
ココで切り替えてたのですねー。。


このソース内、他にも Honeycomb以前だったら判定とか StrictMode設定だったりとか。
いろんなことやってるのがチラチラ見えたのでもっと細かく読んでみようと思います。
恥ずかしい話、ActivityThread は Looperの起動 くらいしか思っていなかったですw

AsyncTask の挙動が異なる(AsyncTask#executeOnExecutor)

AsyncTask の挙動が Gingerbread と ICS で違う、という話を聞いて調べてみた。

複数AsyncTask実行=バックグラウンドで並行処理 と考えてると、実は並行処理になってなくて処理が遅い、ってこと。

結論を言うと、ソースをここだけ修正すればよさそう。

修正方法

  • AsyncTask#execute を AsyncTask#executeOnExecutor に変更する
  • AsyncTask#executeOnExecutor の引数に AsyncTask.THREAD_POOL_EXECUTOR を指定する


1行で済みそうな修正。よかったw
とはいえ気になったので、AsyncTask#execute の挙動の違いについてまとめてみる。
↓はSDKのバージョンを変えて検証した結果。

検証結果

複数AsyncTask の AsyncTask#execute を実行した場合

  • API Level 1012まで
    • 複数バックグラウンド処理がパラレルで実行される
  • API Level 1113以降
    • 複数バックグラウンド処理がシリアルで実行される


3.2 から変わった様子。
ちなみに検証はこんなかんじで。

検証方法

  • ビルドには ICS(API Level14) を使用
  • 以下のパターンのapkを作成
    • AndroidManifest.xmlandroid:minSdkVersion を11〜14の間で変更したapkを作成
  • emulator(API Level14) 上で実行


では、GB相当の AsyncTask と同等の動きをさせるにはどうしたらいいか。
AsyncTask#executeJavadoc見るとこう書いてある。

After HONEYCOMB, it is planned to change this back to a single thread 
to avoid common application errors caused by parallel execution. 
If you truly want parallel execution, 
you can use the executeOnExecutor(Executor, Params...) 
version of this method with THREAD_POOL_EXECUTOR; 
however, see commentary there for warnings on its use.

Honeycomb以降では 複数のAsyncTackのバックグラウンド処理 をパラで実行したいなら
AsyncTask#executeOnExecutor + THREAD_POOL_EXECUTOR を使用してください、と。


今回使用したサンプル。
(ボタン押したら AsyncTask#execute か AsyncTask#executeOnExecutor を実行するだけ)

実行結果(emulator(API Level14))

AsyncTask#execute
01-25 02:58:37.899: D/AsyncTaskTest(613): [doInBackground]start 78
01-25 02:58:39.950: D/AsyncTaskTest(613): [doInBackground]end   78
01-25 02:58:39.950: D/AsyncTaskTest(613): [doInBackground]start 79
01-25 02:58:41.970: D/AsyncTaskTest(613): [doInBackground]end   79
AsyncTask#executeOnExecutor
01-25 02:59:11.889: D/AsyncTaskTest(613): [doInBackground]start 80
01-25 02:59:11.929: D/AsyncTaskTest(613): [doInBackground]start 81
01-25 02:59:13.919: D/AsyncTaskTest(613): [doInBackground]end   80
01-25 02:59:13.989: D/AsyncTaskTest(613): [doInBackground]end   81

AsyncTask#executeOnExecutor の方はちゃんとパラで動いてる。

参考

@findup さんのエントリ。
[http://www.swingingblue.net/mt/archives/003629.html:title=きままな日記帳 - GingerbreadとICSでのAsyncTaskの挙動の違い

ListViewの高速スクローラまわりをカスタマイズ(AbsListView#setFastScrollEnabled)

前回 のエントリに引き続き、さらに ListView の高速スクローラまわりをカスタマイズしてみました。
(ついでにタイトル変更)

環境

2.3.3(API Level10)

before
f:id:dai4649:20111220013528p:image

after
f:id:dai4649:20111220013527p:image

スクローラもいじったりましたw

ここで疑問。
ICSではどうなるのだろうか。
とりあえずソースそのままで API Level だけ変えて実験。

環境

4.0(API Level14)

before
f:id:dai4649:20111220014815p:image

after
f:id:dai4649:20111220014816p:image

ガイド(overlay)部分が3Dぽい感じに、スクローラは細身な感じに。

ソースはこちら。(前回エントリで使用した部分も合わせてます)
https://gist.github.com/1384757

ListView の高速スクロール時ガイドを編集する(AbsListView#setFastScrollEnabled)

Android Advent Calendar 12/18 担当の @daichan4649 です。
豪華メンバーがものすごく有意義なネタを記載されている中で、
空気を無視して ListView について誰得なネタを書いてみるとします。


ListViewには項目が多い場合に 「高速スクローラ」 を表示する機能がありますよね。
このとき 「スクロール中に項目のどのあたりを表示しているか」 を示す、
ガイドみたいなヤツを表示することができたりもします。


高速スクローラ、ガイド表示の実装に必要なのはこの2つ。

  1. AbsListView#setFastScrollEnabled(true)
  2. 1のListViewに設定するAdapterに SectionIndexer をimplementsする


1で高速スクローラを有効にし、
2でガイド表示内容を実装する、と。
実装してみると実際のイメージはこんなかんじになります。


初期実装

この例ではガイドに 「画面内先頭要素のindex値」 をそのまま表示してます。
右側に表示されているスクローラをドラッグすると、
リスト先頭項目に合わせてガイド表示内容も変わっていく、という感じです。


本題に入ります。


このガイド。
普通にいじろうとすると 指定した文字列を表示する、くらいしかできない。
濃いグレーの正方形 は編集できない。


編集できないと言われるといじりたいじゃないですか。


ということで、framework のソース読んで試してみました。
とりあえず今回はこのグレーの正方形を変えるところを実験してみるとします。
いつものドロイド君アイコン画像に差し替えてみたり。


カスタム実装

変更OK!!


では、どんなかんじで差し替えたかを書いてみます。


まず、基本から。
AbsListView#setFastScrollEnabled(true) を呼び出すと
「高速スクローラとガイド」 を管理している FastScrollerインスタンス
AbsListView内で生成されます。
しかし、ガイドのインスタンスが FastScrollerクラス 内に隠蔽されていて、
外部から編集できないようになっていたりします。
↓の FastScrollerインスタンス の中をいじりたいのですが。。。

// AbsListView.java
public void setFastScrollEnabled(boolean enabled) {
    mFastScrollEnabled = enabled;
    if (enabled) {
        if (mFastScroller == null) {
            mFastScroller = new FastScroller(getContext(), this);
        }
    } else {
        if (mFastScroller != null) {
            mFastScroller.stop();
            mFastScroller = null;
        }
    }
}

とまぁ、どうしようもなかったのでリフレクションでいじりましたw
リフレクションでいじるポイント、タイミングは以下。

  1. AbsListView#setFastScrollEnabled(true) を呼び出す
  2. その直後にリフレクションで内部のFastScrollerインスタンスを差し替える


実際のソースはこんな感じになります。
https://gist.github.com/1384757

// 今回のポイントになるリフレクション部分だけ貼りつけてみます

@Override
protected void onCreate(Bundle savedInstanceState) {

    (抜粋)

    final ListView listView = (ListView) findViewById(R.id.listview);

    // 高速スクローラ有効化設定
    listView.setFastScrollEnabled(true);

    // AbsListView#setFastScrollEnabled 直後に実行
    Drawable overlay = getResources().getDrawable(R.drawable.ic_launcher);
    customizeFastScroller(listView, overlay);
}

private void customizeFastScroller(AbsListView listView, Drawable overlay) {
    try {
        // 新FastScrollerインスタンス生成
        Class<?> clazz = Class.forName("android.widget.FastScroller");
        Constructor<?> constructor = clazz.getConstructor(Context.class, AbsListView.class);
        Object newFastScroller = constructor.newInstance(this, listView);

        // ガイド用drawable(グレーの四角)を上書き
        Field fieldOverlay = clazz.getDeclaredField("mOverlayDrawable");
        fieldOverlay.setAccessible(true);
        fieldOverlay.set(newFastScroller, overlay);

        // FastScrollerインスタンス(オリジナル)を上書き
        Field orgFastScroller = AbsListView.class.getDeclaredField("mFastScroller");
        orgFastScroller.setAccessible(true);
        orgFastScroller.set(listView, newFastScroller);

    } catch (Exception e) {
        e.printStackTrace();
    }
}

見てわかるとおり、強引な手法ですw
この方法を使えば 「スクローラ画像」 自体も差し替え可能ですね。
それについてはまた後日書こうと思います。(gistにはコード載せてます)


最後に。
このような素晴らしい企画に参加させていただきまして、
@youten_redo さん、本当にありがとうございました!
明日(12/19)の担当は @patorash さんです!