編輯:關於Android編程
一篇給大家講解了有關setXfermode的幾種模式,還剩最後一系列DST模式沒講,這篇文章就給大家講講這個模式的用法及實戰
由於Mode.DST_IN的模式與SRC_IN正好是相反,所以我們利用Mode.SRC_IN實現的示例,只需要將源圖像與目標圖像對調就可以使用Mode.DST_IN來實現了。
所以大家自己來改造實現下《自定義控件三部曲之繪圖篇(十一)——Paint之setXfermode(二)》中的圖形圓角效果和圖片倒影效果,這裡就不再貼代碼了,有困難的同學可以參考文章底部源碼,源碼中有實現。
這裡我們就再來實現幾個不一樣的效果,這些效果也可以通過Mode.SRC_IN模式實現哦,大家自己可以試試
這裡使用到一張圖片(text_shade.png):
在這張圖片中,只有文字部分是純白色的,其它區域都是透明像素。
所以再加上我們需要自己繪制的水波紋效果的圖片,這裡就有兩張圖片了,一張是水波紋效果圖,另一張是text_shade.png
那麼問題來了,如果我們使用Mode.DST_IN模式的話,誰當目標圖像,誰當源圖像呢?
這就需要分析Mode.DST_IN模式的成像原理了,在Mode.DST_IN中,源圖像所占區域計算結果圖像時,相交區域顯示的是DST目標圖像;
所以我們要最終顯示的被裁剪後的波紋圖,所以DST目標圖像就應該是波紋圖。
所以源碼如下:
public class CircleWave_DSTIN extends View { private Paint mPaint; private Path mPath; private int mItemWaveLength = 1000; private int dx; private Bitmap BmpSRC,BmpDST; public CircleWave_DSTIN(Context context, AttributeSet attrs) { super(context, attrs); mPath = new Path(); mPaint = new Paint(); mPaint.setColor(Color.GREEN); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.text_shade,null); BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888); startAnim(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); generageWavePath(); //先清空bitmap上的圖像,然後再畫上Path Canvas c = new Canvas(BmpDST); c.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); c.drawPath(mPath,mPaint); canvas.drawBitmap(BmpSRC,0,0,mPaint); int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(BmpDST,0,0,mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); canvas.drawBitmap(BmpSRC,0,0,mPaint); mPaint.setXfermode(null); canvas.restoreToCount(layerId); } /** * 生成此時的Path */ private void generageWavePath(){ mPath.reset(); int originY = BmpSRC.getHeight()/2; int halfWaveLen = mItemWaveLength/2; mPath.moveTo(-mItemWaveLength+dx,originY); for (int i = -mItemWaveLength;i<=getWidth()+mItemWaveLength;i+=mItemWaveLength){ mPath.rQuadTo(halfWaveLen/2,-50,halfWaveLen,0); mPath.rQuadTo(halfWaveLen/2,50,halfWaveLen,0); } mPath.lineTo(BmpSRC.getWidth(),BmpSRC.getHeight()); mPath.lineTo(0,BmpSRC.getHeight()); mPath.close(); } public void startAnim(){ ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength); animator.setDuration(2000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { dx = (int)animation.getAnimatedValue(); postInvalidate(); } }); animator.start(); } }這裡涉及到利用貝賽爾曲線生成波浪線的知識,有關波浪線的代碼就一概而過了,核心放在xfermode上。
BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.text_shade,null); BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888);在初始化時,生成了兩張圖像,正如我們所說BmpDST是要顯示的波浪線,所以我們新建一個空白圖像,用以畫即將生成的波浪線;而BmpSRC則用來顯示源圖。
generageWavePath(); //先清空bitmap上的圖像,然後再畫上Path Canvas c = new Canvas(BmpDST); c.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); c.drawPath(mPath,mPaint);這一段是將波浪線的Path畫到BmpDST圖像上。
c.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);有關使用PorterDuff.Mode.CLEAR來清空圖像的知識,後面在講解PorterDuff.Mode.CLEAR時會細講,這裡知道即可。
c.drawPath(mPath,mPaint);在生成BmpDST之後,我們就可以使用xfermode了:
//先畫上SRC圖像來顯示完整的文字 canvas.drawBitmap(BmpSRC,0,0,mPaint); int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(BmpDST,0,0,mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); canvas.drawBitmap(BmpSRC,0,0,mPaint); mPaint.setXfermode(null); canvas.restoreToCount(layerId);有些同學可能會問,為什麼在saveLayer前,先要畫一遍BmpSRC呢,這是因為我們在使用Mode.DST_IN時,除了相交區域以外,其它區域都會因為有空白像素而消失不見了,如果我們不加canvas.drawBitmap(BmpSRC,0,0,mPaint);效果圖將是這樣的:
很明顯,正規的心電圖應該是利用Path把當前的實時的點連接起來,我這裡只是一張圖片(hearmap.png)通過使用動畫來實現的
中間是一條心電圖線,其余位置都是透明像素;大家先想想我們要怎麼利用這張圖片實現上面的動畫呢?
利用Mode.DST_IN模式,由於在這個模式中,相交區域優先顯示目標圖像,所以我們這裡需要顯示心電圖,所以心電圖就是目標圖像。
那麼問題來了,源圖像是啥?
由於我們需要從右向左逐漸顯示心電圖圖像,所以我們源圖像就是自建的空白圖像,在這個圖像中,繪制一個矩形,逐漸增大矩形的區域,即相交區域也會跟著增大,由於相交區域會顯示出目標圖像,顯示出來的結果就是心電圖的動畫,代碼如下:
public class HeartMap extends View { private Paint mPaint; private int mItemWaveLength = 0; private int dx=0; private Bitmap BmpSRC,BmpDST; public HeartMap(Context context, AttributeSet attrs) { super(context, attrs); mPaint = new Paint(); mPaint.setColor(Color.RED); BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.heartmap,null); BmpSRC = Bitmap.createBitmap(BmpDST.getWidth(), BmpDST.getHeight(), Bitmap.Config.ARGB_8888); mItemWaveLength = BmpDST.getWidth(); startAnim(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Canvas c = new Canvas(BmpSRC); //清空bitmap c.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); //畫上矩形 c.drawRect(BmpDST.getWidth() - dx,0,BmpDST.getWidth(),BmpDST.getHeight(),mPaint); //模式合成 int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(BmpDST,0,0,mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); canvas.drawBitmap(BmpSRC,0,0,mPaint); mPaint.setXfermode(null); canvas.restoreToCount(layerId); } public void startAnim(){ ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength); animator.setDuration(6000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { dx = (int)animation.getAnimatedValue(); postInvalidate(); } }); animator.start(); } }這段代碼難度不大,就不再講了,我們再來舉個例子。
在這裡我們需要用到兩張圖:
一張圓形遮罩(circle_shape.png)
一張不規則的波浪圖
想必到這裡,可能很多同學都知道要怎麼做了
就是在圓形遮罩上繪制不斷移動的不規則的波浪圖。
代碼如下:
public class IrregularWaveView extends View { private Paint mPaint; private int mItemWaveLength = 0; private int dx=0; private Bitmap BmpSRC,BmpDST; public IrregularWaveView(Context context, AttributeSet attrs) { super(context, attrs); mPaint = new Paint(); BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.wave_bg,null); BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.circle_shape,null); mItemWaveLength = BmpDST.getWidth(); startAnim(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //先畫上圓形 canvas.drawBitmap(BmpSRC,0,0,mPaint); //再畫上結果 int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(BmpDST,new Rect(dx,0,dx+BmpSRC.getWidth(),BmpSRC.getHeight()),new Rect(0,0,BmpSRC.getWidth(),BmpSRC.getHeight()),mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); canvas.drawBitmap(BmpSRC,0,0,mPaint); mPaint.setXfermode(null); canvas.restoreToCount(layerId); } public void startAnim(){ ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength); animator.setDuration(4000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { dx = (int)animation.getAnimatedValue(); postInvalidate(); } }); animator.start(); } }首先看動畫部分:
public void startAnim(){ ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength); animator.setDuration(4000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { dx = (int)animation.getAnimatedValue(); postInvalidate(); } }); animator.start(); }其中:
mItemWaveLength = BmpDST.getWidth();在這裡生成了一個ValueAnimator動畫,動畫的值從0到波浪圖的總長,實時的值保存在dx變量中。
protected void onDraw(Canvas canvas) { super.onDraw(canvas); //先畫上圓形 canvas.drawBitmap(BmpSRC,0,0,mPaint); //再畫上計算結果 int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(BmpDST,new Rect(dx,0,dx+BmpSRC.getWidth(),BmpSRC.getHeight()),new Rect(0,0,BmpSRC.getWidth(),BmpSRC.getHeight()),mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); canvas.drawBitmap(BmpSRC,0,0,mPaint); mPaint.setXfermode(null); canvas.restoreToCount(layerId); }首先,是畫了個BmpSRC所表示的原形圖:
canvas.drawBitmap(BmpSRC,0,0,mPaint);與“示例1、區域波紋”的原因一樣,我們需要先畫上圓形圖,不然就看不出來整體的樣式是什麼樣的。
canvas.drawBitmap(BmpDST,new Rect(dx,0,dx+BmpSRC.getWidth(),BmpSRC.getHeight()),new Rect(0,0,BmpSRC.getWidth(),BmpSRC.getHeight()),mPaint);它的意思就是截取波浪圖上new Rect(dx,0,dx+BmpSRC.getWidth(),BmpSRC.getHeight())這個矩形位置,將其畫在BmpSRC的位置:new Rect(0,0,BmpSRC.getWidth(),BmpSRC.getHeight())
由於在SRC中,我們知道了Mode.SRC_ATOP與MODE.SRC_IN的區別:
一般而言SRC_ATOP是可以和SRC_IN通用的,但SRC_ATOP所產生的效果圖在目標圖的透明度不是0或100%的時候,會比SRC_IN模式產生的圖像更亮些;
我們再來對比下DST中的兩個模式與SRC中的這兩個模式中公式中區別:
SRC_IN: [Sa * Da, Sc * Da]
SRC_ATOP:[Da, Sc * Da + (1 - Sa) * Dc]
DST_IN:[Da * Sa , Dc * Sa ]
DST_ATOP:[Sa, Sa * Dc + Sc * (1 - Da)]
從公式中可以看到,在SRC模式中,以顯示源圖像為主,透明度和飽和度利用Da來調節
而在DST模式中,以顯示目標圖像為主,透明度和飽和度利用Sa來調節
所以Mode.DST_ATOP與Mode.DST_IN的關系也是:
一般而言DST_ATOP是可以和DST_IN通用的,但DST_ATOP所產生的效果圖在源圖像的透明度不是0或100%的時候,會比DST_IN模式產生的圖像更亮些;
同樣,大家也可以使用Mode.DST_ATOP實現上篇文章中Mode.SRC_ATOP的兩個示例:圓角效果和圖片倒影,這裡就不再講了
到這裡有關DST相關模式都講完了,我們總結一下:
1、DST相關模式是完全可以使用SRC對應的模式來實現的,只不過需要將目標圖像和源圖像對調一下即可。
2、在SRC模式中,是以顯示源圖像為主,通過目標圖像的透明度來調節計算結果的透明度和飽和度,而在DST模式中,是以顯示目標圖像為主,通過源圖像的透明度來調節計算結果的透明度和飽和度。
前面我們做清空圖像的時候用過這個方法,從公式中可以看到,計算結果直接就是[0,0]即空像素。也就是說,源圖像所在區域都會變成空像素!
這樣就起到了清空源圖像所在區域圖像的功能了。上面示例中已經存在這個Mode的用法,這裡就不再舉例了。
單從示例圖像中,好像是異或的功能,即將源圖像中除了相交區域以外的部分做為結果。但仔細看看公式,其實並沒有這麼簡單。
首先看公式中透明度部分:Sa + Da - Sa*Da,就是將目標圖像和源圖像的透明度相加,然後減去它們的乘積,所以計算結果的透明度會增大(即比目標圖像和源圖像都大,當其中一個圖像的透明度為1時,那結果圖像的透明度肯定是1)
然後再看顏色值部分:Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc);表示源圖像和目標圖像分別以自己的透明度的補值乘以對方的顏色值,然後相加得到結果。最後再加上Sc, Dc中的最小值。
這個模式太過復雜,在實際應用中應用也比較少,目前沒想到有哪些示例,大家有用到的,可以跟我說哦。
在實際應用中,我們可以從下面三個方面來決定使用哪一個模式:
1、首先,目標圖像和源圖像混合,需不需要生成顏色的疊加特效,如果需要疊加特效則從顏色疊加相關模式中選擇,有Mode.ADD(飽和度相加)、Mode.DARKEN(變暗),Mode.LIGHTEN(變亮)、Mode.MULTIPLY(正片疊底)、Mode.OVERLAY(疊加),Mode.SCREEN(濾色)
2、當不需要特效,而需要根據某一張圖像的透明像素來裁剪時,就需要使用SRC相關模式或DST相關模式了。由於SRC相關模式與DST相關模式是相通的,唯一不同的是決定當前哪個是目標圖像和源圖像;
3、當需要清空圖像時,使用Mode.CLEAR
這篇文章到這裡就結束啦,有關xfermode的知識比較有難度,大家需要仔細揣摩下
前言:可能Android的事件分發對於剛學Android的童鞋來說接觸得不多,這樣不奇怪。因為剛學的時候,一般人很難注意到或是會選擇主動去了解。那麼究竟什麼是Androi
Android應用內社區SDK技術架構淺析應用內微社區是什麼 ? 圖1 圖2 雪球財經應用內社區 一、以友盟微社區為例簡單來說,友盟的微社區就是一款幫助
當你在微博或微信中看到有趣的秒拍視頻後,想要將它保存到手機裡面該如何保存呢?簡而言之,也就是手機端下載秒拍視頻。下面,小編來教大家秒拍視頻保存到手機裡的方法
Android 中與 Touch 事件相關的方法包括:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEv