Skip to content

ViewPathProvider

zed-alpha edited this page Feb 27, 2026 · 13 revisions

Package: com.zedalpha.shadowgadgets.view


The clip feature suffers from another limitation on Android R and above, on Views with irregular shapes; i.e., those that aren't rectangles, regular rounded rectangles, or circles. Reflection is required to get at the Path that describes those shapes, and the increasing restrictions on non-SDK interfaces have finally made that field inaccessible. To accommodate such cases, the library contains a ViewPathProvider interface that works very similarly to the framework's ViewOutlineProvider class, allowing the user to set the necessary Path.

fun interface ViewPathProvider {
    fun getPath(view: View, path: Path)
}

This is set on a target with another extension property, View.pathProvider. It's a good idea to set this before setting the outlineProvider, since the latter will cause the Outline to be invalidated, and it will end up trying to call the pathProvider immediately. However, if you're doing all of this setup before the target attaches to its Window, the order doesn't really matter.

As an example, this custom View shapes its Outline like a puzzle piece based on its size, and then uses that shape Path in the library's interface to set the input parameter:

@RequiresApi(30)
class PuzzlePieceView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : View(context, attrs) {

    private val viewPath = Path()

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)

    init {
        pathProvider = ViewPathProvider { _, path ->
            path.set(viewPath)
        }
        outlineProvider = object : ViewOutlineProvider() {
            override fun getOutline(view: View, outline: Outline) {
                val sideLength = minOf(view.width, view.height).toFloat()
                viewPath.setToPuzzlePiece(sideLength)
                outline.setPath(viewPath)
            }
        }
        clipOutlineShadow = true

        paint.color = Color.argb(64, 0, 0, 255)
        outlineAmbientShadowColor = Color.BLUE
        outlineSpotShadowColor = Color.BLUE
        elevation = 15F
    }

    override fun onDraw(canvas: Canvas) {
        canvas.drawPath(viewPath, paint)
    }
}

The setToPuzzlePiece() function is available in the demo module, if a full working example would be helpful. Give it a non-zero width and height, and increase the theme alphas a bit, and it'll produce something like:

A translucent blue View shaped like a puzzle piece, with a blue clipped shadow.

Do note that the ViewPathProvider is a fallback, not an override. It will only be checked if the library is unable to determine the Path on its own. If a non-empty Path cannot be resolved – with or without a ViewPathProvider – then a shadow simply won't be drawn.

Also included in the library is the MaterialShapeDrawableViewPathProvider class, a concrete implementation of this interface that will automatically handle figuring the Path on Views with a MaterialShapeDrawable background, which is how many modern library components get their overall shape and appearance.

shapedButton.pathProvider = MaterialShapeDrawableViewPathProvider()

The class's Companion has two helper members:

  • val canGetPath: Boolean, which simply indicates whether MaterialShapeDrawableViewPathProvider's reflection routine works at runtime.

  • fun findMaterialShapeDrawable(root: Drawable): MaterialShapeDrawable?, which is a convenience to help the user ensure at design time that the find routine will work with a particular Drawable.


Examples

The demo app's Irregular page has a demonstration of its use, as well as a more straightforward example of using ViewPathProvider.