22時に寝ようと思って2時に寝る。

備忘録や日記を書いてます。きょうは早く寝よう。

ListViewを使ってみる

Android開発入門の勉強していて、どんどん新しい項目を学んでいくと、「わけわからん!」と理解が困難になるので項目ごとに頭の整理として備忘録を少しずつ書きまとめておこうと思います。

ListViewとは

Androidアプリ開発で設定画面等によく使用されるListView。配列やデータベースの情報を一覧表示できるので画面に項目を一覧表示するときなどに適している。

標準でスクロール機能が実装されているので、大量のデータを高速にスクロールできたりする優れもの。

ListViewは

  • ListView
  • ListAdapter

の2つのクラスから構成されている。

ListAdapterは、データの持ち主であり、そのデータをどのように表示するのか等の振る舞いを決める割と重要なクラスなのでListViewの実装はListAdapterに集約されているといっても良いくらい重要なもの。

ListViewを画面に設置する

activity_main.xmlを編集する。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical"
              tools:context=".MainActivity">

    <ListView
        android:id="@+id/myListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></ListView>

</LinearLayout>

ざっくりとこんな感じ。

ListViewのルールとして、"幅と高さ"は必ず match_parent とすること。

match_parent としないと、内容に合わせてリストのサイズを毎回設定することとなるので動作が遅くなる場合がある。とりあえず、ListViewの幅と高さはmatch_parentと覚えておこう。

ListViewは、activity_main.xmlへの記述だけでは使用できないので、新たに"ListViewの中の行"のレイアウトも作る必要がある。

新規レイアウトを[layout]フォルダを右クリック、[New]→[Layout resource file]と操作して適当な名前のレイアウト、今回は「list_item.xml」として作成した。

list_item.xmlを編集する。

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
          android:padding="16dp"
          android:layout_width="match_parent"
          android:layout_height="wrap_content">
</TextView>

幅は親要素いっぱいに広げ、高さは可変とする。

ListViewにデータを表示する

ListViewにデータを表示するためには、

  • データを準備
  • Adapterを作成(いくつか種類がある中で今回はArrayAdapterを使用)
  • ListViewに表示

実際に記述していく。

MainActivity.javaを編集する。

public class MainActivity extends AppCompatActivity {

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

        ListView myListView = (ListView) findViewById(R.id.myListView);

        // データを準備
        ArrayList<String> items = new ArrayList<>();
        for(int i = 0; i < 30; i++) {
            items.add("items-" + i);
        }

        // Adapter - ArrayAdapter
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(
                this,
                R.layout.list_item,
                items
        );

        // ListViewに表示
        myListView.setAdapter(adapter);
    }


}

データの準備は、ArrayListをString型で用意。

データの中身は、for文で item-0 から item-29 までを生成する。

Adapterは、ArrayAdapterを使用するため、「 ArrayAdapter adapter = new ArrayAdapter」という感じに生成。

引数として今回は、

ArrayAdapter<String> adapter = new ArrayAdapter<String>(
    this, // 第1引数:Context
    R.layout.list_item, // 第2引数:行のレイアウト指定
    items // 第3引数:データ
);

という感じに指定している。

ListViewの取得は、

「ListView myListView = (ListView) findViewById(R.id.myListView);」という風に、idを findViewByIdで取得し、ListView型にキャストして取得する。

最後に表示として、

「myListView.setAdapter(adapter);」として、adapterを渡して、ListViewにAdapterをセットする。

エミュレーターで表示を確認してみる

https://i.gyazo.com/b750b4c171dc798f04119828fdc8a7ec.gif

items-0 から items-29 まで表示されているのがわかる。

以上が、ListViewの基本的な表示方法となる。

表示するデータがない場合に「データがないよ」と表示する方法

データがなかった場合に別のビューを表示したいときがある。

activity_main.xml を編集する。

<TextView
        android:text="データがないよ"
        android:id="@+id/emptyView"
        android:padding="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

LinearLayout の中の ListView の下に追記する。

id は emptyView として、この id をsetEmptyView()メソッドに引数として渡してあげると、"データがない場合"にこのViewが表示されるようになる。

次にそのように動作するように、MainActivity.java を編集する。

public class MainActivity extends AppCompatActivity {

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

        ListView myListView = (ListView) findViewById(R.id.myListView);

        // データを準備
        ArrayList<String> items = new ArrayList<>();
        for(int i = 0; i < 30; i++) {
//            items.add("items-" + i);
        }

        // Adapter - ArrayAdapter
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(
                this,
                R.layout.list_item,
                items
        );

        // ListViewに表示
        myListView.setEmptyView(findViewById(R.id.emptyView));
        myListView.setAdapter(adapter);
    }

setEmptyView()メソッドに先ほど作った emptyViewを引数として渡したものをsetAdapterの前に追記した。また、データがない状態を再現するために、for文のデータを追加する部分をコメントアウトした。

実際に動作を確認してみる。

https://i.gyazo.com/ce54f465a7b8e6dba3920886c7bfab25.png

うまく データがない場合に emptyView を表示することができている。

左側にアイコン、右側に二行テキストを表示するいい感じのListViewレイアウトを作る方法

list_item.xml を編集する。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:orientation="horizontal"
              android:padding="16dp">

    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="16dp"
        android:src="@mipmap/ic_launcher"/>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="vertical">

        <TextView
            android:id="@+id/name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Azunobu"
            android:textSize="17sp"/>

        <TextView
            android:id="@+id/comment"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="僕の夏休みは9月から始まる予定です。"
            android:textSize="15sp"/>

    </LinearLayout>

</LinearLayout>

こんな感じにしてやるといい感じにレイアウトされる(適当)

アイコンは、AndroidStudioで既に用意されている ic_launcher を使用している。

LinearLayoutは全体として horizontal で左側にアイコン、右側にさらにLinearLayout(vertical)を設置し、この中身にTextViewを二つ(nameとcomment)設置している感じ。

Previewで見てみる。

https://i.gyazo.com/f9cdb802aac70881e847d2d16b855412.png

いい感じにレイアウトを作って表示できた。

Userクラスを作ってデータを準備する

ListViewにユーザ情報として複数ユーザのデータを表示したいので、Userクラスをまず作っていく。

list_item.xml を開いてさっき追記した中から

android:src="@mipmap/ic_launcher"
android:text="Azunobu"
android:text="僕の夏休みは9月から始まる予定です。"

これらはデータ表示確認用としていただけなので消しておく。

Userクラスを作っていく。

 public class User {
        private Bitmap icon;
        private String user;
        private String comment;

        public String getUser() {
            return user;
        }

        public void setUser(String user) {
            this.user = user;
        }

        public Bitmap getIcon() {
            return icon;
        }

        public void setIcon(Bitmap icon) {
            this.icon = icon;
        }
    }

これでUser情報を管理する。 メンバ変数にアクセスすためのゲッターとセッターはメンバ変数を用意したら、[Code]→[Generate]→[Getter and Setter]で一発で用意することができる。…こんなことできるんだと、感動した。

次に、Userデータの準備をする。

MainActivity.java を編集する。

public class MainActivity extends AppCompatActivity {

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

        ListView myListView = (ListView) findViewById(R.id.myListView);

        // データを準備
        ArrayList<User> users = new ArrayList<>();

        int[] icons = {
                R.mipmap.ic_launcher,
                R.mipmap.ic_launcher,
                R.mipmap.ic_launcher
        };

        String[] names = {
                "azunobu",
                "azuki",
                "kanon"
        };

        String[] comments = {
                "僕の夏休みは9月から始まる予定です。",
                "…。",
                "キャンキャン!!!ワンワン!!!"
        };

        for (int i = 0; i < icons.length; i++) {
            User user = new User();
            user.setIcon(BitmapFactory.decodeResource(
                    getResources(),
                    icons[i]
            ));
            user.setName(names[i]);
            user.setComment(comments[i]);
            users.add(user);
        }

        // Adapter - ArrayAdapter - UserAdapter
        UserAdapter adapter = new UserAdapter(this, 0, users);


        // ListViewに表示
        myListView.setEmptyView(findViewById(R.id.emptyView));
        myListView.setAdapter(adapter);
    }

    public class UserAdapter extends ArrayAdapter<User> {

        private LayoutInflater layoutInflater;
        public UserAdapter(Context c, int id, ArrayList<User> users) {
            super(c, id, users);
            this.layoutInflater = (LayoutInflater) c.getSystemService(
                    Context.LAYOUT_INFLATER_SERVICE
            );
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                convertView = layoutInflater.inflate(
                        R.layout.list_item,
                        parent,
                        false
                );
            }

            User user = (User) getItem(position);

            ((ImageView) convertView.findViewById(R.id.icon))
                    .setImageBitmap(user.getIcon());
            ((TextView) convertView.findViewById(R.id.name))
                    .setText(user.getName());
            ((TextView) convertView.findViewById(R.id.comment))
                    .setText(user.getComment());

            return convertView;
        }
    }

    public class User {
       略
    }


}

ArrayListをUser型で宣言して、その中に icons, names, commentsで三つずつデータを入れて、それをfor文で一気に様式にそって入れてあげている。

次に、Adapterを用意する。 ArrayAdapterを継承したUserAdapterというクラスを新たに作った。 LayoutInflaterの部分は、「こういうもの」らしいので、その通り書いた。View作る機械なんだと、そう理解した。 getViewはViewに一つずつユーザ情報を追加していくメソッド。 なんとなく見ると、流れがわかる…(適当)

ListViewは100行表示するものがあると、一度に100行用意する…わけではなく、最初に画面に表示できる範囲にいくつかのViewが用意されていて、スクロールされると上に行って見えなくなったViewが下にきてリサイクルされる仕組みになっている。

で、if (convertView == null) では、もし convertView が null だった場合はリサイクルするものがそもそもないので、 layoutInflator を使って View を作っていく必要がある。様式に沿って引数を投げてViewを生成している。一つ目の引数は元となるViewのxmlのidを、二つ目の以降はよくわからない。親をとって、falseで良い、っぽい(適当)

次に、convertView に User のデータを set してあげて、それを返してあげる。 何番目の user を持ってくるかについては、 getItem()メソッドを使って指定。あとは、コードの通りuserの各要素がいい感じに convertView に反映されていく。

エミュレータで動作を確認してみる。

https://i.gyazo.com/1cae0f93c960617b3f49be40a5346b80.png

いい感じにViewが表示できた。

きょうはここまで…次はリストをタップしたときの動作を実装してみます。

全然理解が適当なところがあるので、その辺も徐々に理解できるようにしたい。

参考

dotinstall.com