ShapeableImageView:小而美的官方圖片顯示控制元件
作者:yechaoa
https://blog.csdn.net/yechaoa/article/details/117339632
簡介
ShapeableImageView
是 ImageView
的一個子類。
特點
在 不寫shape、不引入三方庫
的情況下,可實現較多場景下的圖片顯示效果,具體如下圖:

使用介紹
1. 引入Material包
implementation 'com.google.android.material:material:1.2.1'
2. 常用屬性
屬性 | 描述 |
---|---|
strokeWidth | 描邊寬度 |
strokeColor | 描邊顏色 |
shapeAppearance | 外觀樣式 |
shapeAppearanceOverlay | 同上,疊加層 |
3. 常規使用
和ImageView正常使用沒有區別
<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" />
常用效果
下面主要介紹的使用效果是:
-
圓角
-
圓
-
半圓
-
菱形
1. 圓角

<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" />
<!--ShapeableImageView 圓角--> <style name="RoundedStyle"> <item name="cornerFamily">rounded</item> <item name="cornerSize">10dp</item> </style>
-
沒有直接設定圓角的屬性,需要用到
app:shapeAppearance
,後面會說 -
cornerFamily 角的處理方式,rounded圓角,cut裁剪
-
cornerSize 圓角大小

<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" />
<!--ShapeableImageView 圓 --> <style name="CircleStyle"> <item name="cornerFamily">rounded</item> <item name="cornerSize">50%</item> </style>
-
圓角的大小可以用百分比,也可以自己計算,比如寬高100dp,圓角50dp
3. 半圓

<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" />
<!--ShapeableImageView 半圓 --> <style name="SemicircleStyle"> <item name="cornerFamily">rounded</item> <item name="cornerSizeTopLeft">50%</item> <item name="cornerSizeTopRight">50%</item> </style>
4. 菱形

<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" />
<!--ShapeableImageView 菱形 --> <style name="RhombusStyle"> <item name="cornerFamily">cut</item> <item name="cornerSize">50%</item> </style>
-
同樣,裁剪模式下圓角大小也可以計算
擴充套件使用
主要介紹:shapeAppearance、ShapeAppearanceModel、MaterialShapeDrawable
會涉及到原始碼,但是經過去繁從簡,看起來也非常輕鬆的。
1. ShapeAppearance
❝
Shape appearance overlay style reference for ShapeableImageView. ShapeableImageView的形狀外觀覆蓋樣式參考。
❞
前面可以看到我們設定圓角其實是用的 style
,那為什麼不直接用 attrs
呢,不是更加直觀方便嗎,帶著疑問來看看原始碼是怎麼處理的。
直接看 ShapeableImageView
的次構造方法:
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()); } } }
常規操作,獲取自定義屬性。
關鍵的兩行程式碼:
shapeAppearanceModel = ShapeAppearanceModel.builder(context, attrs, defStyle, DEF_STYLE_RES).build(); shadowDrawable = new MaterialShapeDrawable(shapeAppearanceModel);
也就是說我們給 shapeAppearance
設定的style,並不是 ShapeableImageView
自己來處理的,而是由 ShapeAppearanceModel
來構建的,然後又交給 MaterialShapeDrawable
來繪製的。
2. ShapeAppearanceModel
有點類似 Flutter
中的Decoration,可以構建出花裡胡哨的效果。
來看 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() {} ... } ... }
可以看到有各種邊和角的屬性,這裡注意兩個點:
-
MaterialShapeUtils.createDefaultCornerTreatment()
建立預設角的處理方式 -
MaterialShapeUtils.createDefaultEdgeTreatment()
建立預設邊的處理方式
也就意味著,邊和角除了預設,是可以自定義的,這就有極大的想象空間了, 比如這樣:
// 程式碼設定 角和邊 val shapeAppearanceModel2 = ShapeAppearanceModel.builder().apply { setAllCorners(RoundedCornerTreatment()) setAllCornerSizes(50f) setAllEdges(TriangleEdgeTreatment(50f, false)) }.build() val drawable2 = MaterialShapeDrawable(shapeAppearanceModel2).apply { setTint(ContextCompat.getColor([email protected], R.color.colorPrimary)) paintStyle = Paint.Style.FILL_AND_STROKE strokeWidth = 50f strokeColor = ContextCompat.getColorStateList([email protected], R.color.red) } mBinding.text2.setTextColor(Color.WHITE) mBinding.text2.background = drawable2
再比如這樣:
// 程式碼設定 聊天框效果 val shapeAppearanceModel3 = ShapeAppearanceModel.builder().apply { setAllCorners(RoundedCornerTreatment()) setAllCornerSizes(20f) setRightEdge(object : TriangleEdgeTreatment(20f, false) { // center 位置 , interpolation 角的大小 override fun getEdgePath(length: Float, center: Float, interpolation: Float, shapePath: ShapePath) { super.getEdgePath(length, 35f, interpolation, shapePath) } }) }.build() val drawable3 = MaterialShapeDrawable(shapeAppearanceModel3).apply { setTint(ContextCompat.getColor([email protected], R.color.colorPrimary)) paintStyle = Paint.Style.FILL } (mBinding.text3.parent as ViewGroup).clipChildren = false // 不限制子view在其範圍內 mBinding.text3.setTextColor(Color.WHITE) mBinding.text3.background = drawable3
3. MaterialShapeDrawable
用於設定背景、陰影等其他屬性。原始碼如下(有刪減):
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); } } ... } ... }
需要特別說明的是:
-
ShapeAppearanceModel Shapeable Chip MaterialButtom
-
而
MaterialShapeDrawable
其實就是Drawable
,是所有View都可以設定的。
至此,關於ShapeableImageView這個小而美的官方圖片顯示控制元件講解完畢。
為了防止失聯,歡迎關注我的小號
微信改了推送機制,真愛請星標本公號 :point_down:
- 說兩件事~
- 最新的動畫布局來了,一文帶你瞭解!
- Gradle:你必須掌握的開發常見技巧~
- Kotlin DSL 實戰:像 Compose 一樣寫程式碼!
- 厲害了,Android自定義樹狀圖控制元件來了!
- 一文帶你全面掌握Android元件化核心!
- 為什麼大廠開始全面轉向Compose?
- 谷歌限制俄羅斯使用Android系統,俄或將轉用 HarmonyOS!
- 鴻蒙OS、安卓、iOS測試對比,結果出乎意料!
- 最詳細的Android圖片壓縮攻略,讓你一次過足癮(建議收藏)
- Android字型漸變效果實戰!
- 攔截控制元件點選 - 巧用ASM處理防抖!
- Android正確的保活方案,拒絕陷入需求死迴圈!
- 再見 MMKV,自己擼一個FastKV,快的一批
- 白嫖一個Android專案的類圖生成工具!(建議收藏)
- 日常需求做的挺好,面試就被底層原理放倒
- 40歲開始學習Android開發,現在成了一名技術主管
- Android效能優化:全量編譯提速黑科技!
- 華為再次甩出“王炸”:鴻蒙終於“上車”
- 眼瞅著就要過年了,程式設計師們也都按奈不住了了