Google has shown some nice ways that elevation effect are shown on Lollipop here.
android:elevation=\"2dp\"
for buttons,
an
You can mimic the elevation on pre-Lollipop with a official method.
I achieve same effect using,
android:background="@android:drawable/dialog_holo_light_frame"
My tested output:
reference - https://stackoverflow.com/a/25683148/3879847
Thanks to user @Repo..
Update : If you want change color of this drawable try @Irfan answer below ↓
https://stackoverflow.com/a/40815944/3879847
u can easily simulate it by declaring a drawable like this -
shadow.xml
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
>
<gradient android:type="linear" android:angle="270" android:startColor="#b6b6b6" android:endColor="#ffffff"/>
</shape>
and use it int ur main xml like -
android:background="@drawable/shadow"
You can either hack it using a card-view:
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/btnGetStuff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
card_view:cardCornerRadius="4dp"
card_view:cardBackgroundColor="@color/accent"
>
<!-- you could also add image view here for icon etc. -->
<TextView
android:id="@+id/txtGetStuff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/textSize_small"
android:textColor="@color/primary_light"
android:freezesText="true"
android:text="Get Stuff"
android:maxWidth="120dp"
android:singleLine="true"
android:ellipsize="end"
android:maxLines="1"
/></android.support.v7.widget.CardView>
Or look at using this third party library: https://github.com/rey5137/Material (see wiki article on button https://github.com/rey5137/Material/wiki/Button)
To bring dynamic, animated shadows to pre-Lollipop devices you have to:
drawChild
method.
You also have to override setElevation
and setTranslationZ
, override child view drawing in layouts, turn off clip-to-padding and implement state animators.That's a lot of work, but it gives the best looking, dynamic shadows with response animations. I'm not sure why you'd like to achieve that without third party libraries. If you wish, you can analyze sources of Carbon and port the parts you'd like to have in your app:
private static void blurRenderScript(Bitmap bitmap, float radius) {
Allocation inAllocation = Allocation.createFromBitmap(renderScript, bitmap,
Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
Allocation outAllocation = Allocation.createTyped(renderScript, inAllocation.getType());
blurShader.setRadius(radius);
blurShader.setInput(inAllocation);
blurShader.forEach(outAllocation);
outAllocation.copyTo(bitmap);
}
public static Shadow generateShadow(View view, float elevation) {
if (!software && renderScript == null) {
try {
renderScript = RenderScript.create(view.getContext());
blurShader = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
} catch (RSRuntimeException ignore) {
software = true;
}
}
ShadowView shadowView = (ShadowView) view;
CornerView cornerView = (CornerView) view;
boolean isRect = shadowView.getShadowShape() == ShadowShape.RECT ||
shadowView.getShadowShape() == ShadowShape.ROUND_RECT && cornerView.getCornerRadius() < view.getContext().getResources().getDimension(R.dimen.carbon_1dip) * 2.5;
int e = (int) Math.ceil(elevation);
Bitmap bitmap;
if (isRect) {
bitmap = Bitmap.createBitmap(e * 4 + 1, e * 4 + 1, Bitmap.Config.ARGB_8888);
Canvas shadowCanvas = new Canvas(bitmap);
paint.setStyle(Paint.Style.FILL);
paint.setColor(0xff000000);
shadowCanvas.drawRect(e, e, e * 3 + 1, e * 3 + 1, paint);
blur(bitmap, elevation);
return new NinePatchShadow(bitmap, elevation);
} else {
bitmap = Bitmap.createBitmap((int) (view.getWidth() / SHADOW_SCALE + e * 2), (int) (view.getHeight() / SHADOW_SCALE + e * 2), Bitmap.Config.ARGB_8888);
Canvas shadowCanvas = new Canvas(bitmap);
paint.setStyle(Paint.Style.FILL);
paint.setColor(0xff000000);
if (shadowView.getShadowShape() == ShadowShape.ROUND_RECT) {
roundRect.set(e, e, (int) (view.getWidth() / SHADOW_SCALE - e), (int) (view.getHeight() / SHADOW_SCALE - e));
shadowCanvas.drawRoundRect(roundRect, e, e, paint);
} else {
int r = (int) (view.getWidth() / 2 / SHADOW_SCALE);
shadowCanvas.drawCircle(r + e, r + e, r, paint);
}
blur(bitmap, elevation);
return new Shadow(bitmap, elevation);
}
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (!child.isShown())
return super.drawChild(canvas, child, drawingTime);
if (!isInEditMode() && child instanceof ShadowView && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT_WATCH) {
ShadowView shadowView = (ShadowView) child;
Shadow shadow = shadowView.getShadow();
if (shadow != null) {
paint.setAlpha((int) (ShadowGenerator.ALPHA * ViewHelper.getAlpha(child)));
float childElevation = shadowView.getElevation() + shadowView.getTranslationZ();
float[] childLocation = new float[]{(child.getLeft() + child.getRight()) / 2, (child.getTop() + child.getBottom()) / 2};
Matrix matrix = carbon.internal.ViewHelper.getMatrix(child);
matrix.mapPoints(childLocation);
int[] location = new int[2];
getLocationOnScreen(location);
float x = childLocation[0] + location[0];
float y = childLocation[1] + location[1];
x -= getRootView().getWidth() / 2;
y += getRootView().getHeight() / 2; // looks nice
float length = (float) Math.sqrt(x * x + y * y);
int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(
x / length * childElevation / 2,
y / length * childElevation / 2);
canvas.translate(
child.getLeft(),
child.getTop());
canvas.concat(matrix);
canvas.scale(ShadowGenerator.SHADOW_SCALE, ShadowGenerator.SHADOW_SCALE);
shadow.draw(canvas, child, paint);
canvas.restoreToCount(saveCount);
}
}
if (child instanceof RippleView) {
RippleView rippleView = (RippleView) child;
RippleDrawable rippleDrawable = rippleView.getRippleDrawable();
if (rippleDrawable != null && rippleDrawable.getStyle() == RippleDrawable.Style.Borderless) {
int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(
child.getLeft(),
child.getTop());
rippleDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
return super.drawChild(canvas, child, drawingTime);
}
private float elevation = 0;
private float translationZ = 0;
private Shadow shadow;
@Override
public float getElevation() {
return elevation;
}
public synchronized void setElevation(float elevation) {
if (elevation == this.elevation)
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
super.setElevation(elevation);
this.elevation = elevation;
if (getParent() != null)
((View) getParent()).postInvalidate();
}
@Override
public float getTranslationZ() {
return translationZ;
}
public synchronized void setTranslationZ(float translationZ) {
if (translationZ == this.translationZ)
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
super.setTranslationZ(translationZ);
this.translationZ = translationZ;
if (getParent() != null)
((View) getParent()).postInvalidate();
}
@Override
public ShadowShape getShadowShape() {
if (cornerRadius == getWidth() / 2 && getWidth() == getHeight())
return ShadowShape.CIRCLE;
if (cornerRadius > 0)
return ShadowShape.ROUND_RECT;
return ShadowShape.RECT;
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
setTranslationZ(enabled ? 0 : -elevation);
}
@Override
public Shadow getShadow() {
float elevation = getElevation() + getTranslationZ();
if (elevation >= 0.01f && getWidth() > 0 && getHeight() > 0) {
if (shadow == null || shadow.elevation != elevation)
shadow = ShadowGenerator.generateShadow(this, elevation);
return shadow;
}
return null;
}
@Override
public void invalidateShadow() {
shadow = null;
if (getParent() != null && getParent() instanceof View)
((View) getParent()).postInvalidate();
}
Create a 9-patch image with stretchable patches defined on an image with shadow around it.
Add this 9-patch image as a background of your button with a padding so that the shadow is visible.
You can find some pre-defined 9-patch (.9.png) images here or here from where you can select, customize and copy to your project's drawable.
You can't mimic the elevation on pre-Lollipop with a official method.
You can use some drawables to make the shadow in your component. Google uses this way in CardView for example.
The ViewCompat.setElevation(View, int)
currently creates the shadow only on API21+. If you check the code behind, this method calls:
API 21+:
@Override
public void setElevation(View view, float elevation) {
ViewCompatLollipop.setElevation(view, elevation);
}
API < 21
@Override
public void setElevation(View view, float elevation) {
}