Bacon Rank Android App
※これは以下のブログの翻訳である。
Bacon Rank Android App Details May 13, 2010, 10:11 pm
ベーコンランクはUIとバックグラウンド処理という観点から見て、僕が最も野心的に取り組んでいるプロジェクトだ。 これはカスタムなUIコントロールをもち、長時間のネットワークオペレーションをうまく処理する。
まずは、最初にカスタムなSeekBarを見てみよう。これは君がベーコンを何切れ食べたかを選択するものなんだ。 タッチスクリーンがあるなら、特に物理的なキーボードが無いのであれば、ユーザに何かをタイプさせるのは避けたいよね。 一回にそれほど多くのベーコンを食べる人なんていないから、このSeekBarはとてもうまく動作してくれるんんだ。 まずはカスタムなイメージでSeekBarを表示させるために、bacon_seekbar.xmlをdrawableフォルダに作成する。
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+android:id/background" android:drawable="@drawable/progress_mediumbacon" /> <item android:id="@+android:id/SecondaryProgress" android:drawable="@drawable/progress_rawbacon" /> <item android:id="@+android:id/progress" android:drawable="@drawable/progress_cookedbacon" /> </layer-list>
progress_* というDrawableはPNG画像だよ。
メインレイアウトで、SeekBarを使い、カスタムなサムイメージを使う(skilletのPNG画像だ)。
<SeekBar android:id="@+id/SEEKBAR" android:layout_below="@id/BACON_STRIPS" android:layout_width="fill_parent" android:layout_height="wrap_content" android:max="20" android:progress="0" android:secondaryProgress="0" android:paddingLeft="32px" android:paddingRight="32px" android:progressDrawable="@drawable/bacon_seekbar" android:thumb="@drawable/skillet" />
SeekBarは三つの状態を持つ(バックグラウンドも入れるとするなら)、だけど二つしか必要ではない。 バックグラウンドと進捗状態を使おうとしたけど、おかしなことにうまく動かないんだ。 だからそのかわりに、SecondaryProgressをバックグラウンドとして使うことにした。バックグラウンドは見えないようにしてある。
onCreateでSeekbarをセットアップする。
mSeekBar = (SeekBar)findViewById(R.id.SEEKBAR); mSeekBar.setOnSeekBarChangeListener(this); mSeekBar.setMax(PROGRESS_MAX);
onProgressChangedメソッドで、UIの「量」を変更する。 とても素敵に見えるんだけど、でももっと3Dっぽくすればよりよくなるかも。
アプリケーションのもっともトリッキーな部分は、スクリーンの向きの変更中にネットワーク通信を扱う部分だ。 (Androidの)チュートリアルや、stackoverflowの質問と答えのどれもこれもうまくいかない(まぁ、僕が理解できていない可能性もあるけど)。
問題というのはこうだ。Activityの中でAsyncTaskを使ってネットワークとの通信を行うとしよう。 実際のところ、これはどこでだって推奨されてる方法だよね。
ところがだ。ユーザがスクリーンの向きを変えたらどうなる? デフォルトではAndroidは現在のActivityを破棄して、新しいActivityを作成する。 AsyncTaskは動作し続けていて、新しいActivityのことなんか知らないんだ。 逆に、新しいActivityはAsyncTaskのことなんか知らない。どうすりゃいいの?
そこで、カスタムなアプリケーションオブジェクトを作ればいいってわけ。 これはプロセスが走っている限り存在し続けるからね。 だから、ActivityがAsyncTaskをスタートさせたら、アプリケーションにそれへの参照を持たせる。 AsyncTaskはスクリーンに描画中のActivityへの参照が必要なんだけど、それを常に最新のものにするってわけ。
つまり、AsyncTaskの生成時にこれをセットし、スクリーンの回転時Activtyが破棄されたときにnullにし、Activityが再生成されたときに復帰する。 なおかつ、ネットワーク通信が進行中には進捗インジケータも表示させたいよね。 こんな感じだよ(関係の無い部分ば省略してるよ)。
public class BaconApp extends Application { public BaconRank.SyncTask mSyncTask = null; // ... } public class BaconRank extends Activity implements SeekBar.OnSeekBarChangeListener { // ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); // ... } BaconRank.SyncTask getSyncTask() { return ((BaconApp)getApplication()).mSyncTask; } void sync(String action) { ((BaconApp)getApplication()).mSyncTask = (SyncTask) new SyncTask(this).execute(getUserAgent(), mUserId, mBaconStrips.getText().toString(), action); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); SyncTask syncTask = getSyncTask(); if (syncTask != null) { syncTask.setActivity(null); } } @Override public void onRestoreInstanceState(Bundle inState) { super.onRestoreInstanceState(inState); SyncTask syncTask = getSyncTask(); if (syncTask != null) { setProgressBarIndeterminateVisibility(true); syncTask.setActivity(this); } } @Override public void onStop() { super.onStop(); SyncTask syncTask = getSyncTask(); if (syncTask != null) { syncTask.setActivity(null); } } public class SyncTask extends AsyncTask<String, Void, JSONObject> { // ... public SyncTask(BaconRank activity) { super(); mActivity = activity; } public void setActivity(BaconRank activity) { mActivity = activity; } protected void onPreExecute() { if (mActivity != null) { mActivity.setProgressBarIndeterminateVisibility(true); } } protected void onCancelled() { // mActivity cannot be null mActivity.setProgressBarIndeterminateVisibility(false); clearActivity(); } void clearActivity() { // call in ui thread only ((BaconApp)mActivity.getApplication()).mSyncTask = null; } protected void onPostExecute(JSONObject result) { if (mActivity != null) { // Update UI } clearActivity(); } protected JSONObject doInBackground(String... params) { // Do the network stuff } // ... }
そうそう、カスタムアプリケーションオブジェクトをAndroidMnifest.xmlで定義するのを忘れないようにね。
<application android:name=".BaconApp" >