The effect
preface
Let’s take a lookShapeableImageView
What is theAs you can see from the figure aboveShapeableImageView
There’s nothing mysterious about it. It’s just, you knowImageView
It’s just a subclass of, but from the renderings, inNo shape, no tripartite library
It is easy to achieve the desired effect, and it scales well.
use
Introduce the material package
Implementation 'com. Google. Android. Material: material: 1.2.1'Copy the code
conventional
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:src="@mipmap/ic_avatar" />
Copy the code
- It’s no different than ImageView
Rounded corners
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:src="@mipmap/ic_avatar"
app:shapeAppearance="@style/RoundedStyle" />
Copy the code
<!--ShapeableImageView 圆角-->
<style name="RoundedStyle">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">10dp</item>
</style>
Copy the code
- The rounded corner property is not set directly, so it needs to be used
app:shapeAppearance
We’ll talk about it later - CornerFamily handles the corners, rounded corners, cut cuts
- CornerSize cornerSize
round
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:src="@mipmap/ic_avatar"
app:shapeAppearance="@style/CircleStyle" />
Copy the code
<! --ShapeableImageView Circle --> <style name="CircleStyle"> <item name="cornerFamily">rounded</item> <item name="cornerSize">50%</item> </style>Copy the code
- The size of the rounded corner can be used as a percentage or calculated by yourself, such as 100dp by width and 50dp by rounded corner
stroke
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="2dp"
android:src="@mipmap/ic_avatar"
app:shapeAppearance="@style/CircleStyle"
app:strokeColor="@color/red"
app:strokeWidth="4dp" />
Copy the code
- App :strokeColor strokeColor
- App :strokeWidth strokeWidth
- Notice that the padding is half the stroke width, and we’ll say
Corner cut
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="2dp"
android:src="@mipmap/ic_avatar"
app:shapeAppearance="@style/CutStyle"
app:strokeColor="@color/red"
app:strokeWidth="4dp" />
Copy the code
<! ShapeableImageView --> <style Name ="CutStyle"> <item name="cornerFamily"> Cut </item> <item name="cornerSize">10dp</item> </style>Copy the code
- CornerFamily: Cut The processing mode changes to clipping
The diamond
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="2dp"
android:src="@mipmap/ic_avatar"
app:shapeAppearance="@style/RhombusStyle"
app:strokeColor="@color/red"
app:strokeWidth="4dp" />
Copy the code
<! --ShapeableImageView diamond --> <style name="RhombusStyle"> <item name="cornerFamily"> Cut </item> <item name="cornerSize">50%</item> </style>Copy the code
- Similarly, the fillet size can be calculated in clipping mode
leaves
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="2dp"
android:src="@mipmap/ic_avatar"
app:shapeAppearance="@style/LeafStyle"
app:strokeColor="@color/red"
app:strokeWidth="4dp" />
Copy the code
<! --ShapeableImageView leaf --> <style name="LeafStyle"> <item name="cornerFamily">rounded</item> <item name="cornerSizeTopLeft">50%</item> <item name="cornerSizeBottomRight">50%</item> </style>Copy the code
- CornerSizeTopLeft Upper left rounded corner
- CornerSizeBottomRight rounded corner
- And so on, top left, bottom left, top right, bottom right, etc
semicircle
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="2dp"
android:src="@mipmap/ic_avatar"
app:shapeAppearance="@style/SemicircleStyle"
app:strokeColor="@color/red"
app:strokeWidth="4dp" />
Copy the code
<! --ShapeableImageView --> <style name="SemicircleStyle"> <item name="cornerFamily">rounded</item> <item name="cornerSizeTopLeft">50%</item> <item name="cornerSizeTopRight">50%</item> </style>Copy the code
hexagon
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_margin="10dp"
android:padding="2dp"
android:scaleType="centerCrop"
android:src="@mipmap/ic_avatar"
app:shapeAppearance="@style/HexagonStyle"
app:strokeColor="@color/red"
app:strokeWidth="4dp" />
Copy the code
<! < span style =" box-sizing: border-box; color: RGB (62, 62, 62); line-height: 20px; font-size: 16px! Important; word-break: inherit! Important; name="cornerSizeTopLeft">50%</item> <item name="cornerSizeTopRight">50%</item> <item name="cornerSizeBottomLeft">50%</item> <item name="cornerSizeBottomRight">50%</item> </style>Copy the code
Author: yechaoa
attribute
About XML attributes, I also did a collation, there are not many attributes, only 4
attribute | describe |
---|---|
strokeWidth | Stroke width |
strokeColor | Stroke color |
shapeAppearance | Presentation styles |
shapeAppearanceOverlay | Same as above, stacking layers |
extension
In front of the overall typesetting, buried a few foreshadows, here to answer one by one.
The source code will be involved, but it will be simplified and look very easy.
shapeAppearance
Shape Appearance Overlay style Reference for ShapeableImageView.
We can see that we set the rounded corner is actually using the style, so why not use attrs directly, is not more intuitive and convenient, take a look at the source code is how to deal with the question.
Look directly at the ShapeableImageView subconstructor:
public class ShapeableImageView extends AppCompatImageView implements Shapeable { ... public ShapeableImageView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(wrap(context, attrs, defStyle, DEF_STYLE_RES), attrs, defStyle); // Ensure we are using the correctly themed context rather than the context that was passed in. context = getContext(); clearPaint = new Paint(); clearPaint.setAntiAlias(true); clearPaint.setColor(Color.WHITE); clearPaint.setXfermode(new PorterDuffXfermode(Mode.DST_OUT)); destination = new RectF(); maskRect = new RectF(); maskPath = new Path(); TypedArray attributes = context.obtainStyledAttributes( attrs, R.styleable.ShapeableImageView, defStyle, DEF_STYLE_RES); strokeColor = MaterialResources.getColorStateList( context, attributes, R.styleable.ShapeableImageView_strokeColor); strokeWidth = attributes.getDimensionPixelSize(R.styleable.ShapeableImageView_strokeWidth, 0); borderPaint = new Paint(); borderPaint.setStyle(Style.STROKE); borderPaint.setAntiAlias(true); shapeAppearanceModel = ShapeAppearanceModel.builder(context, attrs, defStyle, DEF_STYLE_RES).build(); shadowDrawable = new MaterialShapeDrawable(shapeAppearanceModel); if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { setOutlineProvider(new OutlineProvider()); }}}Copy the code
General operations to get custom properties.
Two key lines of code:
shapeAppearanceModel = ShapeAppearanceModel.builder(context, attrs, defStyle, DEF_STYLE_RES).build();
shadowDrawable = new MaterialShapeDrawable(shapeAppearanceModel);
Copy the code
The Appearancemodel is the MaterialShapeDrawable and the appearancemodel is the MaterialShapeDrawable. The appearancemodel is the MaterialShapeDrawable and the Appearancemodel is the MaterialShapeDrawable. The MaterialShapeDrawable is the MaterialShapeAbleImageView.
ShapeAppearanceModel
This is a great class, a bit like Decoration in Flutter, to create a fancy effect.
ShapeAppearanceModel
public class ShapeAppearanceModel { /** Builder to create instances of {@link ShapeAppearanceModel}s. */ public static final class Builder { @NonNull private CornerTreatment topLeftCorner = MaterialShapeUtils.createDefaultCornerTreatment(); @NonNull private CornerTreatment topRightCorner = MaterialShapeUtils.createDefaultCornerTreatment(); @NonNull private CornerTreatment bottomRightCorner = MaterialShapeUtils.createDefaultCornerTreatment(); @NonNull private CornerTreatment bottomLeftCorner = MaterialShapeUtils.createDefaultCornerTreatment(); @NonNull private CornerSize topLeftCornerSize = new AbsoluteCornerSize(0); @NonNull private CornerSize topRightCornerSize = new AbsoluteCornerSize(0); @NonNull private CornerSize bottomRightCornerSize = new AbsoluteCornerSize(0); @NonNull private CornerSize bottomLeftCornerSize = new AbsoluteCornerSize(0); @NonNull private EdgeTreatment topEdge = MaterialShapeUtils.createDefaultEdgeTreatment(); @NonNull private EdgeTreatment rightEdge = MaterialShapeUtils.createDefaultEdgeTreatment(); @NonNull private EdgeTreatment bottomEdge = MaterialShapeUtils.createDefaultEdgeTreatment(); @NonNull private EdgeTreatment leftEdge = MaterialShapeUtils.createDefaultEdgeTreatment(); public Builder() {} ... }... }Copy the code
You can see that there are various edge and Angle properties. Note two points here:
MaterialShapeUtils.createDefaultCornerTreatment()
Create default corner processingMaterialShapeUtils.createDefaultEdgeTreatment()
Create default edge handling
This means that the edges and angles can be customized in addition to the default, which leaves a lot of room for imagination.
Like this:
/ / code setting Angle and edge val shapeAppearanceModel2 = ShapeAppearanceModel. Builder (). The apply {setAllCorners (RoundedCornerTreatment ()) setAllCornerSizes(50f) setAllEdges(TriangleEdgeTreatment(50f, false)) }.build() val drawable2 = MaterialShapeDrawable(shapeAppearanceModel2).apply { setTint(ContextCompat.getColor(this@ShapeableImageViewActivity, R.color.colorPrimary)) paintStyle = Paint.Style.FILL_AND_STROKE strokeWidth = 50f strokeColor = ContextCompat.getColorStateList(this@ShapeableImageViewActivity, R.color.red) } mBinding.text2.setTextColor(Color.WHITE) mBinding.text2.background = drawable2Copy the code
Or like this:
/ / code sets a chat effect val shapeAppearanceModel3 = ShapeAppearanceModel. Builder (). The apply {setAllCorners (RoundedCornerTreatment ()) setAllCornerSizes(20f) setRightEdge(object : TriangleEdgeTreatment(20f, false) {override fun getEdgePath(length: Float, center: Float) Float, interpolation: Float, shapePath: ShapePath) { super.getEdgePath(length, 35f, interpolation, shapePath) } }) }.build() val drawable3 = MaterialShapeDrawable(shapeAppearanceModel3).apply { setTint(ContextCompat.getColor(this@ShapeableImageViewActivity, R.color.colorPrimary)) paintStyle = Paint.Style.FILL } (mBinding.text3.parent as ViewGroup).clipChildren = false // Son does not restrict the view within the scope of its mBinding. Text3. SetTextColor (Color. WHITE) mBinding. Text3. Background = drawable3Copy the code
MaterialShapeDrawable
Source code (with deletions) :
public class MaterialShapeDrawable extends Drawable implements TintAwareDrawable, Shapeable { ... @Override public void draw(@NonNull Canvas canvas) { fillPaint.setColorFilter(tintFilter); final int prevAlpha = fillPaint.getAlpha(); fillPaint.setAlpha(modulateAlpha(prevAlpha, drawableState.alpha)); strokePaint.setColorFilter(strokeTintFilter); strokePaint.setStrokeWidth(drawableState.strokeWidth); final int prevStrokeAlpha = strokePaint.getAlpha(); strokePaint.setAlpha(modulateAlpha(prevStrokeAlpha, drawableState.alpha)); if (pathDirty) { calculateStrokePath(); calculatePath(getBoundsAsRectF(), path); pathDirty = false; } maybeDrawCompatShadow(canvas); if (hasFill()) { drawFillShape(canvas); } if (hasStroke()) { drawStrokeShape(canvas); }... static final class MaterialShapeDrawableState extends ConstantState { ... public MaterialShapeDrawableState(@NonNull MaterialShapeDrawableState orig) { shapeAppearanceModel = orig.shapeAppearanceModel; elevationOverlayProvider = orig.elevationOverlayProvider; strokeWidth = orig.strokeWidth; colorFilter = orig.colorFilter; fillColor = orig.fillColor; strokeColor = orig.strokeColor; tintMode = orig.tintMode; tintList = orig.tintList; alpha = orig.alpha; scale = orig.scale; shadowCompatOffset = orig.shadowCompatOffset; shadowCompatMode = orig.shadowCompatMode; useTintColorForShadow = orig.useTintColorForShadow; interpolation = orig.interpolation; parentAbsoluteElevation = orig.parentAbsoluteElevation; elevation = orig.elevation; translationZ = orig.translationZ; shadowCompatRadius = orig.shadowCompatRadius; shadowCompatRotation = orig.shadowCompatRotation; strokeTintList = orig.strokeTintList; paintStyle = orig.paintStyle; if (orig.padding ! = null) { padding = new Rect(orig.padding); }}... }... }Copy the code
Nothing special, just know that in addition to stroke, you can also set background, shadow and other properties.
instructions
ShapeAppearanceModel
It can only be implementedShapeable
Interface View can be set, for exampleChip
,MaterialButtom
And so on.- while
MaterialShapeDrawable
In fact, isDrawable
Is configurable for all views.
Stroke problem
Here’s a github imageAgain, this is a normal custom view, half of the brushes are outside the bounds, so you need to set thatpadding
forstrokeWidth
In the half.
Default rounded corners problem
If you’re careful, the first regular ShapeableImageView does have a little bit of rounded corners. Yes, it’s the default.
<style name="Widget.MaterialComponents.ShapeableImageView" parent="android:Widget"> <item name="strokeColor">@color/material_on_surface_stroke</item> <item name="shapeAppearance">? attr/shapeAppearanceMediumComponent</item> </style>Copy the code
The first is the color, is clearly not the, we are looking for to see shapeAppearanceMediumComponent
<attr format="reference" name="shapeAppearanceMediumComponent"/>
Copy the code
It’s just a simple property, and continue looking for associated references
<item name="shapeAppearanceMediumComponent">
@style/ShapeAppearance.MaterialComponents.MediumComponent
</item>
Copy the code
Refer to another style, and continue to see ShapeAppearance. MaterialComponents. MediumComponent this style
<style name="ShapeAppearance.MaterialComponents.MediumComponent">
<item name="cornerSize">@dimen/mtrl_shape_corner_size_medium_component</item>
</style>
Copy the code
Oh no, I see the cornerSize property that I’m familiar with, it’s a cornerSize property, it’s a cornerSize property, so let’s see what the value is
<dimen name="mtrl_shape_corner_size_medium_component">4dp</dimen>
Copy the code
The default 4 dp.
If you don’t want this rounded corner, you can learn to copy one of the source code, but also see above, a bit convoluted, rather than write a style to fix:
<! --ShapeableImageView --> <style name="Corner0Style"> <item name="cornerSize">0dp</item> </style>Copy the code
Then the reference
app:shapeAppearance="@style/Corner0Style"
Copy the code
Effect:
Ok, almost here, although there are a lot of relevant knowledge points did not mention, but also a lot of, it is better to try their own, slowly digest.
Github
Github.com/yechaoa/Mat…
Thank you
- ShapeableImageView official document
- ShapeAppearanceModel Official document
- Android Material components use detailed explanation
- Android Notes | ShapeableImageView
- Material Components — processing of Shape
The last
Writing is not easy, if you have a little help or inspiration, thank you for your support