Lollipop elevation on concave outline Lollipop elevation on concave outline android android

Lollipop elevation on concave outline


As some of the comments and answer point out, native android shadow only works with convex outlines.

So you are left with either drawing a fake shadow manually on your own (via canvas, bitmap etc) or rely on someone else's library to draw fake shadow for you (Google's Material Components library etc).

Are there any known workarounds or any other creative solution that any of you know?

If you must rely on native android shadow, you can try to break down the shape into multiple convex shape and draw these individually.

Here is an example:

I broke down the star shape into 1 pentagon and 5 triangle polygons (all of them have convex outline) and draw them individually.

TriangleView:

public class TriangleView extends View {    private final Path path = new Path();    private final Paint paint = new Paint();    public TriangleView(Context context) {        super(context);        init(context, null, 0,0);    }    public TriangleView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        init(context, attrs, 0,0);    }    public TriangleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init(context, attrs, defStyleAttr, 0);    }    public TriangleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);        init(context, attrs, defStyleAttr, defStyleRes);    }    private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes){        paint.setStyle(Paint.Style.FILL);        paint.setColor(Color.argb(255, 100, 100, 255));        setOutlineProvider(new OutlineProvider());    }    public void setPoints(float x1, float y1, float x2, float y2, float x3, float y3){        path.reset();        path.moveTo(x1, y1);        path.lineTo(x2, y2);        path.lineTo(x3, y3);        path.close();        postInvalidate();    }    @Override    protected void onDraw(Canvas canvas) {        canvas.drawPath(path, paint);    }    private static class OutlineProvider extends ViewOutlineProvider{        @Override        public void getOutline(View view, Outline outline) {            Path path = ((TriangleView)view).path;            outline.setConvexPath(path);        }    }}

PentagonView:

public class PentagonView extends View {    private final Path path = new Path();    private final Paint paint = new Paint();    public PentagonView(Context context) {        super(context);        init(context, null, 0,0);    }    public PentagonView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        init(context, attrs, 0,0);    }    public PentagonView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init(context, attrs, defStyleAttr, 0);    }    public PentagonView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);        init(context, attrs, defStyleAttr, defStyleRes);    }    private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes){        paint.setStyle(Paint.Style.FILL);        paint.setColor(Color.argb(255, 150, 150, 255));        setOutlineProvider(new OutlineProvider());    }    public void setPoints(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float x5, float y5){        path.reset();        path.moveTo(x1, y1);        path.lineTo(x2, y2);        path.lineTo(x3, y3);        path.lineTo(x4, y4);        path.lineTo(x5, y5);        path.close();        postInvalidate();    }    @Override    protected void onDraw(Canvas canvas) {        canvas.drawPath(path, paint);    }    private static class OutlineProvider extends ViewOutlineProvider {        @Override        public void getOutline(View view, Outline outline) {            Path path = ((PentagonView)view).path;            outline.setConvexPath(path);        }    }}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context=".MainActivity">    <app.eccweizhi.concaveshadow.PentagonView        android:id="@+id/pentagonView"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:elevation="4dp" />    <app.eccweizhi.concaveshadow.TriangleView        android:id="@+id/triangle1"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:elevation="4dp" />    <app.eccweizhi.concaveshadow.TriangleView        android:id="@+id/triangle2"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:elevation="4dp" />    <app.eccweizhi.concaveshadow.TriangleView        android:id="@+id/triangle3"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:elevation="4dp" />    <app.eccweizhi.concaveshadow.TriangleView        android:id="@+id/triangle4"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:elevation="4dp" />    <app.eccweizhi.concaveshadow.TriangleView        android:id="@+id/triangle5"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:elevation="4dp" /></FrameLayout>

I then use them like this

class MainActivity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        pentagonView.setPoints(            520f,            520f,            640f,            520f,            677.0818f,            634.1266f,            580f,            704.6608f,            482.9182f,            634.1266f        )        triangle1.setPoints(520f, 520f, 640f, 520f, 580f, 400f)        triangle2.setPoints(640f, 520f, 677.0818f, 634.1266f, 777f, 520f)        triangle3.setPoints(677.0818f, 634.1266f, 580f, 704.6608f, 697f, 750f)        triangle4.setPoints(580f, 704.6608f, 482.9182f, 634.1266f, 440f, 750f)        triangle5.setPoints(482.9182f, 634.1266f, 520f, 520f, 400f, 520f)    }}


There is a new drawable called MaterialShapeDrawable in AndroidX package. Given a path, it can render shadow to both concave and convex shapes.

https://developer.android.com/reference/com/google/android/material/shape/MaterialShapeDrawable

This is how you would provide shadow to your concave shape WITHOUT MaterialShapeDrawable:

  • Create a new bitmap
  • Modify the bitmap ( draw star shape path on it using a new Canvas object )
  • Blur the bitmap so it actually looks like a shadow. ( Blurring should be done with RenderScript for performance reasons )
  • Draw the bitmap on views Canvas.