Android中ShapeableImageView使用實例詳解(告別shape、三方庫)
效果

前言
先來看一下ShapeableImageView是什么

由上圖可以看到ShapeableImageView也沒有什么神秘的,不過是ImageView的一個子類而已,但是從效果圖來看,在不寫shape、不引入三方庫的情況下,還是挺容易實現(xiàn)預期效果的,而且擴展性良好。
使用
引入material包
implementation 'com.google.android.material:material:1.2.1'
常規(guī)

<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" />
和ImageView正常使用沒有區(qū)別
圓角

<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
描邊

<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" />
- app:strokeColor 描邊顏色
- app:strokeWidth 描邊寬度
- 注意這里padding的數(shù)值是描邊寬度的一半,后面會說
切角

<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" />
<!--ShapeableImageView 切角 -->
<style name="CutStyle">
<item name="cornerFamily">cut</item>
<item name="cornerSize">10dp</item>
</style>
cornerFamily:cut 處理模式變?yōu)椴眉?/p>
菱形

<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>
同樣,裁剪模式下圓角大小也可以計算
葉子

<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" />
<!--ShapeableImageView 葉子 -->
<style name="LeafStyle">
<item name="cornerFamily">rounded</item>
<item name="cornerSizeTopLeft">50%</item>
<item name="cornerSizeBottomRight">50%</item>
</style>
- cornerSizeTopLeft 左上圓角
- cornerSizeBottomRight 右下圓角
- 以此類推,左上、左下、右上、右下等
半圓

<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>
六邊形

<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" />
<!--ShapeableImageView 六邊形 -->
<style name="HexagonStyle">
<item name="cornerFamily">cut</item>
<item name="cornerSizeTopLeft">50%</item>
<item name="cornerSizeTopRight">50%</item>
<item name="cornerSizeBottomLeft">50%</item>
<item name="cornerSizeBottomRight">50%</item>
</style>
屬性
關于xml屬性,我也做了一個整理,屬性不多,只有4個
| 屬性 | 描述 |
|---|---|
| strokeWidth | 描邊寬度 |
| strokeColor | 描邊顏色 |
| shapeAppearance | 外觀樣式 |
| shapeAppearanceOverlay | 同上,疊加層 |
擴展
前面為了整體的排版,埋了幾個伏筆,下面來一一解答。
會涉及到源碼,但是經(jīng)過去繁從簡,看起來也非常輕松的。
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());
}
}
}
常規(guī)操作,獲取自定義屬性。
關鍵的兩行代碼:
shapeAppearanceModel = ShapeAppearanceModel.builder(context, attrs, defStyle, DEF_STYLE_RES).build();
shadowDrawable = new MaterialShapeDrawable(shapeAppearanceModel);
也就是說我們給shapeAppearance設置的style,并不是ShapeableImageView自己來處理的,而是由ShapeAppearanceModel來構建的,然后又交給MaterialShapeDrawable來繪制的。
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()創(chuàng)建默認角的處理方式MaterialShapeUtils.createDefaultEdgeTreatment()創(chuàng)建默認邊的處理方式
也就意味著,邊和角除了默認,是可以自定義的,這就有極大的想象空間了,
比如這樣:

// 代碼設置 角和邊
val shapeAppearanceModel2 = ShapeAppearanceModel.builder().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 = 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(this@ShapeableImageViewActivity, R.color.colorPrimary))
paintStyle = Paint.Style.FILL
}
(mBinding.text3.parent as ViewGroup).clipChildren = false // 不限制子view在其范圍內(nèi)
mBinding.text3.setTextColor(Color.WHITE)
mBinding.text3.background = drawable3
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只能是實現(xiàn)Shapeable接口的View才可以設置,比如Chip、MaterialButtom等。- 而
MaterialShapeDrawable其實就是Drawable,是所有View都可以設置的。
描邊問題
這里借github一張圖

又是自定義view的常規(guī)操作,有一半畫筆是在邊界外面的,所以需要設置padding為strokeWidth的一半。
默認圓角問題
有細心的同學會發(fā)現(xiàn)啊,第一個常規(guī)的ShapeableImageView還是有一點圓角的,沒錯,屬于默認的,跟蹤一下源碼來看一下:
<style name="Widget.MaterialComponents.ShapeableImageView" parent="android:Widget">
<item name="strokeColor">@color/material_on_surface_stroke</item>
<item name="shapeAppearance">?attr/shapeAppearanceMediumComponent</item>
</style>
第一個是顏色,很明顯不是我們要找的,繼續(xù)看shapeAppearanceMediumComponent
<attr format="reference" name="shapeAppearanceMediumComponent"/>
只是一個簡單的屬性,繼續(xù)查找關聯(lián)引用
<item name="shapeAppearanceMediumComponent">
@style/ShapeAppearance.MaterialComponents.MediumComponent
</item>
又引用了一個style,繼續(xù)看ShapeAppearance.MaterialComponents.MediumComponent這個style
<style name="ShapeAppearance.MaterialComponents.MediumComponent">
<item name="cornerSize">@dimen/mtrl_shape_corner_size_medium_component</item>
</style>
哦豁,看到了熟悉的屬性cornerSize,藏的還挺深,繼續(xù)看看數(shù)值是多少
<dimen name="mtrl_shape_corner_size_medium_component">4dp</dimen>
默認4dp。
那如果不想要這個圓角怎么辦呢,可以學習源碼仿寫一個,不過上面也看到了,有點繞,不如直接寫個style搞定:
<!--ShapeableImageView 去圓角-->
<style name="Corner0Style">
<item name="cornerSize">0dp</item>
</style>
然后引用
app:shapeAppearance="@style/Corner0Style"
效果:

ok,到這里就差不多了,雖然還有很多相關知識點沒有提及,但是也不少了,不如自己去嘗試一番,慢慢消化。
Github
https://github.com/yechaoa/MaterialDesign
感謝
- ShapeableImageView 官方文檔
- ShapeAppearanceModel 官方文檔
- Android Material組件使用詳解
- Android Notes|玩轉 ShapeableImageView
- Material Components——Shape的處理
最后
到此這篇關于Android中ShapeableImageView使用詳解的文章就介紹到這了,更多相關Android ShapeableImageView使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Android開發(fā)實現(xiàn)長按返回鍵彈出關機框功能
這篇文章主要介紹了Android開發(fā)實現(xiàn)長按返回鍵彈出關機框功能,涉及Android針對長按事件的響應與處理相關操作技巧,需要的朋友可以參考下2017-09-09
自定義視圖view使用Canvas實現(xiàn)手寫板和涂鴉功能
這篇文章主要介紹了自定義視圖view使用Canvas實現(xiàn)手寫板和涂鴉功能,這里直接上代碼,里面有詳細講解和注釋,需要的朋友可以參考下2023-04-04
Android應用開發(fā)中自定義ViewGroup的究極攻略
這里我們要演示的自定義ViewGroup中將實現(xiàn)多種方式排列和滑動等效果,并且涵蓋子View之間Touch Event的攔截與處理等問題,完全干貨,下面就為大家送上Android應用開發(fā)中自定義ViewGroup的究極實例攻略2016-05-05
Android AlertDialog自定義樣式實現(xiàn)代碼
這篇文章主要介紹了Android AlertDialog自定義樣式實現(xiàn)代碼的相關資料,這里提供了實例代碼,一個簡單示例,需要的朋友可以參考下2016-12-12
Android Studio使用教程(五):Gradle命令詳解和導入第三方包
這篇文章主要介紹了Android Studio使用教程(五):Gradle命令詳解和導入第三方包,本文講解了導入Android Studio、Gradle常用命令等內(nèi)容,需要的朋友可以參考下2015-05-05
Android Intent調(diào)用 Uri的方法總結
這篇文章主要介紹了Android Intent調(diào)用 Uri的方法總結的相關資料,這里整理了Android Intent 調(diào)用Uri的常用方法,需要的朋友可以參考下2017-09-09
Android ListView 滾動條的設置詳解及實例代碼
這篇文章主要介紹了 ListView等滾動條的設置詳解詳解及實例代碼的相關資料,需要的朋友可以參考下2017-02-02
Android Studio提示inotify大小不足的解決辦法
大家在使用Android Studio導入AOSP源碼的時候,可能會遇到inotify大小不足的問題,這篇文章就給大家介紹了怎么解決這個問題的方法,有需要的朋友們可以參考借鑒。2016-09-09

