-
Notifications
You must be signed in to change notification settings - Fork 5
ViewPathProvider
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:
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 whetherMaterialShapeDrawableViewPathProvider'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 particularDrawable.
The demo app's Irregular page has a demonstration of its use, as well as a more
straightforward example of using ViewPathProvider.