Lolipop より前のバージョンでカスタムビューに影をつける

今現在 Lolipop より前のバージョンで開発を行ってると``setElevation``が反映されません。
フラットですが、随分とノッペリとしたデザインになってしまいますね。

![noshadow_1.jpg](https://qiita-image-store.s3.amazonaws.com/0/1897/573c39ad-8630-b0f5-a90d-60c721771eaa.jpeg)


View に影を付ける方法は、[ここに記載されている](http://lexical.scopely.com/2014/08/15/backporting-android-shadows/)様に 9-patch や shape を使う方法がありますが、中でもカスタムビュー内で``onDraw``をオーバーライドする方法が一番手頃だと思います。

上記サイトでも紹介されている [Shadow.java](https://gist.github.com/PeterAttardo/cc722b7649d0e62274b2) は自分の影の描画を行うのですが、描画順によっては折角描画した影が上書きされてしまいました。

なので、描画が後の View が前の View の影を描画する必要があります。


```java:shadows.java
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.util.SparseArray;
import android.view.View;

import static android.graphics.drawable.GradientDrawable.Orientation;
import static android.graphics.drawable.GradientDrawable.Orientation.LEFT_RIGHT;
import static android.graphics.drawable.GradientDrawable.Orientation.TOP_BOTTOM;

/**
 * Improved PeterAttardo/Shadow.java
 * @see{https://gist.github.com/PeterAttardo/cc722b7649d0e62274b2}
 */
public class Shadows {

    private static final int START_COLOR = Color.parseColor("#55000000");

    private static final int END_COLOR = Color.parseColor("#00000000");

    private static final int SHADOW_LENGTH = (int) (5 * Resources.getSystem()
            .getDisplayMetrics().density);

    private static final Orientation[] shadowOrientations =
            new Orientation[]{TOP_BOTTOM, LEFT_RIGHT};

    private static int[] colors = new int[]{START_COLOR, END_COLOR};

    private static SparseArray linearGradients = new SparseArray() {{
        for (Orientation orientation : shadowOrientations) {
            put(orientation.ordinal(), new GradientDrawable(orientation, colors));
        }
    }};

    private static GradientDrawable radialGradient = new GradientDrawable() {{
        setGradientType(RADIAL_GRADIENT);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            setColors(colors);
        } else {
            setColor(END_COLOR);
        }
        setGradientRadius(SHADOW_LENGTH);
    }};


    static public void bindShadow(Canvas canvas, View view) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            return;
        }

        int height = view.getHeight();
        int width = view.getWidth();

        Rect bottomBounds = new Rect(SHADOW_LENGTH, height, width, height + SHADOW_LENGTH);
        drawShadow(canvas, TOP_BOTTOM, bottomBounds);

        Rect rightBounds = new Rect(width, SHADOW_LENGTH, width + SHADOW_LENGTH, height);
        drawShadow(canvas, LEFT_RIGHT, rightBounds);

        Rect cornerBLBounds = new Rect(0, height, SHADOW_LENGTH, height + SHADOW_LENGTH);
        radialGradient.setBounds(cornerBLBounds);
        radialGradient.setGradientCenter(1, 0);
        radialGradient.draw(canvas);

        Rect cornerBRBounds = new Rect(width, height, width + SHADOW_LENGTH,
                height + SHADOW_LENGTH);
        radialGradient.setBounds(cornerBRBounds);
        radialGradient.setGradientCenter(0, 0);
        radialGradient.draw(canvas);

        Rect cornerTRBounds = new Rect(width, 0, width + SHADOW_LENGTH, SHADOW_LENGTH);
        radialGradient.setBounds(cornerTRBounds);
        radialGradient.setGradientCenter(0, 1);
        radialGradient.draw(canvas);
    }

    static public void bindAboveShadow(Canvas canvas, View view) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            return;
        }

        int height = 0;
        int width = view.getWidth();

        Rect bottomBounds = new Rect(SHADOW_LENGTH, height, width, height + SHADOW_LENGTH);
        drawShadow(canvas, TOP_BOTTOM, bottomBounds);

        Rect rightBounds = new Rect(width, SHADOW_LENGTH, width + SHADOW_LENGTH, height);
        drawShadow(canvas, LEFT_RIGHT, rightBounds);

        Rect cornerBLBounds = new Rect(0, height, SHADOW_LENGTH, height + SHADOW_LENGTH);
        radialGradient.setBounds(cornerBLBounds);
        radialGradient.setGradientCenter(1, 0);
        radialGradient.draw(canvas);

        Rect cornerBRBounds = new Rect(width, height, width + SHADOW_LENGTH,
                height + SHADOW_LENGTH);
        radialGradient.setBounds(cornerBRBounds);
        radialGradient.setGradientCenter(0, 0);
        radialGradient.draw(canvas);

        Rect cornerTRBounds = new Rect(width, 0, width + SHADOW_LENGTH, SHADOW_LENGTH);
        radialGradient.setBounds(cornerTRBounds);
        radialGradient.setGradientCenter(0, 1);
        radialGradient.draw(canvas);
    }

    static private void drawShadow(Canvas canvas, Orientation orientation,
            Rect bounds) {
        GradientDrawable linearGradient = (GradientDrawable) linearGradients
                .get(orientation.ordinal());
        linearGradient.setBounds(bounds);
        linearGradient.draw(canvas);
    }
}
```

新しく定義した``bindAboveShadow``メソッドを利用して、``onDraw``内で描画を行います。
下記は Toolbar と PagerSlidingTabStrip、ViewPager を利用している場合の例です。

```xml
<AwesomeViewPagerView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:orientation="vertical">

    <include android:id="@+id/toolbar"
             layout="@layout/layout_toolbar"/>

    <!-- PagerSlidingTabStrip を継承して onDrow をオーバーライド -->
    <com.example.ShadowablePagerSlidingTabStrip
            android:id="@+id/tab_strip"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"/>

    <android.support.v4.view.ViewPager
            android:id="@+id/view_pager"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>


</AwesomeViewPagerView>
```

```com.example.ShadowablePagerSlidingTabStrip.java
public class ShadowablePagerSlidingTabStrip extends PagerSlidingTabStrip {

    public ShadowablePagerSlidingTabStrip(Context context) {
        super(context);
    }

    public ShadowablePagerSlidingTabStrip(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ShadowablePagerSlidingTabStrip(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // getChildAt(0) でタブ本体の View に設定をおこなう
        Shadows.bindAboveShadow(canvas, getChildAt(0));
    }
}
```

ViewPager にバインドした View も同様に PagerSlidingTabStrip の影を描画するようにすれば、しっかりと表示されます。
（``onDraw``内ではなくて``dispatchDraw``内で描画しなければいけない場合があります。）

![shadow_1.jpg](https://qiita-image-store.s3.amazonaws.com/0/1897/a228f043-4b6c-d4ca-5d42-2eb3e3991f5b.jpeg)


マテリアルデザインですが、やはり影が無くてはマテリアルデザインらしさが出ないのでしっかりと作りこんで行きたいですね。