LollipopでCardViewの影がつかなかったり余白が小さかったりした時の対処方法


Lollipop未満の端末で確認して大丈夫なのに、Lollipopだとpadding/marginが小さくなり、shadowもつかなくしまった時の対処方法です。

結論から言うと cardUseCompatPadding="true" をセットするだけです。

Before

上下の影がなく、CardView同士のmarginも少し小さいです。

<android.support.v7.widget.CardView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="8dp"
    android:layout_marginRight="8dp">

    <!-- 略 -->

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

After

上下の影がつき、CardView同士のmarginもいい感じになりました。

<android.support.v7.widget.CardView
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="8dp"
    android:layout_marginRight="8dp"
    app:cardUseCompatPadding="true">

    <!-- 略 -->

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

内部のコード

なぜこんなことが起こるかと言うと、Lollipop未満では elevation プロパティがないために影を別の方法でつけていて、実装が異なるからです。

CardViewの実装を見てみると、APIバージョンによって実装クラスを分けています。

static {
    if (Build.VERSION.SDK_INT >= 21) {
        IMPL = new CardViewApi21();
    } else if (Build.VERSION.SDK_INT >= 17) {
        IMPL = new CardViewJellybeanMr1();
    } else {
        IMPL = new CardViewEclairMr1();
    }
    IMPL.initStatic();
}

Lollipop未満は、基本的に CardViewEclairMr1 に実装があり、 RoundRectDrawableWithShadow というDrawableで影を表現しています。

@Override
public void initialize(CardViewDelegate cardView, Context context, int backgroundColor, float radius, float elevation, float maxElevation) {
    RoundRectDrawableWithShadow background = createBackground(context, backgroundColor, radius, elevation, maxElevation);
    background.setAddPaddingForCorners(cardView.getPreventCornerOverlap());
    cardView.setBackgroundDrawable(background);
    updatePadding(cardView);
}

Drawableで影をつけるということは、影がViewの内側で描画されるということなので、影の分だけ余白が増えます。そのため、Lollipopよりも少し余白が大きく見えます。

cardUseCompatPadding は、その余剰分のpaddingをLollipopにも適用して見た目を同じようにするためのプロパティです。

@Override
public void updatePadding(CardViewDelegate cardView) {
    if (!cardView.getUseCompatPadding()) {
        cardView.setShadowPadding(0, 0, 0, 0);
        return;
    }
    float elevation = getMaxElevation(cardView);
    final float radius = getRadius(cardView);
    int hPadding = (int) Math.ceil(RoundRectDrawableWithShadow
            .calculateHorizontalPadding(elevation, radius, cardView.getPreventCornerOverlap()));
    int vPadding = (int) Math.ceil(RoundRectDrawableWithShadow
            .calculateVerticalPadding(elevation, radius, cardView.getPreventCornerOverlap()));
    cardView.setShadowPadding(hPadding, vPadding, hPadding, vPadding);
}

このプロパティを有効にすることで、paddingがなくて影がつぶされたり余白が小さくなる問題を回避できるというわけです。