DEV Community

Mynor Meza
Mynor Meza

Posted on

Simple custom shadow on Android

There might be some rare specific cases when elevation is not enough. In this post we are going to see how to create a custom shadow wrapper in a fast-easy way, using Drawables. At the end of this post you will be able to set a custom shadow for a single component such as an image, I will leave a link to the GitHub repo at the end.

The first thing we will need is a wrapper layout that will represent and eventually draw the shadow, sticking to an image example, we want to put the ImageView inside the wrapper layout like this:

<FrameLayout
    android:id="@+id/wrapper"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="30dp"
    android:padding="14dp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent">

    <ImageView
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:src="@drawable/test" />
</FrameLayout>
Enter fullscreen mode Exit fullscreen mode

Once this is set we can start working on the shadow itself, on the fragment or activity we’ll get a reference to the wrapper view and add the shadow using Kotlin code. The main class that provides the shadow is a ShapeDrawable, we are going to create an object of this class and later we will later set it as background for the wrapper view. This class has a function called setShadowLayer, this allows us to set blur, offsets and color of the shadow. The blur option is the one that controls how “big” or how much the shadow spreads around the child view, ideally this should be a bit smaller than the padding we set to the wrapper view so that the shadow does not cut off. So in this case we’ll take the padding and subtract 4dp to get the shadow’s blur value like this:

val shadowColorValue = ContextCompat.getColor(context, R.color.teal)
val shapeDrawable = ShapeDrawable()
shapeDrawable.setTint(shadowColorValue)

val shadowBlur = view.paddingBottom - 4.toDp(resources)

shapeDrawable.paint.setShadowLayer(
    shadowBlur.toFloat(), //blur
    0f, //dx
    0f, //dy
    getColorWithAlpha(shadowColorValue, 0.8f) //color
)
Enter fullscreen mode Exit fullscreen mode

Here we set a color with 80% opacity for the shadow, and set the same color but with no opacity to the shape itself using the setTint function. If we don’t set color for the shape to be the same as the shadow we might get a black color around the image when using dy or dx options.

If we want the shadow to have shape, we can use a RoundRectShape to mold the shadow, this RoundRectShape takes an array with 8 float values that represent the corners of a rect, each corner takes 2 values, you can play around with each corner values in case you want to get a particular shape. If you don’t want the shadow to have a shape or rounded corners you can omit this part.

val radius = 4.toDp(view.context.resources)
val outerRadius = floatArrayOf(
    radius, radius, //top-left
    radius, radius, //top-right
    radius, radius, //bottom-right
    radius, radius  //bottom-left
)
shapeDrawable.shape = RoundRectShape(outerRadius, null, null)
Enter fullscreen mode Exit fullscreen mode

At this point, if we set the shapeDrawable as background to the wrapper layout, the shadow won’t be visible, we need to add inset with the help of a LayerDrawable, ideally the inset will be the same as the padding of the wrapper view, this can’t be smaller than padding of the wrapper view, so that the shapeDrawable itself stays behind the ImageView and only the shadow is visible.

val drawable = LayerDrawable(arrayOf<Drawable>(shapeDrawable))
val inset = view.paddingBottom 
drawable.setLayerInset(
    0,
    inset,
    inset,
    inset,
    inset
)
view.background = drawable
Enter fullscreen mode Exit fullscreen mode

And this is the final result:

Shadow

If we’d like, we can set padding for the wrapper layout to the sides where we’d like the shadow to be visible, for example if we only want the shadow to be at the bottom and right, we could only set paddingBottom and paddingRight on the wrapper layout, but that is not the best solution, lets see a better option.

Using the offsets options in the setShadowLayer we can control placement of the shadow, but we’ll need to make a few adjustments like reducing the blur and making use of a BlurMaskFilter on the shapeDrawable. This is the code for it:

val shadowBlur = view.paddingBottom - 4.toDp(resources)
val offset = 4.toDp(resources)
shapeDrawable.paint.setShadowLayer(
    shadowBlur - offset, //blur
    offset, //dx
    offset, //dy
    getColorWithAlpha(shadowColorValue, 0.8f) //color
)
val filter = BlurMaskFilter(offset, BlurMaskFilter.Blur.OUTER)
view.setLayerType(View.LAYER_TYPE_SOFTWARE, shapeDrawable.paint)
shapeDrawable.paint.maskFilter = filter
Enter fullscreen mode Exit fullscreen mode

These adjustments are needed so that the shadow doesn’t get cut off and draws nicely below the image, all the rest of the code stays the same and the results look like this:

Shadow with offsets

Conclusion

This is one easy option to create a custom shadow, if you are in the need of a quick solution this can be of help, specially if you only need to apply it in one specific place/view. Although this example is not reusable and might not be the best solution for you, you can play around with it to see if it can suit your needs, there are also other options to create custom shadows that you can find browsing the web. You can explore and use the best for your case!

Source Code

Discussion (0)