前言
做過(guò)安卓開(kāi)發(fā)的都知道,安卓的UI開(kāi)發(fā)耗時(shí)耗力顾瞪,實(shí)現(xiàn)不規(guī)則圖片效果舔庶,如老生常談的圓角抛蚁、圓形圖片,要么引入第三方控件惕橙,要么自定義ImageView瞧甩,第三方控件不一定滿足,而自定義ImageView對(duì)開(kāi)發(fā)者有一定的要求且花時(shí)間弥鹦。Google在去年發(fā)布的Android Material 組件 (MDC-Android) 1.2.0肚逸,提供了豐富的控件,有助于提高UI開(kāi)發(fā)效率彬坏,今天的主角ShapeableImageView正式其中一員吼虎,類(lèi)似的還有MaterialButton。
看下效果:
先來(lái)看下ShapeableImageView是什么
從類(lèi)繼承關(guān)系看出苍鲜,ShapeableImageView只不過(guò)是ImageView的一個(gè)子類(lèi),但是可以輕松實(shí)現(xiàn)效果圖中的各種樣式玷犹。
xml屬性
屬性名 | 作用 |
---|---|
shapeAppearance | 形狀外觀樣式混滔,引用 style 樣式 |
shapeAppearanceOverlay | 外觀疊加樣式,引用 style 樣式 |
strokeWidth | 描邊寬度 |
strokeColor | 描邊顏色 |
使用
引入material包
implementation 'com.google.android.material:material:1.2.1'
常規(guī)使用
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image"
android:layout_width="110dp"
android:layout_height="110dp"
android:padding="1dp"
android:src="@drawable/head"
/>
跟ImageView效果一樣歹颓。
各種花俏樣式
1坯屿、圓角圖片
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image1"
android:layout_width="110dp"
android:layout_height="110dp"
android:padding="1dp"
android:src="@drawable/head"
app:shapeAppearance="@style/roundedCornerStyle"
app:strokeColor="@android:color/holo_blue_bright"
app:strokeWidth="2dp"/>
對(duì)應(yīng)的style:
<!-- 圓角圖片 -->
<style name="roundedCornerStyle">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">8dp</item>
</style>
2、圓形圖片
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image2"
android:layout_width="110dp"
android:layout_height="110dp"
android:padding="1dp"
android:src="@drawable/head"
app:shapeAppearance="@style/circleStyle"
app:strokeColor="@android:color/holo_blue_bright"
app:strokeWidth="2dp"/>
對(duì)應(yīng)的style:
<!-- 圓形圖片 -->
<style name="circleStyle">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">50%</item>
</style>
3巍扛、切角圖片
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image3"
android:layout_width="110dp"
android:layout_height="110dp"
android:padding="1dp"
android:src="@drawable/head"
app:shapeAppearance="@style/cutCornerStyle"
app:strokeColor="@android:color/holo_blue_bright"
app:strokeWidth="2dp"/>
對(duì)應(yīng)的style:
<!-- 切角圖片 -->
<style name="cutCornerStyle">
<item name="cornerFamily">cut</item>
<item name="cornerSize">12dp</item>
</style>
4领跛、菱形圖片
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image4"
android:layout_width="110dp"
android:layout_height="110dp"
android:padding="1dp"
android:src="@drawable/head"
app:shapeAppearance="@style/diamondStyle"
app:strokeColor="@android:color/holo_blue_bright"
app:strokeWidth="2dp"/>
對(duì)應(yīng)的style:
<!-- 菱形圖片 -->
<style name="diamondStyle">
<item name="cornerFamily">cut</item>
<item name="cornerSize">50%</item>
</style>
5、右上角圓角圖片
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image5"
android:layout_width="110dp"
android:layout_height="110dp"
android:padding="1dp"
android:src="@drawable/head"
app:shapeAppearance="@style/topRightCornerStyle"
app:strokeColor="@android:color/holo_blue_bright"
app:strokeWidth="2dp"/>
對(duì)應(yīng)的style:
<!-- 右上角圓角圖片 -->
<style name="topRightCornerStyle">
<item name="cornerFamilyTopRight">rounded</item>
<item name="cornerSizeTopRight">50dp</item>
</style>
6撤奸、小雞蛋圖片
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image6"
android:layout_width="110dp"
android:layout_height="110dp"
android:padding="1dp"
android:src="@drawable/head"
app:shapeAppearance="@style/eggStyle"
app:strokeColor="@android:color/holo_blue_bright"
app:strokeWidth="2dp"/>
對(duì)應(yīng)的style:
<!-- 小雞蛋圖片 -->
<style name="eggStyle">
<item name="cornerFamilyTopRight">rounded</item>
<item name="cornerSizeTopRight">50dp</item>
<item name="cornerSizeTopLeft">50dp</item>
<item name="cornerFamilyTopLeft">rounded</item>
</style>
7吠昭、組合弧度圖片效果
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image7"
android:layout_width="110dp"
android:layout_height="110dp"
android:padding="1dp"
android:src="@drawable/head"
app:shapeAppearance="@style/comCornerStyle"
app:strokeColor="@android:color/holo_blue_bright"
app:strokeWidth="2dp"/>
對(duì)應(yīng)的style:
<!-- 組合弧度圖片效果 -->
<style name="comCornerStyle">
<item name="cornerFamily">rounded</item>
<item name="cornerSizeTopRight">50%</item>
<item name="cornerSizeBottomLeft">50%</item>
</style>
8、 小 Tips
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image8"
android:layout_width="110dp"
android:layout_height="50dp"
android:padding="1dp"
android:src="@drawable/head"
app:shapeAppearance="@style/tipsCornerStyle"
app:strokeColor="@android:color/holo_blue_bright"
app:strokeWidth="2dp"/>
對(duì)應(yīng)的style:
<!-- 小 Tips -->
<style name="tipsCornerStyle">
<item name="cornerFamilyTopLeft">rounded</item>
<item name="cornerSizeTopLeft">50%</item>
<item name="cornerFamilyBottomLeft">rounded</item>
<item name="cornerSizeBottomLeft">50%</item>
<item name="cornerFamilyTopRight">cut</item>
<item name="cornerSizeTopRight">50%</item>
<item name="cornerFamilyBottomRight">cut</item>
<item name="cornerSizeBottomRight">50%</item>
</style>
9胧瓜、扇形圖片
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image9"
android:layout_width="110dp"
android:layout_height="110dp"
android:padding="1dp"
android:src="@drawable/head"
app:shapeAppearance="@style/fanStyle"
app:strokeColor="@android:color/holo_blue_bright"
app:strokeWidth="2dp"/>
對(duì)應(yīng)的style:
<!-- 扇形 -->
<style name="fanStyle">
<item name="cornerFamilyBottomLeft">rounded</item>
<item name="cornerFamilyBottomRight">rounded</item>
<item name="cornerFamilyTopLeft">rounded</item>
<item name="cornerFamilyTopRight">rounded</item>
<item name="cornerSizeBottomLeft">0dp</item>
<item name="cornerSizeBottomRight">0dp</item>
<item name="cornerSizeTopLeft">0%</item>
<item name="cornerSizeTopRight">100%</item>
</style>
通過(guò)源碼學(xué)知識(shí)
從前面應(yīng)用可以發(fā)現(xiàn)矢棚,通過(guò)定義ShapeableImageView的shapeAppearance屬性style值,可以實(shí)現(xiàn)各種不同的樣式府喳,而style有哪些的屬性蒲肋,分別表示什么,定義了這些style實(shí)現(xiàn)這些樣式效果的原理是什么钝满,帶著這些疑問(wèn)閱讀源碼兜粘。
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());
}
}
在構(gòu)造方法中有兩行核心代碼:
shapeAppearanceModel =
ShapeAppearanceModel.builder(context, attrs, defStyle, DEF_STYLE_RES).build();
shadowDrawable = new MaterialShapeDrawable(shapeAppearanceModel);
可以看出,style的屬性由ShapeAppearanceModel來(lái)管理弯蚜,得到style屬性后構(gòu)造MaterialShapeDrawable對(duì)象孔轴,由MaterialShapeDrawable繪制形狀。
設(shè)置邊和角的屬性
通過(guò) R 文件可以查看當(dāng)前 ShapeAppearanceModel 具有的屬性:
<declare-styleable name="ShapeAppearance">
<!-- Corner size to be used in the ShapeAppearance. All corners default to this value -->
<attr format="dimension|fraction" name="cornerSize"/>
<!-- Top left corner size to be used in the ShapeAppearance. -->
<attr format="dimension|fraction" name="cornerSizeTopLeft"/>
<!-- Top right corner size to be used in the ShapeAppearance. -->
<attr format="dimension|fraction" name="cornerSizeTopRight"/>
<!-- Bottom right corner size to be used in the ShapeAppearance. -->
<attr format="dimension|fraction" name="cornerSizeBottomRight"/>
<!-- Bottom left corner size to be used in the ShapeAppearance. -->
<attr format="dimension|fraction" name="cornerSizeBottomLeft"/>
<!-- Corner family to be used in the ShapeAppearance. All corners default to this value -->
<attr format="enum" name="cornerFamily">
<enum name="rounded" value="0"/>
<enum name="cut" value="1"/>
</attr>
<!-- Top left corner family to be used in the ShapeAppearance. -->
<attr format="enum" name="cornerFamilyTopLeft">
<enum name="rounded" value="0"/>
<enum name="cut" value="1"/>
</attr>
<!-- Top right corner family to be used in the ShapeAppearance. -->
<attr format="enum" name="cornerFamilyTopRight">
<enum name="rounded" value="0"/>
<enum name="cut" value="1"/>
</attr>
<!-- Bottom right corner family to be used in the ShapeAppearance. -->
<attr format="enum" name="cornerFamilyBottomRight">
<enum name="rounded" value="0"/>
<enum name="cut" value="1"/>
</attr>
<!-- Bottom left corner family to be used in the ShapeAppearance. -->
<attr format="enum" name="cornerFamilyBottomLeft">
<enum name="rounded" value="0"/>
<enum name="cut" value="1"/>
</attr>
</declare-styleable>
<declare-styleable name="ShapeableImageView">
<attr name="strokeWidth"/>
<attr name="strokeColor"/>
<!-- Shape appearance style reference for ShapeableImageView. Attribute declaration is in the
shape package. -->
<attr name="shapeAppearance"/>
<!-- Shape appearance overlay style reference for ShapeableImageView. To be used to augment
attributes declared in the shapeAppearance. Attribute declaration is in the shape package.
-->
<attr name="shapeAppearanceOverlay"/>
</declare-styleable>
可以看出碎捺,通過(guò)ShapeAppearanceModel 可以定義各種邊和角的屬性距糖。
繪制圖形
自定義不規(guī)則圖片的一般的做法是重寫(xiě)ImageView的onDraw方法玄窝,處理邊角則使用。何為PorterDuffXfermode悍引?
類(lèi)
繼承自android.graphics.Xfermode恩脂。在用Android中的Canvas進(jìn)行繪圖時(shí),可以通過(guò)使用PorterDuffXfermode將所繪制的圖形的像素與Canvas中對(duì)應(yīng)位置的像素按照一定規(guī)則進(jìn)行混合趣斤,形成新的像素值俩块,從而更新Canvas中最終的像素顏色值,這樣會(huì)創(chuàng)建很多有趣的效果浓领。
使用方法也必將簡(jiǎn)單玉凯,將其作為參數(shù)傳給Paint.setXfermode(Xfermode xfermode)方法,這樣在用該畫(huà)筆paint進(jìn)行繪圖時(shí)联贩,Android就會(huì)使用傳入的PorterDuffXfermode漫仆,如果不想再使用Xfermode,那么可以執(zhí)行Paint.setXfermode(null)泪幌。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//設(shè)置背景色
canvas.drawARGB(255, 139, 197, 186);
int canvasWidth = canvas.getWidth();
int r = canvasWidth / 3;
//正常繪制黃色的圓形
paint.setColor(0xFFFFCC44);
canvas.drawCircle(r, r, r, paint);
//使用CLEAR作為PorterDuffXfermode繪制藍(lán)色的矩形
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
paint.setColor(0xFF66AAFF);
canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
//最后將畫(huà)筆去除Xfermode
paint.setXfermode(null);
}
效果如下:
而可以實(shí)現(xiàn)的混合效果非常多盲厌,如圖:
在ShapeableImageView的構(gòu)造方法中可用看到一行:
clearPaint.setXfermode(new PorterDuffXfermode(Mode.DST_OUT));
可知ShapeableImageView的原理也是使用PorterDuffXfermode將圖片和指定的圖形混合得到想要的不規(guī)則圖片。其核心代碼如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(maskPath, clearPaint);
drawStroke(canvas);
}
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
super.onSizeChanged(width, height, oldWidth, oldHeight);
updateShapeMask(width, height);
}
private void updateShapeMask(int width, int height) {
destination.set(
getPaddingLeft(), getPaddingTop(), width - getPaddingRight(), height - getPaddingBottom());
pathProvider.calculatePath(shapeAppearanceModel, 1f /*interpolation*/, destination, path);
// Remove path from rect to draw with clear paint.
maskPath.rewind();
maskPath.addPath(path);
// Do not include padding to clip the background too.
maskRect.set(0, 0, width, height);
maskPath.addRect(maskRect, Direction.CCW);
}
代碼比較容易看懂祸泪,從onDraw看到繪制的流程:
1吗浩、先調(diào)用父類(lèi)ImageView的onDraw繪制基本圖片;
2没隘、生成不規(guī)則的圖片懂扼,clearPaint設(shè)置了PorterDuffXfermode(Mode.DST_OUT),即去掉src圖片重疊部分右蒲,僅保留剩下部分阀湿,而maskPath正是由不規(guī)則圖形與矩形圖片邊框組成;
3瑰妄、繪制邊界炕倘。
總結(jié)
上面只是分析了ShapeableImageView的核心代碼,很多細(xì)節(jié)沒(méi)有展開(kāi)翰撑,ShapeableImageView提供了豐富的屬性罩旋,通過(guò)改變邊角的值的組合可以實(shí)現(xiàn)各種各樣的圖形,使用起來(lái)是非常方便的眶诈,值得推薦涨醋。
參考
ShapeableImageView官方文檔
Android中Canvas繪圖之PorterDuffXfermode使用及工作原理詳解
關(guān)注V: “
碼農(nóng)翻身記
”,回復(fù)888逝撬,免費(fèi)領(lǐng)取Android/Java高頻面試題解析浴骂、進(jìn)階知識(shí)整理、圖解網(wǎng)絡(luò)宪潮、圖解操作系統(tǒng)等資料溯警。關(guān)注后趣苏,你將不定期收到優(yōu)質(zhì)技術(shù)及職場(chǎng)干貨分享。