ListViewのカスタマイズ

2011/02/24

ListViewを使うとなると、行のコンポーネントをカスタマイズしたいところ。 iPhoneで言うと、UITableCellView、Flexで言うと(ry

参考ページ

ListViewとListActivity(3)-応用編

Android Adapter & ListView & ExpandableListView

ListViewの作法を知る

想像していたよりやっかいでした。 http://hoge/api/person/list にアクセスして人名リストデータをJSONで取得し、ListViewに人名と画像を表示するサンプル。 (サンプル用にオブジェクト名を変えているのでコンパイルエラーしているかも) ※画像はプロジェクトに埋め込み

layout/person_row.xml





    
        
            
            
            
        
    

src/xxxx/PersonList.java [java] public class PersonList extends ListActivity { DefaultHttpClient httpClient;

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    initHttp();
    loadPersons();
}

static class ViewHolder {
    TextView nameTextView;
    ImageView imageView1;
}

class Person {
    String name;
    String no;

    public Person() {

    }
}

public int personImageId(int no) {
    switch (no) {
        case 1:
             return R.drawable.image1;
        case 2:
             return R.drawable.image2;
        case 3:
             return R.drawable.image2;
    }
    return 0;
}

public class MyAdapter extends ArrayAdapter<Person> {
    private LayoutInflater inflater;
    private int layoutId;

    public MyAdapter(Context context, int layoutId, List<Person> objects) {
        super(context, 0, objects);
        this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        this.layoutId = layoutId;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            holder = new ViewHolder();
            convertView = inflater.inflate(layoutId, parent, false);
            holder.nameTextView = (TextView) convertView.findViewById(R.id.nameTextView);
            holder.imageView1 = (ImageView) convertView.findViewById(R.id.imageView1);

            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        if(!isEnabled(position)) {
            convertView.setBackgroundColor(Color.BLACK);
        } else {
            Person person = getItem(position);
            holder.nameTextView.setText(person.name);   
            int resourceId1 = personImageId(Integer.parseInt(person.no));
            if (resourceId1 > 0) holder.imageView1.setImageResource(resourceId1);           
        }

        return convertView;
    }

}

public void initHttp() {
    //スキーマ登録
    SchemeRegistry schReg = new SchemeRegistry();
    schReg.register(
            new Scheme(
                    HttpHost.DEFAULT_SCHEME_NAME,
                    PlainSocketFactory.getSocketFactory(),
                    80
            )
    );

    //HTTPパラメータ設定
    HttpParams httpParams;
    httpParams= new BasicHttpParams();
    HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
    HttpProtocolParams.setContentCharset(httpParams, HTTP.UTF_8);

    //HTTPクライアント生成
    httpClient = new DefaultHttpClient(
            new ThreadSafeClientConnManager(httpParams, schReg),
            httpParams
    );
}

public void loadPersons() {
    Uri.Builder uriBuilder = new Uri.Builder();
    uriBuilder.scheme("http");
    uriBuilder.authority("hoge");
    uriBuilder.path("/api/person/list");

    String uri = uriBuilder.toString();
    HttpUriRequest httpRequest = new HttpGet(uri);

    //HTTP Request送信
    HttpResponse response = null;
    try{
        response = httpClient.execute(httpRequest);
    } catch(Exception e) {
        return;
    }

    // レスポンスコードを確認
    if(response.getStatusLine().getStatusCode() != HttpStatus.SC_OK){
        AlertDialog.Builder diag = new AlertDialog.Builder(PersonList.this);
        diag.setTitle("ネットワークエラー");
        diag.setMessage("サーバに接続できませんでした");

        // ダイアログに表示するボタンの定義
        DialogInterface.OnClickListener listner = new DialogInterface.OnClickListener(){
        public void onClick(DialogInterface dialog, int which) {
                setResult(RESULT_OK);
            }
        };

        diag.setPositiveButton("OK",listner);
        diag.create();
        diag.show();
        return;
    }

    //JSON取得
    StringBuilder json = new StringBuilder();
    try{
        HttpEntity entity = response.getEntity();
        InputStream input = entity.getContent();
        InputStreamReader reader = new InputStreamReader(input);
        BufferedReader bufReader = new BufferedReader(reader);
        String line;
        while((line = bufReader.readLine()) != null) {
            json.append(line);
        }
    } catch(IOException e) {
        return;
    }

    items = new ArrayList<Person>();
    // JSON解析
    try{
        JSONObject jsonRoot = new JSONObject(json.toString());
        JSONArray jsonAreas = jsonRoot.getJSONArray("person");
        int i;
        for(i = 0; i < jsonAreas.length(); i++){
            JSONObject jsonRslt = jsonAreas.getJSONObject(i);

            Person person = new Person();
            person.name = jsonRslt.getString("name");
            person.image1 = jsonRslt.getString("image1");
            items.add(person);
        }

        MyAdapter adapter = new MyAdapter(this, R.layout.person_row, items);
        setListAdapter(adapter);
    } catch(JSONException e) {
        return;
    }
}

[/java]

ポイントやハマったところ。

ListActivityを使う

普通にActivityを使ってもできますが、手続きを多少簡略化できるListActivityを使いました。 Activityの場合、ListViewレイアウトを作ったり、Adapterのセットの仕方が異なってくるかと。

2011/08/11 追記 ListActivity を継承しない場合は、 [java] .... MyAdapter adapter = new MyAdapter(this, R.layout.list_row, values); (ListView) listView = (ListView) findViewById(R.id.listView); listView.setAdapter(adapter); [/java] のように、setAdapter() メソッドで Adapter を直接指定します。 ListActivity は1画面1リスト(だと思う)なので、複数リストを利用したい場合は、このやり方でよいかと。

ArrayAdapter

JSONデータ取得&Listデータ生成後、ArrayAdapterを利用してListViewにバインドしてやります。 [java] MyAdapter adapter = new MyAdapter(this, R.layout.person_row, items); setListAdapter(adapter); [/java] 2番目の引数が、行レイアウトになります。

getView

リストを描画しようとする度に呼ばれます(多分)。 iOSで言うと、TableView:cellForRowAtIndexPath にあたる部分と思ってよいかも。

Androidでは何やらif文で分岐してまどろっこしい書き方だけど、 既に行のコンポーネントが作成されていれば再利用します。

つまりは、ListViewの行のキャッシュ処理

行のキャッシュは、静的クラス(ViewHolder)として定義しています。

positionは行のインデックスにあたり、データであるListからgetItemで取得します。 またisEnabledで行がない場合は、何も表示されないので非表示。

レイアウト作成でイライラ

これはどうにかして欲しいかも。 ちょっと、画面を作る気にならないほど面倒くさいです。

と言う訳で、リスト表示に関してはコーディング的にもツール的にAppleの方が軍配が上がるかな?