今現在 Lolipop より前のバージョンで開発を行ってるとsetElevation
が反映されません。
フラットですが、随分とノッペリとしたデザインになってしまいますね。
View に影を付ける方法は、ここに記載されている様に 9-patch や shape を使う方法がありますが、中でもカスタムビュー内でonDraw
をオーバーライドする方法が一番手頃だと思います。
上記サイトでも紹介されている Shadow.java は自分の影の描画を行うのですが、描画順によっては折角描画した影が上書きされてしまいました。
なので、描画が後の View が前の View の影を描画する必要があります。
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 を利用している場合の例です。
<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>
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
内で描画しなければいけない場合があります。)
マテリアルデザインですが、やはり影が無くてはマテリアルデザインらしさが出ないのでしっかりと作りこんで行きたいですね。