Locked History Actions

Diff for "Android/baconRank"

Differences between revisions 4 and 5
Deletions are marked like this. Additions are marked like this.
Line 190: Line 190:
そうそう、カスタムアプリケーションオブジェクトをAndroidMnifest.xmlで定義するのを忘れないように そうそう、カスタムアプリケーションオブジェクトをAndroidMnifest.xmlで定義するのを忘れないようにね。
Line 197: Line 197:

And that wraps the Bacon Rank presentation. If you know how to do anything I presented above in a better way, please leave a comment. The application is also localizeable, so if you want to translate the app into your language let me know.

If you did not read it already, you might want to read about the application running baconrank.com, the server the Android application talks to.

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