How to implement the Material-design Elevation for Pre-lollipop
Asked Answered
D

7

44

Google has shown some nice ways that elevation effect are shown on Lollipop here.

android:elevation="2dp"

for buttons,

android:stateListAnimator="@anim/button_state_list_animator"

How can I mimic the elevation effect on pre-Lollipop versions without 3rd party library?

Demythologize answered 11/6, 2015 at 10:24 Comment(5)
You are able to do that using the Custom Library in your App. You are able to find the examples in following link :github.com/wasabeef/awesome-android-uiZephyr
@RajanBhavsar can I implement without 3rd party library?Demythologize
Yes,arjunu.com/2015/02/materializing-your-apps-for-pre-lollipop folow this link to do that.Zephyr
you can try this : #26729070Grappling
possible duplicate of Android view shadowSide
T
82

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:

enter image description here

reference - https://mcmap.net/q/138928/-android-view-shadow

Thanks to user @Repo..

Update : If you want change color of this drawable try @Irfan answer below ↓

https://mcmap.net/q/138929/-how-to-implement-the-material-design-elevation-for-pre-lollipop

Tamathatamaulipas answered 19/6, 2015 at 6:51 Comment(2)
what if i want to use a button with blue backgroundEndear
how to customize value of elevation using this methodTawannatawdry
D
22

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) {

}
Dilettantism answered 11/6, 2015 at 12:34 Comment(0)
B
15

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)

Brisbane answered 22/6, 2015 at 9:22 Comment(0)
G
10

To bring dynamic, animated shadows to pre-Lollipop devices you have to:

  1. Draw a black shape of a view to a bitmap
  2. Blur that shape using elevation as a radius. You can do that using RenderScript. It's not exactly the method Lollipop's using, but gives good results and is easy to add to existing views.
  3. Draw that blurred shape beneath the view. Probably the best place is the 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.

enter image description here

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:

Shadow generation

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);
    }
}

Drawing a view with a shadow

@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);
}

Elevation API backported to pre-Lollipop

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();
}
Gangster answered 11/6, 2015 at 13:35 Comment(1)
The link for the Elevation API which points to the File Line is updated one day ago from today. So, Could you plz mention bit of code, So, developer have better idea of the actual code.Milwaukee
G
8

Create a 9-patch image with stretchable patches defined on an image with shadow around it.

enter image description here

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.

Govan answered 22/6, 2015 at 8:11 Comment(0)
S
6

to add @Ranjith Kumar answer

To add background color to the drawable (example button background color), we need to get drawable programatically.

first get the drawable

Drawable drawable = getResources().getDrawable(android.R.drawable.dialog_holo_light_frame);

set the color

drawable.setColorFilter(new PorterDuffColorFilter(getResources().getColor(R.color.color_primary), PorterDuff.Mode.MULTIPLY));

then set it to the view.

view.setBackgroundDrawable(drawable);

in case anyone searching.

Saragossa answered 26/11, 2016 at 7:15 Comment(0)
S
2

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"
Shayla answered 12/3, 2016 at 16:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.