CardViewでEdge-to-edgeなカードを作るときに気をつけるポイント


# 概要
CardViewを上手く使うと、Pre-Lollipop端末にもElevation風な影をカードに付けることができるためUIを実装する際に非常に役に立ちます。
CardViewを使ってEdge-to-edge（Widthが画面の端から端まであるよう）なカードを実装した際に、Pre-Lollipop端末とLollipop端末の間で微妙にPaddingに差異が出る問題にハマったので、今回はそのハマった点と解決のポイントを紹介します。

# ハマったときの状態

```
<android.support.v7.widget.CardView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="16dp"
    card_view:cardBackgroundColor="@color/white"
    card_view:cardCornerRadius="0dp"
    card_view:cardElevation="2dp"
    card_view:contentPadding="16dp">
    ...
```

### Lollipop

![スクリーンショット 2015-09-07 18.47.38.png](https://qiita-image-store.s3.amazonaws.com/0/40539/2ecf6f4b-eff5-3612-4a5c-e40a5ea15bcc.png "スクリーンショット 2015-09-07 18.47.38.png")

### Pre-Lollipop

![スクリーンショット 2015-09-07 18.54.55.png](https://qiita-image-store.s3.amazonaws.com/0/40539/89b5752b-83a5-a45e-c2fa-828aad88b5fc.png "スクリーンショット 2015-09-07 18.54.55.png")

# ポイント
## cardUseCompatPaddingをtrueする
CardViewの ```cardUseCompatPadding``` のパラメータをtrueにすることで、Lollipop端末のCardViewのHorizontalPaddingに __Elevationのサイズ分__ の値が付与されて、VerticalPaddingに __Elevationのサイズ✕1.5__ の値が付与され、Pre-Lollipop端末のCardViewと同じ状態にすることができます。

```CardViewApi21.java
@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);
}
```

```RoundRectDrawableWithShadow.java
static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
        boolean addPaddingForCorners) {
    if (addPaddingForCorners) {
        return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
    } else {
        return maxShadowSize * SHADOW_MULTIPLIER;
    }   
}

static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,
        boolean addPaddingForCorners) {
    if (addPaddingForCorners) {
        return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);
    } else {
        return maxShadowSize;
    }   
}
```

## Elevationのサイズ分Marginを調整する
```cardUseCompatPadding``` をtrueにするだけだとすべての端末が、ハマったときのPre-Lollipopの状態になるだけなので、次は地道にElevationのサイズ分Marginを調整する必要があります。
若干イケてないやり方ですが...

```
<android.support.v7.widget.CardView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="-2dp"
    android:layout_marginRight="-2dp"
    android:layout_marginTop="13dp"
    card_view:cardBackgroundColor="@color/white"
    card_view:cardCornerRadius="0dp"
    card_view:cardElevation="2dp"
    card_view:cardUseCompatPadding="true"
    card_view:contentPadding="16dp">
```

具体的には ```layout_marginLeft``` と ```layout_marginRight``` に __Elevationサイズ分のネガティブマージン__ を設定して```layout_marginTop``` や ```layout_marginBottom``` から __Elevationサイズ✕1.5__ の値を引いてあげればOKです。

これで、バージョン別にレイアウトファイルを分けることなく、すべての端末でEdge-to-edgeなCardViewをキレイに作ることができます。

# 参考にしたページ
https://developer.android.com/reference/android/support/v7/widget/CardView.html
http://stackoverflow.com/questions/29068430/cardview-has-extra-margin-in-each-edge-on-pre-lollipop