Revision 2 as of 2010-10-24 14:13:24

Clear message
Locked History Actions

Android/baconRank

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 has three states (if you count background), but I am really just using two. The weird part is that I thought I would use background and progress states, but I could not get that to work. Instead, I am using SecondaryProgress as background, and background is not visible anywhere. In onCreate I set up the SeekBar:

mSeekBar = (SeekBar)findViewById(R.id.SEEKBAR); mSeekBar.setOnSeekBarChangeListener(this); mSeekBar.setMax(PROGRESS_MAX);

In onProgressChanged method I update the amount in the UI and that is about it for the SeekBar. I think it looks pretty nice, but it would be better if I could make it look more 3D:

The trickiest part of the application was handling of the network activity while allowing for screen orientation changes. None of the tutorials I’ve read or the stackoverflow questions and answers regarding this seemed workable (or maybe I did not understand them). The problem is this: suppose in your activity you start an AsyncTask for the network activity. This is what is actually recommended pretty much everywhere. But suppose your user then changes the screen orientation. By default Android destroys and recreates the current activity. Your AsyncTask is still going, but it does not know about the new activity, and your new activity has no knowledge of the AsyncTask. So what is the fix?

It turns out you can create a custom application object, which will be available as long as your process is running. So in your activity you can start the AsyncTask as before, but keep a reference to the AsyncTask in the application object. The AsyncTask needs a reference to the current activity to draw on the screen, which your activity keeps up to date. Basically set it on AsyncTask creation, null it during screen orientation and activity destroy, and restore when the activity is recreated. We also want to run an indeterminate progress indicator in the UI while network activity is going. In code this looks something like this (I’m omitting many irrelevant details):

public class BaconApp extends Application {

}

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
      } // ...
    }

You will also need to specify the custom application object in the AndroidManifest.xml:

  • <application

    >

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.