読者です 読者をやめる 読者になる 読者になる

Murayama blog.

プログラミングと、その次の話

AndroidでParse入門 - User Interface -

元ネタ

こちらを参考にUser Interfaceについてまとめます。
https://parse.com/docs/android_guide#ui

前回の記事はこちら。
AndroidでParse入門 - GeoPoints - - Murayama blog.

User Interface

アプリケーションのユーザはAndroidのUIコンポーネントと対話します。ParseはParse上のデータを効率よく扱うための共通的なUIウィジェットのサブクラスを提供しています。

ParseQueryAdapter

Parseはコレクションデータを表示するためにAdapterの実装クラスを提供しています。静的なオブジェクト配列を使うベーシックなListAdapterを使う代わりに、ParseQueryAdapterクラスを使えば抽象的なレイヤーが提供されます。その結果、AdapterView(たとえばListView、GridView)を使って、Parseクラスのデータを簡単に表示できるようになります。

Activity上でデータを表示するためのParseQueryAdapterの使い方は、onCreateメソッドで次のように実装します。

  1. ParseQueryAdapterを生成します。
  2. 必要に応じてカスタマイズします(データの問い合わせ、ビューの変更などの詳細については、後のセクションを参照してください)
  3. setAdapter()メソッドによってAdapterViewにAdapterをセットします。

WindowにAdapterViewがアタッチされると、ParseQueryAdapterは自動的にデータの先頭部分をフェッチします。このサブクラスは次に示すコードを簡略化します。

  1. タップで次のページを読み込むページネーション
  2. 行内のリモートイメージの表示やダウンロードの設定
  3. Parseオブジェクト配列の自動読み込み管理
  4. データライフサイクルにおける主要なイベントからのコールバック

次のコードは、ListViewにデータを表示するためのシンプルなParseQueryAdapterのセットアップです。わずかな設定で機能的なListViewが実装できます。

// Inside an Activity
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  // Uses a layout with a ListView (id: "listview"), which uses our Adapter.
  setContentView(R.layout.main);
 
  ParseQueryAdapter<ParseObject> adapter = new ParseQueryAdapter<ParseObject>(this, "Instrument");
  adapter.setTextKey("name");
  adapter.setImageKey("photo");
 
  ListView listView = (ListView) findViewById(R.id.listview);
  listView.setAdapter(adapter);
}

このビューはInstrumentsリストのnameを表示します。ここでは記述していないコードに注目してください。連続したページのフェッチロジックを記述していませんし、配列データの管理ロジックも記述していません。UIウィジェットに表示するイメージをバックグラウンドでダウンロードするコードもないですし、次のページをロードするタッチイベントの処理も記述していません。

ParseQueryAdapterは、データの加工や表示方法、フェッチ時の前処理、後処理をカスタマイズできます。以降の節では、ParseQueryAdapterを使ってあなたが何をできるのかを解説します。ニーズに合わせるために、どのように微調整すれば良いかを見ていきましょう。

Customizing the Query

ParseQueryAdapterのコンストラクタは、ContextとParseのクラス名を引数に取ります。これにより、クラスに属するすべてのParseObjectがフェッチされ、createdAtタイムスタンプ順に表示されます。

Parseは、この振る舞いを変更できるようにArrayAdapterの機能から設計しています。ParseQueryAdapterは、Adapterによって表示されるオブジェクト配列を設定するのではなく、自分で定義したParseQueryを返すQueryFactoryクラスを引数に受け取ります。QueryFactoryインスタンスコンストラクタで受け取ることで、Adapterはクエリの結果を使用し、オブジェクトをフェッチして表示するようになります。

次のコードは、punkとmetalで4名以上メンバーがいるバンドをレコードの売上順で表示するためのParseQueryAdapterのセットアップです。

ParseQueryAdapter<ParseObject> adapter =
  new ParseQueryAdapter<ParseObject>(this, new ParseQueryAdapter.QueryFactory<ParseObject>() {
    public ParseQuery<ParseObject> create() {
      // Here we can configure a ParseQuery to our heart's desire.
      ParseQuery query = new ParseQuery("Band");
      query.whereContainedIn("genre", Arrays.asList({ "Punk", "Metal" }));
      query.whereGreaterThanOrEqualTo("memberCount", 4);
      query.orderByDescending("albumsSoldCount");
      return query;
    }
  });

Customizing the Rows

AdapterView内の個々ビューのデフォルトレイアウトは、ParseImageViewとTextViewによるシンプルなLinearLayoutです。ParseQueryAdapterに対してsetTextKey(String) を呼び出すと、指定したキーがParseObjectの表示項目となり、TextViewに表示されます。同様に、setImageKey(String)を呼び出すと、指定したキーがParseObjectの表示項目となり、ImageViewに表示されます。

行をカスタマイズする一つの方法は、getItemView(ParseObject, View, ViewGroup) やgetNextPageView(View, ViewGroup)をオーバーライドし、スーパークラスの適切なメソッドを呼び出す方法です。独自のレイアウトをスーパークラスの実装に提供する場合、getItemView(ParseObject, View, ViewGroup) とgetNextPageView(View, ViewGroup) は、textKeyがセットされているならTextView (id: android.R.id.text1)、imageKeyがセットされているならParseImageView (id: android.R.id.icon)を期待します。*1

独自のレイアウトをinflateして設定し、TextViewとParseImageView、さらに"description"TextView(id: R.id.description)を追加する場合、次のようになります。

@Override
public View getItemView(ParseObject object, View v, ViewGroup parent) {
  if (v == null) {
    v = View.inflate(getContext(), R.layout.adapter_item, null);
  }
 
  // Take advantage of ParseQueryAdapter's getItemView logic for
  // populating the main TextView/ImageView.
  // The IDs in your custom layout must match what ParseQueryAdapter expects
  // if it will be populating a TextView or ImageView for you.
  super.getItemView(object, v, parent);
 
  // Do additional configuration before returning the View.
  TextView descriptionView = (TextView) v.findViewById(R.id.description);
  descriptionView.setText(object.getString("description"));
  return v;
}

行をカスタマイズするもう一つの方法は、ParseQueryAdapterのメソッドをオーバーライドし、スーパークラスの実装を完全に無視する方法です。ParseObjectに定義されたcolorを個々のItem Viewに適用するシンプルな例は次のようになります。

@Override
public View getItemView(ParseObject object, View v, ViewGroup parent) {
  if (v == null) {
    v = View.inflate(getContext(), R.layout.adapter_item, null);
  }
  v.setBackgroundColor(object.getInt("color"));
  return v;
}
 
@Override
public View getNextPageView(View v, ViewGroup parent) {
  if (v == null) {
    // R.layout.adapter_next_page contains an ImageView with a custom graphic
    // and a TextView.
    v = View.inflate(getContext(), R.layout.adapter_next_page, null);
  }
  TextView textView = (TextView) v.findViewById(R.id.nextPageTextViewId);
  textView.setText("Loaded " + getCount() + " rows. Get more!");
  return v;
}

Loading Remote Images in Rows

ParseQueryAdapterは、簡単にリモートイメージを表示することができます。setImageKey(String)を呼び出せば、ParseFileを保持したParseObjectのキー名を通じて、Parseからイメージをフェッチし対象行のParseImageViewにロードします。

イメージは非同期でダウンロードされるので、ParseImageViewはバックグラウンドで更新されます。ユーザのスクロールにより、Adapterによって行がリサイクルされると、ParseObjectが割り当てられ、イメージがフェッチされて表示されます。

画像のフェッチが完了するまでプレースホルダイメージを定義することもできます。ParseQueryAdapterのsetPlaceholder(Drawable) を呼び出すと、指定したDrawableがフォールバックイメージとして描画されます。

Lifecycle Methods

カスタムロジックを適用するために、Adapterに2つのデータライフサイクルフックを用意しています。一つは、データのクエリの前処理であり、もう一つはクエリからフェッチされたオブジェクトがロードされたときの後処理です。これらのメソッドは、ロードされたUIの切り替えなどに有効です。OnQueryLoadListenerはsetOnQueryLoadListenerによってセットします。onLoading() とonLoaded(List, Exception) を実装する必要があります。

Pagination

ページネーションは、テーブルからリストの1ページ目だけを取得します。ページに含むオブジェクトの件数は、setObjectsPerPage(int)を呼び出すことで指定できます。

クエリはページネーションによって自動的に調整されます。AdapterViewの最終行にページネーション行が表示されるので、ユーザは次のページを読み込むことができます。

ページネーションはデフォルトで有効となっています。無効にしたい場合はsetPaginationEnabled(false)を呼び出してください。ページネーションを無効にすると、ParseQueryAdapterはデフォルトで最大100件の結果を読み込みます。

Auto-loading of Data

ParseQueryAdapterを設定したAdapterViewがWindowにアタッチされると、先頭のページをフェッチする際にParseQueryAdapterのloadObjects()メソッドが呼び出されます。この挙動を無効にしたい場合(データの読み込みを遅延させたり、カスタムロジックを実行させたりしたい場合)、setAutoload(false)を呼び出してください。その場合、loadObjects()は手動で呼び出すようにします。

*1:変な言い回しですみません、後のコード見た方が良いです。