前言
在平時自定義View的過程中,基礎(chǔ)的測量繪制等操作能夠打造一個基本的自定義View,但如果想讓你的自定義View玩出更多花樣淳衙,就要結(jié)合一些比較靈活的繪制方式,比如本文要講的混合模式——Xfermode饺著。之前寫的幾個自定義View也有用到它來實現(xiàn):
傳送門:『Android自定義View實戰(zhàn)』給我一個圖標(biāo)箫攀,還你一個水波紋進度球
?
傳送門:『Android自定義View實戰(zhàn)』自定義完美的刮刮樂效果
之前一直沒有去完整地總結(jié)Xfermode,本文將針對Xfermode的各個模式展開詳細(xì)的分析幼衰,理解其各個模式的作用靴跛。
?
正文
什么是混合模式?
其概念最早來自于SIGGRAPH的Tomas Proter和Tom Duff渡嚣,混合圖形的概念極大地推動了圖形圖像學(xué)的發(fā)展梢睛,延伸到計算機圖形圖像學(xué)像Adobe和AutoDesk公司著名的多款設(shè)計軟件都可以說一定程度上受到影響肥印。從字面上其實大概猜到了它的作用,混合模式就是將畫布中的兩個圖像绝葡,按照一定的算法深碱,合成一個新的圖像。在Android中提供了混合模式相關(guān)的類——Xfermode藏畅,它派生出來的一個子類——ProterDuffXfermode就是我們平常在Android中使用混合模式時需要用到的類,它定義了很多模式供我們選擇愉阎,實現(xiàn)兩個圖像疊加在一起時的多樣化呈現(xiàn)榜旦。
?
混合模式有哪些幽七?
首先理解下圖形的一些概念,圖像是由很多個像素點組合而成锉走,每個像素點都有一個自己的顏色通道組合藕届,即所謂的ARGB:
A代表透明度Alpha
R代表紅色通道Red
G代表綠色通道Green
B代表藍(lán)色通道Blue
混合模式中挪蹭,將ARGB劃分為兩個部分,即A+RGB的格式休偶,也就是每個像素都會被劃分為這樣的形式的來描述:[Alpha,RGB]词顾,即透明度+顏色值碱妆,那么如果是兩個圖層疊在在一起時疹尾,它們交集的區(qū)域可以有很多種可能性,例如[取圖層1的的透明度窍蓝,取圖層1的顏色值]
繁成,[取圖層2的透明度巾腕,取圖層2的顏色值]
絮蒿,稍微復(fù)雜一點可以是[取圖層1的透明度*圖層2的透明度歌径,取圖層1的色值*圖層2的透明度]
....等等亲茅,將兩個圖像分別稱之為源圖像(Src)和目標(biāo)圖像(Dst)克锣,從而可以根據(jù)這些算法衍生出很多種模式,也就是我們的18種混合模式:
模式 | 組合算法 | 效果 |
---|---|---|
ADD | Saturate(S + D) | 飽和相加,對圖像飽和度進行相加 |
CLEAR | [0, 0] | 交集區(qū)域alpha和rgb值均為0 |
DARKEN | [Sa + Da - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] | 變暗验残,較深的顏色會覆蓋淺色 |
DST | [Da, Dc] | 交集區(qū)域只顯示DST的透明度和色值 |
DST_ATOP | [Sa, Sa * Dc + Sc * (1 - Da)] | 相交處繪制DST的部分您没,其他區(qū)域只顯示SRC |
DST_IN | [Sa * Da, Sa * Dc] | 只在DST和SRC相交的地方繪制DST的部分 |
DST_OUT | [Da * (1 - Sa), Dc * (1 - Sa)] | 只在DST和SRC交集之外的區(qū)域繪制DST的部分 |
DST_OVER | [Sa + (1 - Sa)Da, Rc = Dc + (1 - Da)Sc] | DST蓋在SRC上面 |
LIGHTEN | [Sa + Da - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] | 相交的區(qū)域變亮 |
MULTIPLY | [Sa * Da, Sc * Dc] | 透明度和色值均不為0的地方繪制 |
OVERLAY | 疊加效果 | |
SCREEN | [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] | 保留兩個圖層中較白的部分胆绊,較暗的部分被遮蓋 |
SRC | [Sa, Sc] | 交集區(qū)域只顯示SRC的透明度和色值 |
SRC_ATOP | [Da, Sc * Da + (1 - Sa) * Dc] | 相交處繪制SRC的部分压状,其他區(qū)域只顯示DST |
SRC_IN | [Sa * Da, Sc * Da] | 只在DST和SRC相交的地方繪制SRC的部分 |
SRC_OUT | [Sa * (1 - Da), Sc * (1 - Da)] | 只在DST和SRC交集之外的區(qū)域繪制SRC的部分 |
SRC_OVER | [Sa + (1 - Sa)Da, Rc = Sc + (1 - Sa)Dc] | SRC蓋在DST上面 |
XOR | [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] | 相交的區(qū)域受透明度和色值的影響种冬,如果完全不透明相交處不繪制 |
它們都是根據(jù)以上公式計算出來對應(yīng)的繪制形式,其中莺匠,Sa趣竣、Sc代表源圖像的透明度(Src Alpha)和色值(Src Color)纪挎,Ds异袄、Dc代表目標(biāo)圖像的透明度(Dst Alpha)和色值(Dst Color)玛臂,例如 DST_IN 模式,它是根據(jù) [Sa * Da, Sa * Dc] 算法來進行繪制讽营,按照上文講的 [Alpha橱鹏,RGB] 的公式套進去,那么就是:
交集區(qū)域的透明度 = 源圖像的透明度 * 目標(biāo)圖像的透明度
交集區(qū)域的色值 = 源圖像的透明度 * 目標(biāo)圖像的色值
可以看到挑围,無論是透明度還是色值杉辙,源圖像只有透明度派上用場捶朵,換句話說综看,就是去除了源圖像的色值,那么就只剩透明度了珍昨,所以在源圖像和目標(biāo)圖像的交集區(qū)域镣典,只會顯示出目標(biāo)圖像的效果唾琼,但是其透明度會受到源圖像的透明度的影響锡溯。
?
如何使用混合模式?
1.禁用硬件加速
Android中混合模式的操作其實不復(fù)雜芜茵,首先九串,也是容易遺漏的一個步驟,就是禁用硬件加速品山,部分混合模式的使用必須在禁用硬件加速的前提下進行肘交,否則出來的效果會所出入扑馁,因此一般保險起見腻要,使用混合模式之前都禁用硬件加速,如果不想影響應(yīng)用的其他地方市栗,可以只在自定義View的構(gòu)造方法中調(diào)用:
//禁用硬件加速
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
?
2.繪制目標(biāo)圖像
先用畫筆繪制一個形狀或圖片填帽,作為DST圖:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(dstBm, 0, 0, paint);
}
?
3.設(shè)置混合模式
接著為畫筆設(shè)置一個混合模式:
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
?
4.繪制源圖像
canvas.drawBitmap(srcBm, 0, 0, paint);
?
5.清除混合模式
最后清除畫筆混合模式篡腌,以防止下次調(diào)用onDraw的時候受影響:
paint.setXfermode(null);
匯總起來也就是:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(dstBm, 0, 0, paint);
paint.setXfermode(duffXfermode);
canvas.drawBitmap(srcBm, 0, 0, paint);
paint.setXfermode(null);
}
?
混合模式效果
紙上得來終覺淺勾效,我們通過一個demo层宫,來看下這些模式真正呈現(xiàn)出來的效果是怎樣的,創(chuàng)建一個自定義View限匣,并繪制一個圓形和一個方形分別作為目標(biāo)圖和源圖:
public class DuffModeView extends View{
private Paint paint;
private int width, height;
private Bitmap srcBm, dstBm;
public DuffModeView(Context context) {
this(context, null);
}
public DuffModeView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DuffModeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//禁用硬件加速
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
//初始化畫筆
paint = new Paint();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
width = right - left;
height = bottom - top;
srcBm = createSrcBitmap(width, height);
dstBm = createDstBitmap(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(dstBm, 0, 0, paint);
canvas.drawBitmap(srcBm, 0, 0, paint);
}
public Bitmap createDstBitmap(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint dstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
dstPaint.setColor(Color.parseColor("#00b7ee"));
canvas.drawCircle(width / 3, height / 3, width / 3, dstPaint);
return bitmap;
}
public Bitmap createSrcBitmap(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint scrPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
scrPaint.setColor(Color.parseColor("#ec6941"));
canvas.drawRect(new Rect(width / 3, height / 3, width, height), scrPaint);
return bitmap;
}
}
將View的寬高劃分為了3等分,將圓形繪制在左上角峦筒,正方形繪制在右下角窗慎,并分別設(shè)置不同的顏色,中間有一部分相交,效果如下:
“素材”準(zhǔn)備好了宠进,那么就可以開始“動刀”了藐翎,我們實例化一個PorterDuffXfermode
對象吝镣,將其設(shè)置給剛才中的畫筆:
duffXfermode = new PorterDuffXfermode(PorterDuff.Mode.ADD);
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//離屏繪制
int layerID = canvas.saveLayer(0, 0, getWidth(), getHeight(), paint, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(dstBm, 0, 0, paint);
paint.setXfermode(duffXfermode);
canvas.drawBitmap(srcBm, 0, 0, paint);
paint.setXfermode(null);
canvas.restoreToCount(layerID);
}
這里onDraw中有幾個注意的點末贾,我們通過Canvas的saveLayer和restoreToCount將混合模式置于單獨的層面中進行,以免混合模式相關(guān)的操作對畫布的其他區(qū)域產(chǎn)生影響辉川,通俗的講乓旗,可以理解成從Canvas中抽取出一層單獨的圖層集索,一切繪制都不會影響到原本的畫布务荆,等調(diào)用restoreToCount之后,就會把我們的最終效果加回到Canvas上毅厚。(是不是有點類似Photoshop的圖層概念)
接著經(jīng)過以下幾個步驟:
1.調(diào)用drawBitmap繪制了DST圖
2.為畫筆設(shè)置混合模式
3.用設(shè)置了混合模式的畫筆繪制SRC圖
4.清除畫筆混合模式
這里采用了PorterDuff.Mode.ADD模式吸耿,呈現(xiàn)出來的效果如下:
可以看到中間相交區(qū)域的不是藍(lán)色也不是紅色咽安,而是它們的飽和度相加之后得到的結(jié)果蓬推。
我們只需要替換PorterDuffXfermode的構(gòu)造方法參數(shù),改成其他模式动分,即可看到其他模式的效果红选,依次如下:
PorterDuff.Mode.CLEAR
CLEAR模式的公式是[0, 0]喇肋,也就是透明度和色值均為0蝶防,由于我們是將混合模式設(shè)置給畫筆之后才繪制紅色方形,所以可以看到藍(lán)色(DST)與紅色(SRC)相交的區(qū)域殷费,藍(lán)色部分也一并被清除掉宗兼,也就是交集區(qū)域透明度和色值均為0殷绍,因此此時的SRC就類似是一塊“橡皮擦”的效果鹊漠。
PorterDuff.Mode.DARKEN
相交的區(qū)域躯概,看起來像是變成了另外一種顏色娶靡,其實這種模式就是比較深色的會覆蓋淺色的,可以嘗試把其中一個改成黑色或者白色塔鳍,就更明顯了~
PorterDuff.Mode.DST
DST模式的公式是[Da, Dc]轮纫,也就是整個紅色方形的區(qū)域掌唾,都取決于這兩個值來顯示,那么在紅色方形范圍內(nèi)凭语,也就只有兩者相交的區(qū)域才有藍(lán)色的透明度和色值,圓形是DST圖却舀,方形是SRC圖挽拔,可以看到最終的效果只剩下DST圖了螃诅,也就是交集區(qū)域只顯示DST的透明度和色值术裸。
PorterDuff.Mode.DST_ATOP
DST_ATOP的公式為[Sa, Sa * Dc + Sc * (1 - Da)]袭艺,透明度取決于SRC的透明度猾编,在兩者相交的區(qū)域升敲,Da為1瘪撇,所以其實色值只剩Sa*Dc港庄,就是在交集的區(qū)域顯示DST圖的色值叉存,不相交的區(qū)域只顯示SRC的部分歼捏。
PorterDuff.Mode.DST_IN
DST_IN的公式為[Sa * Da, Sa * Dc]瞳秽,可以看到练俐,只在交集的區(qū)域有顏色和透明值的顯示,且顯示的是DST圖的部分燕锥,這是因為在兩者相交的區(qū)域归形,Sa不為0鼻由,所以Sa*Da顯示DST的透明度蕉世,Sa*Dc顯示DST的色值狠轻,在相交之外的區(qū)域,要么是Sa為0哩至,要么是Da為0菩貌,所以這些區(qū)域計算出來的結(jié)果都是[0,0]箭阶。
PorterDuff.Mode.DST_OUT
DST_OUT的公式為[Da * (1 - Sa), Dc * (1 - Sa)]仇参,仔細(xì)看公式婆殿,它與DST_IN的計算方式區(qū)別僅僅在于(1-Sa),在兩者不相交的區(qū)域婆芦,且Sa為0的地方,顯示DST的部分员帮,可以看到导饲,它與DST_IN的效果恰恰就是互補渣锦。
PorterDuff.Mode.DST_OVER
呈現(xiàn)出來的效果袋毙,如同DST圖蓋住了SRC圖,所以稱之為DST_OVER~
PorterDuff.Mode.LIGHTEN
與DARKEN相反生闲,淺色覆蓋深色,會呈現(xiàn)出變亮的效果悬蔽。
PorterDuff.Mode.MULTIPLY
MULTIPLY模式的公式為[Sa * Da, Sc * Dc],透明度為Sa*Da倍啥,說明只在相交的區(qū)域有透明度虽缕,其他區(qū)域均由于Sa=0或者Da=0導(dǎo)致透明度為0氮趋,色值為SRC和DST的色值相乘后的結(jié)果。
PorterDuff.Mode.OVERLAY
在這種模式下,雖然是覆蓋晾腔,但是并不會完全遮擋,而是形成疊加的視覺效果扩借。
PorterDuff.Mode.SCREEN
兩個形狀的交集區(qū)域潮罪,呈現(xiàn)出了另一種顏色嫉到,但其實是取了兩者較白的部分混合而成的效果何恶。
PorterDuff.Mode.SRC
SRC的公式為[Sa, Sc]细层,在兩者相交區(qū)域疫赎,Sa和Sc均不為0碎节,在兩者相交區(qū)域之外的地方狮荔,就取決于SRC的透明度和色值了殖氏,形成的效果就是混合后只顯示SRC的部分雅采。
PorterDuff.Mode.SRC_ATOP
SRC_ATOP的公式為[Da, Sc * Da + (1 - Sa) * Dc]总滩,透明度取決于DST的透明度,在兩者相交的區(qū)域席函,Sa為1茂附,所以其實色值只剩Sc*Da营曼,就是在交集的區(qū)域顯示SRC圖的色值蒂阱,不相交的區(qū)域只顯示DST的部分。
PorterDuff.Mode.SRC_IN
SRC_IN的公式為[Sa * Da, Sc * Da],可以看到妈踊,只在交集的區(qū)域有顏色和透明值的顯示廊营,且顯示的是SRC圖的部分露筒,這是因為在兩者相交的區(qū)域,Da不為0荸哟,所以Sa*Da顯示DST的透明度,Sc*Da顯示SRC的色值肪虎,在相交之外的區(qū)域扇救,要么是Sa為0迅腔,要么是Da為0,所以這些區(qū)域計算出來的結(jié)果都是[0,0]靠娱。
PorterDuff.Mode.SRC_OUT
SRC_OUT的公式為[Sa * (1 - Da), Sc * (1 - Da)]沧烈,可以看到,只在交集之外的區(qū)域有顏色和透明值的顯示像云,且顯示的是SRC圖的部分锌雀,這是因為在兩者相交的區(qū)域蚂夕,Da為1,所以Sa*(1-Da)為0腋逆,Sc*(1 - Da)顯示也為0婿牍,在相交之外的區(qū)域惩歉,只有SRC的區(qū)域Da才為0等脂,所以這些區(qū)域計算出來的結(jié)果就是[Sa,Sc]。
PorterDuff.Mode.SRC_OVER
呈現(xiàn)出來的效果撑蚌,如同SRC圖蓋住了DST圖慎菲,所以稱之為SRC_OVER~
PorterDuff.Mode.SRC_XOR
在SRC圖像和DST圖像相交的區(qū)域之外繪制它們,在相交的區(qū)域受到對應(yīng)alpha和色值影響锨并,如果完全不透明則相交處完全不繪制露该。
?
運用混合模式實現(xiàn)效果
裁剪任意形狀圖片
圓形圖片View的實現(xiàn)方式有很多種,比如說繼承ImageView在onDraw中ClipPath第煮,混合模式也能實現(xiàn)這種效果解幼,只需要將我們要加載的背景圖設(shè)置為目標(biāo)圖像,然后繪制一個圓形形狀的源圖像包警,設(shè)置混合模式為DST_IN撵摆,那么就只會繪制出圓形范圍內(nèi)的背景圖,從而實現(xiàn)圓形圖片的效果害晦,關(guān)鍵代碼如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//離屏繪制
int layerID = canvas.saveLayer(0, 0, width, height, paint, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(dstBm, 0, 0, paint);
paint.setXfermode(duffXfermode);
canvas.drawBitmap(srcBm, 0, 0, paint);
paint.setXfermode(null);
canvas.restoreToCount(layerID);
}
public Bitmap createDstBitmap(int width, int height) {
return BitmapFactory.decodeResource(getResources(), R.drawable.bg_duffmode_test);
}
public Bitmap createSrcBitmap(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint dstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
dstPaint.setColor(Color.parseColor("#ec6941"));
canvas.drawCircle(width/2, height/2, height/2, dstPaint);
return bitmap;
}
?
效果如下:
甚至我們可以自定義裁剪形狀特铝,只要修改上面代碼中創(chuàng)建源圖像的代碼如下:
public Bitmap createSrcBitmap(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint srcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
srcPaint.setStyle(Paint.Style.FILL);
srcPaint.setColor(Color.parseColor("#ec6941"));
Path path = new Path();
path.moveTo(width/2, 0);
path.lineTo(width/6, height);
path.lineTo(width*5/6, height);
path.close();
canvas.drawPath(path, srcPaint);
//canvas.drawCircle(width/2, height/2, height/2, srcPaint);
return bitmap;
}
就能得到一個三角形的效果:
水波紋進度球效果
原理其實就是利用貝塞爾曲線繪制水波紋性狀并且將其閉合起來,作為目標(biāo)圖像壹瘟,然后再繪制一個圓形作為源圖像鲫剿,也就是用圓形來“裁剪”水波紋,利用混合模式稻轨,只顯示出圓形范圍內(nèi)的水波紋部分灵莲,從而形成水波紋進度球效果:
甚至可以利用圖標(biāo)去填充水波紋的區(qū)域,代碼比較多就不貼了殴俱,詳見我另一篇文章『Android自定義View實戰(zhàn)』給我一個圖標(biāo)政冻,還你一個水波紋進度球
?
結(jié)語
簡單來講,其實目標(biāo)圖像就類似于底片线欲,源圖像就類似于疊加在上面的圖層明场,混合模式就相當(dāng)于各種濾鏡±罘幔混合模式雖然一共只有18種苦锨,但巧妙地利用這些模式已經(jīng)足以幫助我們輕松實現(xiàn)一些特殊效果,本文只是列舉一些利用混合模式實現(xiàn)的效果,如果你有更好的創(chuàng)意逆屡,歡迎一起討論~ 文中Demo完整代碼已上傳到 一個集合酷炫效果的自定義組件庫圾旨,歡迎Issue。
?
歡迎關(guān)注 Android小Y 的簡書魏蔗,更多Android精選自定義View
『Android自定義View實戰(zhàn)』實現(xiàn)一個小清新的彈出式圓環(huán)菜單
『Android自定義View實戰(zhàn)』玩轉(zhuǎn)PathMeasure之自定義支付結(jié)果動畫
『Android自定義View實戰(zhàn)』自定義弧形旋轉(zhuǎn)菜單欄——衛(wèi)星菜單
『Android自定義View實戰(zhàn)』自定義帶入場動畫的弧形百分比進度條
GitHub:GitHub-ZJYWidget
CSDN博客:IT_ZJYANG
簡 書:Android小Y
在 GitHub 上建了一個集合炫酷自定義View的項目砍的,里面有很多實用的自定義View源碼及demo,會長期維護莺治,歡迎Star~ 如有不足之處或建議還望指正廓鞠,相互學(xué)習(xí),相互進步谣旁,如果覺得不錯動動小手點個喜歡床佳, 謝謝~