AndroidのListViewを,いかにシンプルかつ楽にコーディングするか。Adapterを記述不要のライブラリ案
ListViewを楽に使うためのDSLを考案する。
はじめに
AndroidのUIで,ListViewの実装はめんどい。
スマホアプリで,要素がリスト形式に並ぶ,というシーンは頻繁にある。
だが特にAdapter周りを毎回コーディングするのが面倒で,どこか回りくどい。
データ構造も微妙に煩雑。
AndroidアプリでListViewをカスタマイズし,Web上の画像を行ごとに表示するサンプルコード (SimpleAdapterクラスを独自に拡張)
http://language-and-engineering.hatenablog.jp/entry/20111014/p1
この手間を,いかに省くか。
一つの方法は,そもそもListViewを使わないことだ。
別の手段で,複数の要素を「動的に」列挙する。
この方法については,Android-MVCフレームワークの初版公開時にすでに提案してある。
Androidアプリの画面レイアウトを,まるでjQueryのようなコードで動的構築できるライブラリ (の試作品。UIコーディングのためのDSL)
http://language-and-engineering.hatenablog.jp/entry/20120210/p1
forループ中で要素を動的にaddしているサンプルコード
http://code.google.com/p/android-mvc-...
そして,今回提案するもう一つの方法は,ListViewを手軽に扱うためのDSLを構築することだ。
上図のような単純なUIを実現するために,
準備済みのライブラリがあれば,どれぐらい短くコーディングできるのか。
サンプルコード
Activityのコード:
package com.example; import com.android_mvc.framework.ui.UIBuilder; import com.android_mvc.framework.ui.list.ListViewDataStore; import com.android_mvc.framework.ui.list.LineUIBuilder; import com.android_mvc.framework.ui.list.MListView; import com.android_mvc.framework.ui.view.MButton; import com.android_mvc.framework.ui.view.MLinearLayout; import android.os.Bundle; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.view.View; import android.view.View.OnClickListener; import android.widget.TextView; /** * ListViewのDSLのサンプル画面。 * @author id:language_and_engineering * */ public class ListViewSampleActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Context context = this; new UIBuilder(this) .withoutScroll() .add( new MButton(this) .text("ボタン1") , new MLinearLayout(this) .heightPx(350) .add( // ここからListView new MListView(this) .bgcolor(Color.parseColor("#ffffff")) .widthFillParent() .defineLines( // 行のUIを定義 new LineUIBuilder(R.layout.menu_listline) { @Override protected void describeOneLine( final int position, View lineView ) { // 動的に文言を表示 ((TextView)lineView.findViewById(R.id.tv1)).setText( (position + 1) + ": " + getLineData(position, "word_title").toString() ); // 行タップ時の挙動 lineView.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // 指定されたアクティビティに画面遷移 context.startActivity( new Intent( context, (Class<?>) getLineData(position, "dest") ) ); } }); } } , // 行内のデータを定義 new ListViewDataStore() .newLine() .put("word_title", "日本語") .put("dest", AActivity.class) .newLine() .put("word_title", "英語") .put("dest", BActivity.class) .newLine() .put("word_title", "ンデベレ語") .put("dest", CActivity.class) .newLine() .put("word_title", "C言語") .put("dest", DActivity.class) ) ) // ListView ここまで , new MButton(this) .text("ボタン2") ) .display(); } }
リストビューの1行分のレイアウトXML:
menu_listline.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/linearLayout1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="20dp" android:background="#ffffff" > <TextView android:id="@+id/tv1" android:textAppearance="?android:attr/textAppearanceLarge" android:layout_height="wrap_content" android:layout_width="wrap_content" android:textColor="#222222" /> <ImageView android:layout_alignParentRight="true" android:layout_height="30dp" android:layout_width="30dp" android:layout_marginRight="20dp" android:src="@android:drawable/ic_media_play" /> </RelativeLayout>
ソースコードが,画面の仕様をそのまま表現している。
1行分のUI定義と,各行に渡したいデータとを記述するだけ。
付随的なことは一切,コードに現れない。
※各行内のレイアウトを動的に構築するという発想もあったのだが,没とした。
ListViewの性質上,各行の描画処理(getView)が何度も繰り返し頻繁にコールされるので,
その度に見た目を1行ずつ動的に構築する方法には無理があったため。
なので,ベースとなる行のレイアウトはXMLで定義する必要がある。
サンプルコードを動作させるための,ライブラリ側のコード
以下はフレームワーク側のコードになる。
Android-MVC framework ver0.3に機能追加する形での提案となる。
わりと簡単な事しかやっていない。
上述のサンプルコード中で,
LineUIBuilderは,単なるメソッドのラッパ。
ListViewDataStoreは,ArrayListとHashMapのラッパ。
残りのクラスが本質的であり,その中でも要点となる部分の処理を掲載する。
まず,ビュー部品のラッパ。
MListView.java
package com.android_mvc.framework.ui.list; import java.util.HashMap; import java.util.List; import com.android_mvc.framework.annotations.SuppressDebugLog; import com.android_mvc.framework.ui.view.IFWView; import com.kids_be_genius.EnglishGreetingVoice.util.Util; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import android.widget.SimpleAdapter; /** * ListViewのラッパークラス。 * @author id:language_and_engineering * */ @SuppressDebugLog(true) public class MListView extends ListView implements IFWView { // NOTE: // ・ScrollViewの中では使わないこと。高さがおかしくなる。 // http://stackoverflow.com/questions/10709411/android-listview-rows-in-scrollview-not-fully-displayed-clipped // ・高さを変えるには,ラッパーViewとしてLinearLayoutを設け,そこでheightを指定すること。 // ListViewとアダプタの基本的な使い方 @see http://language-and-engineering.hatenablog.jp/entry/20111014/p1 〜中略(属性操作など)〜 // 行の定義 /** * 行のUIとデータを定義 */ public MListView defineLines(LineUIBuilder line_builder, ListViewDataStore listViewDataStore) { // アダプタを作ってセットする this.setAdapter( new MSimpleAdapter( context, listViewDataStore.getResult(), line_builder.line_template_layout_id, new String[]{}, new int[]{}, line_builder ) ); return this; } /** * アダプタ */ protected class MSimpleAdapter extends SimpleAdapter { private LayoutInflater mInflater; private LineUIBuilder line_builder; // 初期化 public MSimpleAdapter ( Context context, List<HashMap<String, Object>> list_data, int resource, String[] from, int[] to, LineUIBuilder line_builder ) { super(context, list_data, resource, from, to); this.line_builder = line_builder; this.line_builder.registerData(list_data); // リストの動的な描画のためにインフレータを生成 this.mInflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE ); Util.d("ListView:アダプタの初期化が完了"); } // 1行を描画するたびに呼ばれるメソッド @Override public View getView(int position, View convertView, ViewGroup parent) { // 行を表すビュー View v = convertView; if(v == null){ v = mInflater.inflate(line_builder.line_template_layout_id, null); } // この行のUIを構築 //Util.d("ListView:" + position + "行目の描画を開始"); line_builder.describeOneLine(position, v); //Util.d("ListView:" + position + "行目の描画を完了"); return v; } } }
また,UIBuilder.javaに,少し追記する。
/** * ルートビューからスクロールをなくす */ public UIBuilder withoutScroll() { // ScrollViewではないレイアウトを雛形として使う context.setContentView(R.layout.fw_template_noscroll); this.rootLayout = (MLinearLayout)context.findViewById(R.id._FWRootLayout); // NOTE: setContentViewを複数回読んだら,後者が優先される // http://ichitcltk.hustle.ne.jp/gudon/modules/pico_rd/index.php?content_id=44 return this; }
そして,スクロールのないレイアウトXMLを定義。
(ListViewにはスクロール機能がついているので,ScrollViewの中に配置できないから)
fw_template_noscroll.xml
<?xml version="1.0" encoding="utf-8"?> <!-- 注意:フレームワーク側でテンプレートとして使うファイル。書き換えないこと。 --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <com.android_mvc.framework.ui.view.MLinearLayout android:id="@+id/_FWRootLayout" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <!-- ここに,UIBuilderでViewが構築される。 --> </com.android_mvc.framework.ui.view.MLinearLayout> </LinearLayout>
これでOK。
無いよりはずっとましだろう。
補足
ンデベレ語について:
しりとりで勝つ
http://www.eonet.ne.jp/~arawashi/ur/h...
- 「ン」でしりとりに勝つには、アフリカの力を借りるのが最もスマート ※「ンジャメナ」など
- ジンバブエには大きくわけて二つの民族が住んでいます。ひとつはジンバブエの首都ハラレを中心としたショナ族、そしてもう一つがジンバブエ第二の都市ブラワヨ周辺の民族、ンデベレ族
関連する記事:
AndroidのUIで,レイアウトXMLの記述を簡素にするための,7つの基礎知識
http://language-and-engineering.hatenablog.jp/entry/20121114/SimpleAndroidLay...
jQuery Mobile と HTML5 で、Androidのネイティブアプリを作成する手順
http://language-and-engineering.hatenablog.jp/entry/20120717/CreateAndroidNat...
Android SDK の動かないコード(中級編) ListView内の要素にアクセスしようとするとNullPointerExceptionで落ちるエラー
http://language-and-engineering.hatenablog.jp/entry/20111013/p1