LoginSignup
23
28

More than 5 years have passed since last update.

【Android】RecyclerViewのセルにポップアップメニューを実装

Last updated at Posted at 2016-03-24

はじめに

  • 動作確認したAPI Versionは21〜23
  • サポートライブラリ v7を使用

作るもの

Material Designのこれ

スクリーンショット 0028-03-24 12.05.22.png

実装方法

PopupWindow(API Level1から存在)を使うと簡単でした。
Design Support Libraryとかに目が行きがちですが、昔からあるクラスで今風に実装できることも多いです。

RecyclerViewを作る

RecyclerViewの基本的な作り方は以下を参照してください。
【Android】RecyclerViewの基本的な実装

まずはこんな感じでRecyclerAdapterを用意しましょう

MenuRecyclerAdapter.java
public class MenuRecyclerAdapter extends RecyclerView.Adapter<MenuRecyclerAdapter.ViewHolder> {

    /* ---------------------------------------------------------------------- */
    /* Field                                                                  */
    /* ---------------------------------------------------------------------- */

    // クラスタグ
    public static final String TAG = MenuRecyclerAdapter.class.getSimpleName();

    // 表示データのリスト
    private ArrayList<String> dataList;

    // コンテキスト
    private Context context;

    // RecyclerViewのアイテムクリックリスナー
    private OnRecyclerListener recyclerListener;

    /* ---------------------------------------------------------------------- */
    /* Listener                                                               */
    /* ---------------------------------------------------------------------- */

    /**
     * RecyclerViewのアイテムクリックリスナー
     */
    public interface OnRecyclerListener {
        void onItemClicked(int position, String data);
        void onMenuItemClicked(int position, String data, int menuId);
    }

    /* ---------------------------------------------------------------------- */
    /* Method                                                                 */
    /* ---------------------------------------------------------------------- */

    /**
     * コンストラクタ
     */
    public MenuRecyclerAdapter(Context context, ArrayList<String> data, OnRecyclerListener listener) {
        this.context = context;
        this.dataList = data;
        this.recyclerListener = listener;
    }

    /**
     * ViewHolderの生成
     */
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        // 表示するレイアウトを設定
        return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.list_item_recycler_menu, viewGroup, false), context, recyclerListener);
    }

    /**
     * ViewHolderへのデータセット
     */
    @Override
    public void onBindViewHolder(ViewHolder viewHolder, final int i) {
        // データセット
        viewHolder.position = i;
        viewHolder.data = dataList.get(i);

        // セルのクリック処理
        viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // セルの番号とデータを返す
                recyclerListener.onItemClicked(i, dataList.get(i));
            }
        });
    }

    /**
     * リスト数を返す
     */
    @Override
    public int getItemCount() {
        if (dataList != null) {
            return dataList.size();
        } else {
            return 0;
        }
    }

...

Adapterに特に変わった所はありません。
メニュー項目のクリックイベントを、RecyclerViewのクリックイベントリスナーについでに持たせてるくらいです。(onMenuItemClicked)

一応、Fragment側ではこんな感じで受け取る準備をしておきます。

RecyclerFragment.java
public class RecyclerFragment extends Fragment implements MenuRecyclerAdapter.OnRecyclerListener {

...

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // 適当にデータ作成
        ArrayList<String> array = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            array.add("");
        }

        mAdapter = new MenuRecyclerAdapter(mActivity, array, this);
        mRecyclerView.setAdapter(mAdapter);
    }

...

    /* ---------------------------------------------------------------------- */
    /* OnRecyclerListener                                                     */
    /* ---------------------------------------------------------------------- */
    @Override
    public void onItemClicked(int position, String data) {
        String message = String.format("Position:%d Data:%s", position, data);
        Toast.makeText(mActivity, message, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onMenuItemClicked(int position, String data, int menuId) {
        String message = String.format("Position:%d MenuItem:%d", position, menuId);
        Toast.makeText(mActivity, message, Toast.LENGTH_SHORT).show();
    }

...

}

とりあえず押された物の情報をトースト表示するようにしています。

RecyclerViewのセルのレイアウトを作成

セルはとりあえずこんな感じです。

list_item_recycler_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="160dp">
    <!-- セルに表示する画像(システムアイコン) -->
    <ImageView
        android:src="@android:drawable/sym_def_app_icon"
        android:background="#4CAF50"
        android:tint="#e91e63"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <!-- メニューを開くボタン(黒画像しか手元に無かったのでtintで白くしましたすみません) -->
    <ImageButton
        android:id="@+id/menu_button"
        android:tint="@color/white"
        android:src="@drawable/ic_more_vert_black_24dp"
        android:background="@drawable/selectable_button_default"
        android:layout_gravity="top|right"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        android:padding="8dp"/>

    <!-- 区切り線的なもの -->
    <View
        android:layout_height="1dp"
        android:layout_width="match_parent"
        android:background="@android:color/white"
        android:layout_gravity="bottom"/>
</FrameLayout>

そうするとこんな感じのキモいリストが作れます。

PopupWindow_SS_mini.png

ContextMenuのレイアウトを作成

表示したいメニューのレイアウトファイルを(今回は雰囲気で)作成します。
このレイアウトファイルの一番親でlayout_widthを設定しても、後で生成時にコード上で設定しないと無視されます。その為、基本的には一番親はwrap_contentです。

menu.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical" android:layout_width="wrap_content"
              android:layout_height="wrap_content"
    android:background="@android:color/transparent">
    <LinearLayout
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/menu_background"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:elevation="8dp"
        android:paddingTop="8dp"
        android:paddingBottom="8dp">

        <Button
            android:id="@+id/menu1"
            android:text="Item1"
            android:background="@drawable/selectable_button_default"
            android:paddingLeft="16dp"
            android:paddingRight="16dp"
            android:layout_width="match_parent"
            android:layout_height="56dp" />

        <Button
            android:id="@+id/menu2"
            android:text="Item2"
            android:background="@drawable/selectable_button_default"
            android:paddingLeft="16dp"
            android:paddingRight="16dp"
            android:layout_width="match_parent"
            android:layout_height="56dp" />

        <Button
            android:id="@+id/menu3"
            android:text="Item3"
            android:background="@drawable/selectable_button_default"
            android:paddingLeft="16dp"
            android:paddingRight="16dp"
            android:layout_width="match_parent"
            android:layout_height="56dp" />
    </LinearLayout>
</LinearLayout>

menu_backgroundとselectable_button_defaultは、ただの自作drawableです

menu_background.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="@android:color/white"/>
    <corners android:radius="2dp"/>
</shape>
selectable_button_default.xml
<?xml version="1.0" encoding="utf-8"?>
<ripple
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="#42000000">
    <item android:id="@android:id/mask" android:drawable="@android:color/white" />
</ripple>

RecyclerAdapterのViewHolderにPopupWindowの生成・表示を実装

ここでようやくPopupWindowの生成になります。
さっきのAdapterクラスのViewHolderで、PopupWindowの生成と表示を行います。

MenuRecyclerAdapter.ViewHolder
    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        // このセルの番号
        public int position;

        // このセルのデータ
        public String data = null;

        // メニューを表示する為のボタン(縦の・・・みたいなやつ)
        public ImageButton button = null;

        // 表示するメニュー(PopupWindow)のインスタンス
        public PopupWindow popupWindow = null;

        // Adapterから受け取ったメニューのクリックリスナー
        private OnRecyclerListener recyclerListener = null;

        // コンストラクタ
        public ViewHolder(View itemView, Context context, OnRecyclerListener recyclerListener) {
            super(itemView);
            this.recyclerListener = recyclerListener;

            // PopupWindowのインスタンスを生成
            popupWindow = new PopupWindow(context);

            // PopupWindowに表示するViewを生成
            View contentView = LayoutInflater.from(context).inflate(R.layout.menu, null);

            // クリックイベントのやりとりはお好みの方法で
            contentView.findViewById(R.id.menu1).setOnClickListener(this);
            contentView.findViewById(R.id.menu2).setOnClickListener(this);
            contentView.findViewById(R.id.menu3).setOnClickListener(this);

            // PopupWindowに表示するViewをセット
            popupWindow.setContentView(contentView);

            // PopupWindowに表示するViewのサイズを設定
            float width = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 140, context.getResources().getDisplayMetrics());
            popupWindow.setWindowLayoutMode((int) width, WindowManager.LayoutParams.WRAP_CONTENT);
            popupWindow.setWidth((int) width);

            // PopupWindowの外をタッチしたらPopupWindowが閉じるように設定
            popupWindow.setOutsideTouchable(true);

            // PopupWindow外のUIのタッチイベントが走らないようにフォーカスを持っておく
            popupWindow.setFocusable(true);

            // PopupWindow内のクリックを可能にしておく
            popupWindow.setTouchable(true);

            // レイアウトファイルで設定した背景のさらに背景(黒とか)が生成される為、ここで好みの背景を設定しておく
            popupWindow.setBackgroundDrawable(new ColorDrawable(context.getResources().getColor(android.R.color.transparent)));

            // メニューを表示するためのボタンを生成
            button = (ImageButton) itemView.findViewById(R.id.menu_button);

            // メニューを表示するためのクリックイベント設定
            button.setOnClickListener(this);
        }

        /**
         * クリックイベント
         */
        @Override
        public void onClick(View v) {
            if (v != null) {
                if (v.getId() == R.id.menu_button) {
                    // アンカーにするViewと、そのViewの左下から見た位置を指定してPopupWindowを表示する
                    popupWindow.showAsDropDown(v, v.getWidth(), -v.getHeight());
                } else {
                    // メニュー項目がクリックされたらFragmentに返して処理したりするのが多そう
                    recyclerListener.onMenuItemClicked(position, data, v.getId());
                }
            }
        }
    }

結果

SS_3.png

画面の半分を境に、自動的にアンカーから上向きか下向きの表示を切り替えてくれるのも便利です。
あとはデザインを調整して、理想のContextualMenuにしてみてください。

おわり

23
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
28