The effect

preface

Let’s take a lookShapeableImageViewWhat is theAs you can see from the figure aboveShapeableImageViewThere’s nothing mysterious about it. It’s just, you knowImageViewIt’s just a subclass of, but from the renderings, inNo shape, no tripartite libraryIt 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 usedapp:shapeAppearanceWe’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 processing
  • MaterialShapeUtils.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

  • ShapeAppearanceModelIt can only be implementedShapeableInterface View can be set, for exampleChip,MaterialButtomAnd so on.
  • whileMaterialShapeDrawableIn fact, isDrawableIs 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 thatpaddingforstrokeWidthIn 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