Java
Android
AndroidStudio
android開発
MaterialDesign

[Android] ListView + Bottom Sheet でマテリアルな一覧画面を作ってみた

More than 1 year has passed since last update.

はじめに

昨今マテリアルデザインなるものが持て囃される風潮にありますね。
見た目がスッキリする事はとても大事ですが、見た目重視になりずぎて使い勝手はイマイチなんて事にならないよう願うばかりです。

個人的には最近のiOSがその一例な気がします...

そんな事はさておき、まずはマテリアルデザインがどんなものなのか知っておく必要はあると思うので、早速作ってみます!

material.gif

準備

今回必要なのはcom.android.support:designです。FabボタンにSVGを使うときはcom.android.support:support-vector-drawableも追加で入れるようにしましょう。またminSdkVersion21未満の場合はSupport Libraryを使う必要があるので、多少記述の仕方が変わります。気をつけましょう。

build.gradle
android {
  compileSdkVersion 26
  buildToolsVersion "26.0.1"
  defaultConfig {
    ...
    minSdkVersion 19
    targetSdkVersion 26
    vectorDrawables.useSupportLibrary = true  // 注意:SVGアイコンを使う時、minSdkVersionによってはこの記述が必要です(今回19なので必要)
  }
  buildTypes {
    ...
  }
}

dependencies {
  compile fileTree(include: ['*.jar'], dir: 'libs')
  compile 'com.android.support:appcompat-v7:26.1.0'
  compile 'com.android.support:support-v4:26.1.0'
  compile 'com.android.support:design:26.1.0'
  compile 'com.android.support:support-vector-drawable:26.1.0'
}

スクリーンショット 2017-09-23 22.09.48.png

また、今回SVGアイコンを使ってみたいという方は、[app]->[res]->[drawable]を右クリック、[New]->[Vector Asset]を選択し、「add」というマークのアイコンを選択すればic_add_black_24dp.xmlというファイルが作成されるかと思います。私は白色のアイコンが欲しかったので、作成されたxmlファイルにandroid:fillColor="@color/white"を追加し、ic_add_white_24dp.xmlという名前で保存しました。サンプルコードではそのファイルを参照しているので、ご自分の環境に合わせて適宜変更してください。

ic_add_white_24dp.xm
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportHeight="24.0"
        android:viewportWidth="24.0">

  <path
      android:fillColor="@color/white"
      android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

簡単に実装をしてみる

今回は「ListView」+「Bottom Sheet」を使って簡単な友達一覧・編集画面を作ってみました。
以下サンプルコードです。

MainActivity.java
public class MainActivity extends AppCompatActivity {
  private BottomSheetBehavior behavior;
  private ArrayList<String> friend;
  private ArrayAdapter<String> friend_adapter;
  private int position;  // ListView のどの行がクリックされたか記憶しておく

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

    // toolbar
    Toolbar toolbar = findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    // bottom sheet
    View bottomSheet = findViewById(R.id.bottom_sheet);
    behavior = BottomSheetBehavior.from(bottomSheet);
    behavior.setState(BottomSheetBehavior.STATE_HIDDEN);

    // friend data
    friend = new ArrayList<>();
    for (int i = 0; i < 30; i++) {
      friend.add("友人" + i);
    }

    friend_adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, friend);

    ListView friendList = findViewById(R.id.list_friend);
    friendList.setAdapter(friend_adapter);
    friendList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
        // friend list クリック時、Bottom Sheetを開く。すでに開いていれば閉じる。
        if (behavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
          behavior.setState(BottomSheetBehavior.STATE_HIDDEN);
        } else {
          behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
          position = i;
        }
      }
    });

    // fab button
    FloatingActionButton mFab = findViewById(R.id.fab_button);
    mFab.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View view) {
        // editText view
        final EditText input = new EditText(MainActivity.this);
        input.setInputType(InputType.TYPE_CLASS_TEXT);
        input.setMaxLines(1);

        // create dialog
        new AlertDialog.Builder(MainActivity.this)
                .setTitle("新規登録")
                .setMessage("登録名を入力してください")
                .setView(input)
                .setPositiveButton("登録", new DialogInterface.OnClickListener() { // OKボタンの設定
                  public void onClick(DialogInterface dialog, int whichButton) {
                    friend.add(input.getText().toString());
                    friend_adapter.notifyDataSetChanged();
                    dialog.dismiss();
                  }
                })
                .setNegativeButton("キャンセル", new DialogInterface.OnClickListener() { // キャンセルボタンの設定
                  public void onClick(DialogInterface dialog, int whichButton) {
                    dialog.dismiss();
                  }
                })
                .show();

        behavior.setState(BottomSheetBehavior.STATE_HIDDEN);
      }
    });

    // menu list
    ArrayAdapter<String> menu_adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
    menu_adapter.add("名前変更");
    menu_adapter.add("削除");
    menu_adapter.add("閉じる");

    ListView menuList = findViewById(R.id.list_menu);
    menuList.setAdapter(menu_adapter);
    menuList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> adapterView, View view, final int i, long l) {
        switch (i) {
          case 0:
            // editText view
            final EditText input = new EditText(MainActivity.this);
            input.setInputType(InputType.TYPE_CLASS_TEXT);
            input.setMaxLines(1);

            // create dialog
            new AlertDialog.Builder(MainActivity.this)
                    .setTitle("名前変更")
                    .setMessage(friend.get(position) + "さんの名前を変更します")
                    .setView(input)
                    .setPositiveButton("変更", new DialogInterface.OnClickListener() { // OKボタンの設定
                      public void onClick(DialogInterface dialog, int whichButton) {
                        friend.set(position, input.getText().toString());
                        friend_adapter.notifyDataSetChanged();
                        dialog.dismiss();
                      }
                    })
                    .setNegativeButton("キャンセル", new DialogInterface.OnClickListener() { // キャンセルボタンの設定
                      public void onClick(DialogInterface dialog, int whichButton) {
                        dialog.dismiss();
                      }
                    })
                    .show();
            break;
          case 1:
            // create dialog
            new AlertDialog.Builder(MainActivity.this)
                    .setTitle("削除")
                    .setMessage(friend.get(position) + "さんを本当に削除しますか?")
                    .setPositiveButton("削除", new DialogInterface.OnClickListener() { // OKボタンの設定
                      public void onClick(DialogInterface dialog, int whichButton) {
                        friend.remove(position);
                        friend_adapter.notifyDataSetChanged();
                        dialog.dismiss();
                      }
                    })
                    .setNegativeButton("キャンセル", new DialogInterface.OnClickListener() { // キャンセルボタンの設定
                      public void onClick(DialogInterface dialog, int whichButton) {
                        dialog.dismiss();
                      }
                    })
                    .show();
            break;
        }
        behavior.setState(BottomSheetBehavior.STATE_HIDDEN);
      }
    });
  }
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
                                                 xmlns:app="http://schemas.android.com/apk/res-auto"
                                                 android:layout_width="match_parent"
                                                 android:layout_height="match_parent">

  <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        app:theme="@style/ThemeOverlay.AppCompat.ActionBar"/>

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

  </LinearLayout>

  <LinearLayout
      android:id="@+id/bottom_sheet"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:background="@android:color/white"
      android:orientation="vertical"
      app:behavior_hideable="true"
      app:behavior_peekHeight="200dp"
      app:layout_behavior="@string/bottom_sheet_behavior">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="15dp"
        android:layout_marginTop="15dp"
        android:text="Menu"
        android:textSize="18sp"/>

    <ListView
        android:id="@+id/list_menu"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:divider="@color/white"/>

  </LinearLayout>

  <android.support.design.widget.FloatingActionButton
      android:id="@+id/fab_button"
      android:layout_width="56dp"
      android:layout_height="56dp"
      android:layout_margin="20dp"
      app:layout_anchor="@id/bottom_sheet"
      app:layout_anchorGravity="right|top"
      app:srcCompat="@drawable/ic_add_white_24dp"/>

</android.support.design.widget.CoordinatorLayout>

一旦このままコピペして動作させてみるのが良いと思います。
そこまで難しい記述はないので、ところどころ変更しながら手探りで理解を深めて行くのが近道です。