CardViewとStaggered GridLayout Managerを使ってみる


今回は、Android 5 の新しい Widget「CardView」と、前にご紹介した RecyclerView で利用可能な Layout の1つ、「StaggeredGridLayoutManager」を使ってみましたので、ご紹介します。
前回の記事はコチラ
Material Design な Navigation Drawer を目指す(1)
Material Design な Navigation Drawer を目指す(2)

span1

RecyclerView のアイテムとして複数の CardView と、RecyclerView.LayoutManager には StaggeredGridLayoutManager を利用して、上図のような複数カラムのリスト表示を行います。

CardView の準備

まずは build.gradle の dependencies に以下のライブラリを追加します。

dependencies {
...
compile "com.android.support:cardview-v7:+"
}

今回のサンプルでは大・中・小の3種類の CardView を利用します。それぞれのレイアウトは、Components > Cardsの Card layout guidelines を参考に設定しています。

card

CardView は FrameLayout を継承したクラスで、異なるプラットフォーム間でも一貫した外観を表示することができます。

    <android.support.v7.widget.CardView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        card_view:cardBackgroundColor="#ffffff"
        android:elevation="2dp"
        card_view:cardCornerRadius="2dp">

        <!-- CardView に表示したいViewを配置 -->

    </android.support.v7.widget.CardView>

表示データXMLの準備

前回と同じような形式で、RecyclerView に表示するデータをXMLで定義しています。

    <!-- Cardの構造 menu -->
    <array name="card_list_data">
        <item>@array/card1</item>
        <item>@array/card2</item>
        <item>@array/card3</item>
        ...
    </array>

    <!-- Cardの要素種別 +type -->
    <array name="type_bg_card">
        <item name="key">type</item>
        <item name="value">bigCard</item>
    </array>
    <array name="type_md_card">
        <item name="key">type</item>
        <item name="value">middleCard</item>
    </array>
    <array name="type_sm_card">
        <item name="key">type</item>
        <item name="value">smallCard</item>
    </array>
    ...

    <!-- Cardの1要素内の構造 item -->
    <array name="card1">
        <item>@array/type_bg_card</item>
        <item>@array/card_image1</item>
        <item>@array/card_headline_text_1</item>
        <item>@array/card_body_text_1</item>
    </array>
    ...

    <!-- Cardの表示内容・設定 content -->
    <array name="card_image1">
        <item name="key">image</item>
        <item name="value">@drawable/photo</item>
    </array>
    <array name="card_headline_text_1">
        <item name="key">headlineTxt</item>
        <item name="value">headline 1</item>
    </array>
    <array name="card_body_text_1">
        <item name="key">bodyTxt</item>
        <item name="value">body 1</item>
    </array>
    ...

StaggeredGridLayoutManager の設定

StaggeredGridLayoutManager の第1引数 spanCount の値でカラムを設定し、第2引数で VERTICAL or HORIZONTAL を指定することができます。

 
StaggeredGridLayoutManager mLayoutManager = new StaggeredGridLayoutManager(
        spanCount,
         StaggeredGridLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(mLayoutManager);
 

Adapter の記述

表示するアイテムの種類によって処理を分岐するのは前回と同様なので省略しますが、
onBindViewHolder 内の処理で、大・中サイズの CardView の場合は、2カラム表示を無効にして全幅表示(setFullSpan)になるようにレイアウトパラメータを設定しています。

 
        // SmallサイズのCard以外は、全幅表示
        final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
        if (lp instanceof StaggeredGridLayoutManager.LayoutParams) {
            StaggeredGridLayoutManager.LayoutParams sglp = (StaggeredGridLayoutManager.LayoutParams) lp;
            sglp.setFullSpan(holder.getItemViewType() != TYPE_SM_CARD_ID);
            holder.itemView.setLayoutParams(sglp);
        }
 

加えて CardView 内のボタンのクリックイベントを検知できるように、以下のような独自リスナーを設定します。CardView の場合、1アイテム内に複数アクション(今回の例では、Done/Cancelボタン)を含む場合は、前回のタップ座標値からViewを割り出すよりは、今回のように直接監視対象のViewにリスナーを設定しておく方が適しているかと思います。

    // Done・Cancelボタンにタグ設定
    holder.mDoneBtn.setOnClickListener(this);
    holder.mDoneBtn.setTag(position);
    holder.mCancelBtn.setOnClickListener(this);
    holder.mCancelBtn.setTag(position);

    private OnItemClickListener mListener;

    public void setOnItemClickListener(OnItemClickListener listener) {
        mListener = listener;
    }

    public static interface OnItemClickListener {
        public void onItemClick(CardAdapter adapter, int position, HashMap<String, Object> item, View clickView);
    }

    @Override
    public void onClick(View view) {
        if (mRecyclerView == null) {
            return;
        }

        if (mListener != null) {
            int position = (Integer) view.getTag();
            HashMap<String, Object> item = mCardDataArr.get(position);
            mListener.onItemClick(this, position, item, view);
        }
    }

上記で用意したリスナーを使って、Activity にて監視&検知を行います。

        mCardAdapter.setOnItemClickListener(new CardAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(CardAdapter adapter, int position, HashMap<String, Object> item, View clickView) {
                // 独自実装したアイテムクリックの検知
                switch (clickView.getId()){
                    case R.id.doneBtn:
                        Toast.makeText(getActivity(), "DONE:" + item.toString(), Toast.LENGTH_LONG).show();
                        break;
                    case R.id.cancelBtn:
                        Toast.makeText(getActivity(), "CANCEL:" + item.toString(), Toast.LENGTH_LONG).show();
                        break;
                }
            }
        });

実行結果