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"
>