365連休

にわかのandroidとかの開発メモ。

Android Studio 3.4でListViewおよびGridViewを使うサンプルコード

Androidでリスト形式のUIを使いたい場合、ListViewやGridViewが手ごろです。

 

しかし、データの表示には奇怪なAdapterクラスを使用しなければいけません。

 

個人的にAdapterはだいっっっっきらいですが、少しだけ打ち解けたのでサンプルコードを置いておきます。

 

機能

  • ActivityでListViewとGridViewを使用してデータを表示する。
  • Adapterの動きを理解するために、ダミーデータを表示する。

 

手順

  1. ActivityのレイアウトにListViewまたはGridViewを配置する(現在は非推奨なのでLegacyコンポーネント)
  2. BaseAdapterを継承し独自Adapterを定義する
  3. ActivityのonCreateでListViewまたはGridViewをfindViewByIdする
  4. setAdapterを行う
  5. 実行する

 

レイアウト(activity_main.xml)

今回はLinearLayoutで上半分にGridView、下半分にListViewを配置しました。

Legacyコンポーネントからマウスでぽちぽち配置した結果のコードです。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <GridView
            android:id="@+id/gridTest1"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:numColumns="4" />
        <ListView
            android:id="@+id/listTest1"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

 

 Adapterクラス(Activityの内部クラスとして)

アイテム項目としてのレイアウトは簡単にするためTextViewが一つだけです。そのため、レイアウトファイルは用いずコードからTextViewを生成します。

アイテム項目の生成要求となるgetViewではViewHolderパターンを使用します。
ViewHolderパターンとは何度もfindViewByを使用しないために、ViewのsetTagに必要なview参照を全て含めてしまおう、というテクニックのことらしいです。特殊な便利クラスを継承してるわけじゃなく、ただの構造体なので、名前は別にViewHolderじゃなくてもいいです。

他に、Adapterの動きを見やすくするため、サンプルコードでよくあるデータクラスを省いています。通常はAdapterのコンストラクタで配列またはリスト構造のデータを受け取り内部で保持するのが一般的です。

引数のpositionはindexの事です。0 <= position < getCount()

    /** 独自アダプター、ダミーデータ同梱 */
    private class MyTestAdapter extends BaseAdapter {

        /** ViewHolderパターン、View参照を保存するだけの構造体 */
        private class ViewHolder{
            TextView text;
        }

        /** データの総数を返す */
        @Override
        public int getCount() {
            return 15; //ダミーで15件
        }

        /** インデックスに紐づくデータを返す */
        @Override
        public Object getItem(int position) {
            return null; //今回は使わないためnull
        }

        /** インデックスに紐づくItemId(任意値)を返す */
        @Override
        public long getItemId(int position) {
            return position; //通常はAdapterに設定されたデータのid項目を返す
        }

        /**
         * ListViewやGridViewからのインデックスに対応するView構築要求<br/>
         * スクロールによってViewがフレームアウト<br/> ⇒ フレームインするViewとして再利用される<br/>
         * @param position インデックス
         * @param convertView 初回のみnull、再利用時は前回生成したViewが入っているためデータを書き換える
         * @param parent 親の参照、特に使わない
         * @return 常に有効なconvertViewを返す
         */
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            //以下のロジックは定形文

            ViewHolder vh; //ViewHolderパターン、単純な構造体クラス

            if(convertView==null) { //初回のView構築要求
                vh = new ViewHolder();
                vh.text = new TextView(Main3Activity.this); //Viewを構築
                convertView = vh.text; //ルートになるViewを設定
                convertView.setTag(vh); //ViewHolderパターンによってView参照を保存、Tagにはなんでも入る
            }else{ //前回生成したViewがある
                vh = (ViewHolder) convertView.getTag(); //ViewHolderパターンによってView参照を一発で取得
            }

            vh.text.setText(position + ":0123456789"); //ダミーデータ

            return convertView; //必ずconvertViewを返す
        }
    }

着目すべきはgetViewの振る舞いです。

getViewはListViewやGridViewからのアイテム生成要求になりますが、毎回Viewを生成するとコストがかさむため、再利用する仕組みになっています。

引数のconvertViewには、初回生成時はnullが入っていて、再利用時は過去に生成したいずれかのindexのViewが入っています。

 

そのため、

  1. 最初にconvertViewがnullかどうか判断し必要があればViewを生成し、
  2. その後indexに該当するデータを設定し、
  3. 最後にconvertViewを返すという流れになります。

 

これはおまじない級の定型文です。

 

ActivityのonCreate

極めてシンプルです。findViewByIdしてsetAdapterのみ。

ListViewやGridViewを使うのに必要なことは、Adapterの設計が全てといっても過言ではありません。

通常は表示したいデータの取得処理を行い、それをAdapterコンストラクタへ渡す処理を書きます。

面白いのはListViewとGridViewに同じAdapterを使うことができる点です。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //UI参照取得
        GridView gridTest1 = findViewById(R.id.gridTest1);
        ListView listTest1 = findViewById(R.id.listTest1);
        
        //Adaterによるデータ設定
        gridTest1.setAdapter(new MyTestAdapter());
        listTest1.setAdapter(new MyTestAdapter());

    }

 

実行結果

縦にスクロールが可能です。

f:id:neet_rookie:20190612112921p:plain

 

Activityの完全なソース

public class MainActivity extends AppCompatActivity {

    /** 独自アダプター、ダミーデータ同梱 */
    private class MyTestAdapter extends BaseAdapter {

        /** ViewHolderパターン、View参照を保存するだけの構造体 */
        private class ViewHolder{
            TextView text;
        }
        
        /** データの総数を返す */
        @Override
        public int getCount() {
            return 15; //ダミーで15件
        }

        /** インデックスに紐づくデータを返す */
        @Override
        public Object getItem(int position) {
            return null; //今回は使わないためnull
        }

        /** インデックスに紐づくItemId(任意値)を返す */
        @Override
        public long getItemId(int position) {
            return position; //通常はAdapterに設定されたデータのid項目を返す
        }

        /**
         * ListViewやGridViewからのインデックスに対応するView構築要求
* スクロールによってViewがフレームアウト
 ⇒ フレームインするViewとして再利用される
* @param position インデックス * @param convertView 初回のみnull、再利用時は前回生成したViewが入っているためデータを書き換える * @param parent 親の参照、特に使わない * @return 常に有効なconvertViewを返す */ @Override public View getView(int position, View convertView, ViewGroup parent) { //以下のロジックは定形文 ViewHolder vh; //ViewHolderパターン、単純な構造体クラス if(convertView==null) { //初回のView構築要求 vh = new ViewHolder(); vh.text = new TextView(Main3Activity.this); //Viewを構築 convertView = vh.text; //ルートになるViewを設定 convertView.setTag(vh); //ViewHolderパターンによってView参照を保存、Tagにはなんでも入る }else{ //前回生成したViewがある vh = (ViewHolder) convertView.getTag(); //ViewHolderパターンによってView参照を一発で取得 } vh.text.setText(position + ":0123456789"); //ダミーデータ return convertView; //必ずconvertViewを返す } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //UI参照取得 GridView gridTest1 = findViewById(R.id.gridTest1); ListView listTest1 = findViewById(R.id.listTest1); //Adaterによるデータ設定 gridTest1.setAdapter(new MyTestAdapter()); listTest1.setAdapter(new MyTestAdapter()); } }

ただのリストを表示するためにこんな複雑なコードが必要・・・

DataBindingなる技もあるみたいだけど、これ以上複雑にしたくないので使いたくないです。
クラスファイルやレイアウトファイルなど、どんどんファイルが増えていくのがとても気に入らない。

ListViewとGridViewの親密度は40です。

RecyclerViewの親密度はまだ0です。

いつか分かり合えるのかな?

 

 

 追記:斜めスクロール実現しました。

neet-rookie.hatenablog.com