Material Components is Google’s official best practice for Material Deign. This library attempts to unify the look and use of Material Design UI Components across Android versions. Of course, these components are also unified across different platforms (there are versions of libraries for iOS, the Web and Flutter). The Material Components library also implements features introduced in the new Material Design specification.
The official documentation explains Material Components in great detail, and the address is shown below.
Github.com/material-co…
This time, we will talk about the processing of Shape in Material Components.
Shape
The MaterialShapeDrawable class provides a very useful toolset that can achieve very cool effects for our application. The MaterialShapeDrawable class lets you define a shape by specifying what the edges and corners of the final shape look like. These basic shape definitions can be controlled in addition using the interpolation float property to allow corner and edge animation.
To create a custom MaterialShapeDrawable, use the ShapeAppearanceModel constructor. ShapeAppearanceModel uses the EdgeTreatment and CornerTreatment classes to store information for each Edge and Corner of a shape (there are always 4 Edges and 4 Corners, although you can define almost any shape for them). You can set different processing for each Edge and Corner, or you can set the same processing for all edges and corners in one call.
There are also some pre-defined, off-the-shelf Edge and Corner processes in the ShapePathModel that already implement most of the shape effects described in the Material Design specification. There are already rounded corners (float RADIUS), cut corners (float size), or triangular edges (float Size, Boolean Inside), and they all work perfectly as you’d expect them to.
Regarding the processing of Shape, there are official detailed documents, the address is as follows.
Github.com/material-co…
Material Components, designed by Google, not only implements the development specifications of Android, but actually aligns the development paradigm with Flutter, the Web, and even iOS. IO /develop/and…
Easiest to use
ShapeAppearanceModel Builder function can be very convenient to control the Shape of the corner, the code is shown below.
val shapePathModel = ShapeAppearanceModel.builder() .setAllCorners(RoundedCornerTreatment()) .setAllCornerSizes(10.dp()) .build() val backgroundDrawable = MaterialShapeDrawable(shapePathModel).apply { setTint(Color.parseColor("#bebebe")) paintStyle = Paint.Style.FILL } test1.background = backgroundDrawableCopy the code
The MaterialShapeDrawable actually acts as a Drawable to create a background for a given Shape.
In addition to setting setAllCorners and setAllCornerSizes, you can also use the following method.
.setAllCorners(CornerFamily.ROUNDED, 8.dp())
Copy the code
The display is as shown.
Similarly, you can specify the Edge effect.
val shapePathModel = ShapeAppearanceModel.builder() .setAllCorners(RoundedCornerTreatment()) .setAllCornerSizes(10.dp()) .setAllEdges(TriangleEdgeTreatment(8.dp(), true)) .build() val backgroundDrawable = MaterialShapeDrawable(shapePathModel).apply { setTint(Color.parseColor("#bebebe")) paintStyle = Paint.Style.FILL_AND_STROKE strokeWidth = 2.dp() } test1.background = backgroundDrawableCopy the code
The display is as shown.
The MaterialShapeDrawable can also set stroke, as shown in the figure above.
TriangleEdgeTreatment(8.dp(), true) is used. The second isInside parameter is set to true. If it is set to false, You need to set the parent View’s clipChildren property to false.
(test1.parent as? ViewGroup)? .clipChildren = falseCopy the code
This is important because if it is a encapsulated custom View, it can usually be set in attachToWindow.
There are many different kinds of EdgeTreatment and Corner treatment built into the source code. These basic Edge and Corner treatments can meet most usage scenarios.
Customize CornerTreatment and EdgeTreatment
In addition to the basic Edge and Corner customized by the system, you can also customize the Edge and Corner styles. In fact, the code is very simple, is for a given ShapePath to carry out some tailoring, the following is a list of some processing Demo, I believe that you can understand how to deal with it.
class InnerCutCornerTreatment : CornerTreatment() {
override fun getCornerPath(shapePath: ShapePath, angle: Float, f: Float, size: Float) {
val radius = size * f
shapePath.reset(0f, radius, 180f, 180 - angle)
shapePath.lineTo(radius, radius)
shapePath.lineTo(radius, 0f)
}
}
class InnerRoundCornerTreatment : CornerTreatment() {
override fun getCornerPath(shapePath: ShapePath, angle: Float, f: Float, size: Float) {
val radius = size * f
shapePath.reset(0f, radius, 180f, 180 - angle)
shapePath.addArc(-radius, -radius, radius, radius, angle, -90f)
}
}
class ExtraRoundCornerTreatment : CornerTreatment() {
override fun getCornerPath(shapePath: ShapePath, angle: Float, f: Float, size: Float) {
val radius = size * f
shapePath.reset(0f, radius, 180f, 180 - angle)
shapePath.addArc(-radius, -radius, radius, radius, angle, 270f)
}
}
class ArgEdgeTreatment(val size: Float, val inside: Boolean) : EdgeTreatment() {
override fun getEdgePath(length: Float, center: Float, f: Float, shapePath: ShapePath) {
val radius = size * f
shapePath.lineTo(center - radius, 0f)
shapePath.addArc(
center - radius, -radius,
center + radius, radius,
180f,
if (inside) -180f else 180f
)
shapePath.lineTo(length, 0f)
}
}
class QuadEdgeTreatment(val size: Float) : EdgeTreatment() {
override fun getEdgePath(length: Float, center: Float, f: Float, shapePath: ShapePath) {
shapePath.quadToPoint(center, size * f, length, 0f)
}
}
Copy the code
For unilateral, single Angle processing
In addition to allXXX can be unified to set the four corners and edge properties, of course, also can specify a corner or edge, ShapeAppearanceModel Builder also provides the following methods to deal with single side and single corner.
With the help of unilateral, single Angle processing, you can complete some common style processing, for example, chat interface border bubble effect, code as shown below.
val shapePathModel = ShapeAppearanceModel.builder() .setAllCorners(RoundedCornerTreatment()) .setAllCornerSizes(16.dp()) .setRightEdge(object : TriangleEdgeTreatment(8.dp(), false) { override fun getEdgePath( length: Float, center: Float, interpolation: Float, shapePath: ShapePath ) { super.getEdgePath(length, 12.dp(), interpolation, shapePath) } }) .build() val backgroundDrawable = MaterialShapeDrawable(shapePathModel).apply { setTint(Color.parseColor("#bebebe")) paintStyle = Paint.Style.FILL } (test1.parent as? ViewGroup)? .clipChildren = false test1.background = backgroundDrawableCopy the code
The display effect is shown in the figure.
Shadow handling
Although MD provides setElevation to set the Elevation of View, domestic designers generally do not agree with this design concept, believing that the shadows set by Elevation are stiff and not natural enough. With the help of MaterialShapeDrawable, we can also deal with the shadows.
In general, there are several ways to deal with shadows.
- TranslationZ and Elevation, domestic designers do not like
- Use 9 patch shadow figure, can generally through inloop. Making. IO/shadow4andr… To create, and the View needs to leave shadow space
- Using setShadowLayer to draw is limited
For now, the mature one is to use the MaterialShapeDrawable to implement the shadow effect, as shown below.
val shapePathModel = ShapeAppearanceModel.builder() .setAllCorners(RoundedCornerTreatment()) .setAllCornerSizes(16.dp()) .build() val backgroundDrawable = MaterialShapeDrawable(shapePathModel).apply { setTint(Color.parseColor("#05bebebe")) paintStyle = Paint.Style.FILL shadowCompatibilityMode = MaterialShapeDrawable.SHADOW_COMPAT_MODE_ALWAYS initializeElevationOverlay(this@MainActivity) shadowRadius = 16.dp().toInt() setShadowColor(Color.parseColor("#D2D2D2")) shadowVerticalOffset = 2.dp().toInt() } (test1.parent as? ViewGroup)? .clipChildren = false test1.background = backgroundDrawableCopy the code
First of all, the shadow is outside the layout boundary, so the clipChildren attribute needs to be used. Meanwhile, the core of setting custom shadow is the shadowCompatibilityMode parameter. It has several enumerations (SHADOW_COMPAT_MODE_DEFAULT, SHADOW_COMPAT_MODE_NEVER, SHADOW_COMPAT_MODE_ALWAYS), SHADOW_COMPAT_MODE_DEFAULT indicates whether the mapping method of elevation or fake shadow is used.
SHADOW_COMPAT_MODE_ALWAYS needs to be set to SHADOW_COMPAT_MODE_ALWAYS, which means fake shadow is always used. Its parameters shadowRadius, setShadowColor and shadowVerticalOffset respectively represent the three parameters for drawing shadows, as shown in the figure.
To sum up, basically all Shape processing of Material Design can be realized through Material Components’ Material Shapedrawable. In the modern Android development, Google has been on the application layer of a lot of design, development methods to unify and comb, the use of these advanced development tools, can make our usual development more convenient.
For more information, please pay attention to my personal website xuyisheng.top/