日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

转载爱哥自定义View系列--Paint详解

發布時間:2023/12/10 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 转载爱哥自定义View系列--Paint详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

上圖是paint中的各種set方法

這些屬性大多我們都可以見名知意,很好理解,即便如此,哥還是帶大家過一遍逐個剖析其用法,其中會不定穿插各種繪圖類比如Canvas、Xfermode、ColorFilter等等的用法。

set(Paint src)

顧名思義為當前畫筆設置一個畫筆,說白了就是把另一個畫筆的屬性設置Copy給我們的畫筆,不累贅了

setARGB(int a, int r, int g, int b)

不扯了,別跟我說不懂

setAlpha(int a)

同上

setAntiAlias(boolean aa)

這個上一節我們用到了,打開抗鋸齒,不過我要說明一點,抗鋸齒是依賴于算法的,算法決定抗鋸齒的效率,在我們繪制棱角分明的圖像時,比如一個矩形、一張位圖,我們不需要打開抗鋸齒。

setColor(int color)

不扯

setColorFilter(ColorFilter filter)

設置顏色過濾,什么意思呢?就像拿個篩子把顏色“濾”一遍獲取我們想要的色彩結果,感覺像是扯蛋白說一樣是不是?沒事我們慢慢說你一定會懂,這個方法需要我們傳入一個ColorFilter參數同樣也會返回一個ColorFilter實例,那么ColorFilter類是什么呢?追蹤源碼進去你會發現其里面很簡單幾乎沒有:

ColorMatrixColorFilter、LightingColorFilter和PorterDuffColorFilter,也就是說我們在setColorFilter(ColorFilter filter)的時候可以直接傳入這三個子類對象作為參數,那么這三個子類又是什么東西呢?首先我們來看看

ColorMatrixColorFilter

中文直譯為色彩矩陣顏色過濾器,要明白這玩意你得先了解什么是色彩矩陣。在Android中圖片是以RGBA像素點的形式加載到內存中的,修改這些像素信息需要一個叫做ColorMatrix類的支持,其定義了一個4x5的float[]類型的矩陣:

ColorMatrix colorMatrix = new ColorMatrix(new float[]{1, 0, 0, 0, 0,0, 1, 0, 0, 0,0, 0, 1, 0, 0,0, 0, 0, 1, 0, });

其中,第一行表示的R(紅色)的向量,第二行表示的G(綠色)的向量,第三行表示的B(藍色)的向量,最后一行表示A(透明度)的向量,這一順序必須要正確不能混淆!這個矩陣不同的位置表示的RGBA值,其范圍在0.0F至2.0F之間,1為保持原圖的RGB值。每一行的第五列數字表示偏移值,何為偏移值?顧名思義當我們想讓顏色更傾向于紅色的時候就增大R向量中的偏移值,想讓顏色更傾向于藍色的時候就增大B向量中的偏移值,這是最最樸素的理解,但是事實上色彩偏移的概念是基于白平衡來理解的,什么是白平衡呢?說得簡單點就是白色是什么顏色!如果大家是個單反愛好者或者會些PS就會很容易理解這個概念,在單反的設置參數中有個色彩偏移,其定義的就是白平衡的色彩偏移值,就是當你去拍一張照片的時候白色是什么顏色的,在正常情況下白色是(255, 255, 255, 255)但是現實世界中我們是無法找到這樣的純白物體的,所以在我們用單反拍照之前就會拿一個我們認為是白色的物體讓相機記錄這個物體的顏色作為白色,然后拍攝時整張照片的顏色都會依據這個定義的白色來偏移!而這個我們定義的“白色”(比如:255, 253, 251, 247)和純白(255, 255, 255, 255)之間的偏移值(0, 2, 4, 8)我們稱之為白平衡的色彩偏移。

那么說了這么多,這玩意到底有啥用呢?我們來做個test!還是接著昨天那個圓環,不過我們今天把它改成繪制一個圓并且去掉線程動畫的效果因為我們不需要:

public class CustomView extends View {private Paint mPaint;// 畫筆private Context mContext;// 上下文環境引用public CustomView(Context context) {this(context, null);}public CustomView(Context context, AttributeSet attrs) {super(context, attrs);mContext = context;// 初始化畫筆 initPaint();}/*** 初始化畫筆*/private void initPaint() {// 實例化畫筆并打開抗鋸齒mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);/** 設置畫筆樣式為描邊,圓環嘛……當然不能填充不然就么意思了* * 畫筆樣式分三種: * 1.Paint.Style.STROKE:描邊 * 2.Paint.Style.FILL_AND_STROKE:描邊并填充* 3.Paint.Style.FILL:填充*/mPaint.setStyle(Paint.Style.FILL);// 設置畫筆顏色為自定義顏色mPaint.setColor(Color.argb(255, 255, 128, 103));/** 設置描邊的粗細,單位:像素px 注意:當setStrokeWidth(0)的時候描邊寬度并不為0而是只占一個像素*/mPaint.setStrokeWidth(10);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 繪制圓形canvas.drawCircle(MeasureUtil.getScreenSize((Activity) mContext)[0] / 2, MeasureUtil.getScreenSize((Activity) mContext)[1] / 2, 200, mPaint);} }

運行下是一個橙紅色的圓~~是不是有點蘿卜頭國旗幟的感腳?

?

下面我們為Paint設置一個色彩矩陣:

// 生成色彩矩陣 ColorMatrix colorMatrix = new ColorMatrix(new float[]{1, 0, 0, 0, 0,0, 1, 0, 0, 0,0, 0, 1, 0, 0,0, 0, 0, 1, 0, }); mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));

再次運行發現沒變化啊!!!草!!!是不是感覺被我坑了?如果你真的那么認為我只能說你壓根就沒認真看上面的文字,我說過什么?值為1時表示什么?表示不改變原色彩的值!!這時我們改變色彩矩陣:

// 生成色彩矩陣 ColorMatrix colorMatrix = new ColorMatrix(new float[]{0.5F, 0, 0, 0, 0,0, 0.5F, 0, 0, 0,0, 0, 0.5F, 0, 0,0, 0, 0, 1, 0, }); mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));

再次運行:

是不是明顯不一樣了?顏色變深了便淳厚了!我們通過色彩矩陣與原色彩的計算得出的色彩就是這樣的。那它們是如何計算的呢?其實說白了就是矩陣之間的運算乘積:

矩陣ColorMatrix的一行乘以矩陣MyColor的一列作為矩陣Result的一行,這里MyColor的RGBA值我們需要轉換為[0, 1]。那么我們依據此公式來計算下我們得到的RGBA值是否跟我們計算得出來的圓的RGBA值一樣:

我們計算得出最后的RGBA值應該為:0.5, 0.25, 0.2, 1;

有興趣的童鞋可以去PS之類的繪圖軟件里試試看正不正確對不對~~~這里就不演示了!看完這里有朋友又會說了,這玩意有毛線用啊!改個顏色還這么復雜!勞資直接setColor多爽!!沒錯,你這樣想是對的,因為畢竟我們只是一個顏色,可是如果是一張圖片呢????一張圖片可有還幾十萬色彩呢!!!你麻痹你跟我說setColor?那么我們換張圖片來試試唄!看看是什么樣的效果:

?

public class CustomView extends View {private Paint mPaint;// 畫筆private Context mContext;// 上下文環境引用private Bitmap bitmap;// 位圖private int x,y;// 位圖繪制時左上角的起點坐標public CustomView(Context context) {this(context, null);}public CustomView(Context context, AttributeSet attrs) {super(context, attrs);mContext = context;// 初始化畫筆 initPaint();//初始化資源 initRes(context);}/*** 初始化畫筆*/private void initPaint() {// 實例化畫筆mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);}/*** 初始化資源*/private void initRes(Context context) {// 獲取位圖bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a);/** 計算位圖繪制時左上角的坐標使其位于屏幕中心* 屏幕坐標x軸向左偏移位圖一半的寬度* 屏幕坐標y軸向上偏移位圖一半的高度*/x = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;y = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 繪制位圖 canvas.drawBitmap(bitmap, x, y, mPaint);} }

如代碼所示我們清除了所有的畫筆屬性設置因為沒必要,從資源獲取一個Bitmap繪制在畫布上:

?

一張灰常漂亮的風景圖,好!現在我們來為我們的畫筆添加一個顏色過濾:

// 生成色彩矩陣 ColorMatrix colorMatrix = new ColorMatrix(new float[]{0.5F, 0, 0, 0, 0,0, 0.5F, 0, 0, 0,0, 0, 0.5F, 0, 0,0, 0, 0, 1, 0, }); mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));

大家看到還是剛才那個色彩矩陣,運行下看看什么效果呢:

變暗了對吧!沒意思,我們來點更刺激的,改下ColorMatrix矩陣:

ColorMatrix colorMatrix = new ColorMatrix(new float[]{0.33F, 0.59F, 0.11F, 0, 0,0.33F, 0.59F, 0.11F, 0, 0,0.33F, 0.59F, 0.11F, 0, 0,0, 0, 0, 1, 0, });


噢!變灰了!還是沒意思!繼續改:

ColorMatrix colorMatrix = new ColorMatrix(new float[]{-1, 0, 0, 1, 1,0, -1, 0, 1, 1,0, 0, -1, 1, 1,0, 0, 0, 1, 0, });

?

喲呵!!是不是有點類似PS里反相的效果?我們常看到的圖片都是RGB的,顛覆一下思維,看看BGR的試試:

ColorMatrix colorMatrix = new ColorMatrix(new float[]{0, 0, 1, 0, 0,0, 1, 0, 0, 0,1, 0, 0, 0, 0,0, 0, 0, 1, 0, });


這樣紅色的變成了藍色而藍色的就變成了紅色,繼續改:

ColorMatrix colorMatrix = new ColorMatrix(new float[]{0.393F, 0.769F, 0.189F, 0, 0,0.349F, 0.686F, 0.168F, 0, 0,0.272F, 0.534F, 0.131F, 0, 0,0, 0, 0, 1, 0, });


是不是有點類似于老舊照片的感腳?繼續:

?

ColorMatrix colorMatrix = new ColorMatrix(new float[]{1.5F, 1.5F, 1.5F, 0, -1,1.5F, 1.5F, 1.5F, 0, -1,1.5F, 1.5F, 1.5F, 0, -1,0, 0, 0, 1, 0, });


類似去色后高對比度的效果,繼續:

ColorMatrix colorMatrix = new ColorMatrix(new float[]{1.438F, -0.122F, -0.016F, 0, -0.03F,-0.062F, 1.378F, -0.016F, 0, 0.05F,-0.062F, -0.122F, 1.483F, 0, -0.02F,0, 0, 0, 1, 0, });


飽和度對比度加強,好了不演示了……累死我了!截圖粘貼上傳!!

?

這些各種各樣的圖像效果在哪見過?PS?對的!還有各種拍照軟件拍攝后的特效處理!大致原理都是這么來的!有人會問愛哥你傻逼么!這么多參數怎么玩!誰記得!而且TMD用參數調顏色?我映像中都是直接在各種繪圖軟件(比如PS)里拖進度條的!這怎么玩!淡定!如我所說很多時候你壓根不需要了解太多原理,只需站在巨人的丁丁上即可,所以稍安勿躁!再下一個系列教程“設計色彩”中愛哥教你玩轉色彩并且讓設計和開發無縫結合!

ColorMatrixColorFilter和ColorMatrix就是這么個東西,ColorMatrix類里面也提供了一些實在的方法,比如setSaturation(float sat)設置飽和度,而且ColorMatrix每個方法都用了陣列的計算,如果大家感興趣可以自己去深挖來看不過我是真心不推薦的~~~

下面我們來看看ColorFilter的另一個子類

LightingColorFilter

顧名思義光照顏色過濾,這肯定是跟光照是有關的了~~該類有且只有一個構造方法:

LightingColorFilter (int mul, int add)

這個方法非常非常地簡單!mul全稱是colorMultiply意為色彩倍增,而add全稱是colorAdd意為色彩添加,這兩個值都是16進制的色彩值0xAARRGGBB。這個方法使用也是非常的簡單。還是拿上面那張圖片來說吧,比如我們想要去掉綠色:

public class CustomView extends View {private Paint mPaint;// 畫筆private Context mContext;// 上下文環境引用private Bitmap bitmap;// 位圖private int x, y;// 位圖繪制時左上角的起點坐標public CustomView(Context context) {this(context, null);}public CustomView(Context context, AttributeSet attrs) {super(context, attrs);mContext = context;// 初始化畫筆 initPaint();// 初始化資源 initRes(context);}/*** 初始化畫筆*/private void initPaint() {// 實例化畫筆mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);// 設置顏色過濾mPaint.setColorFilter(new LightingColorFilter(0xFFFF00FF, 0x00000000));}/*** 初始化資源*/private void initRes(Context context) {// 獲取位圖bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a);/** 計算位圖繪制時左上角的坐標使其位于屏幕中心* 屏幕坐標x軸向左偏移位圖一半的寬度* 屏幕坐標y軸向上偏移位圖一半的高度*/x = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;y = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 繪制位圖 canvas.drawBitmap(bitmap, x, y, mPaint);} }

運行后你會發現綠色確實是沒了但是原來偏綠的部分現在居然成了紅色,為毛!敬請關注下一系列設計色彩文章!!哈哈哈!!當LightingColorFilter(0xFFFFFFFF, 0x00000000)的時候原圖是不會有任何改變的,如果我們想增加紅色的值,那么LightingColorFilter(0xFFFFFFFF, 0x00XX0000)就好,其中XX取值為00至FF。那么這個方法有什么存在的意義呢?存在必定合理,這個方法存在一定是有它可用之處的,前些天有個盆友在群里問點擊一個圖片如何直接改變它的顏色而不是為他多準備另一張點擊效果的圖片,這種情況下該方法就派上用場了!如下圖一個灰色的星星,我們點擊后讓它變成黃色


代碼如下,注釋很清楚我就不再多說了:

public class CustomView extends View {private Paint mPaint;// 畫筆private Context mContext;// 上下文環境引用private Bitmap bitmap;// 位圖private int x, y;// 位圖繪制時左上角的起點坐標private boolean isClick;// 用來標識控件是否被點擊過public CustomView(Context context) {this(context, null);}public CustomView(Context context, AttributeSet attrs) {super(context, attrs);mContext = context;// 初始化畫筆 initPaint();// 初始化資源 initRes(context);setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {/** 判斷控件是否被點擊過*/if (isClick) {// 如果已經被點擊了則點擊時設置顏色過濾為空還原本色mPaint.setColorFilter(null);isClick = false;} else {// 如果未被點擊則點擊時設置顏色過濾后為黃色mPaint.setColorFilter(new LightingColorFilter(0xFFFFFFFF, 0X00FFFF00));isClick = true;}// 記得重繪 invalidate();}});}/*** 初始化畫筆*/private void initPaint() {// 實例化畫筆mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);}/*** 初始化資源*/private void initRes(Context context) {// 獲取位圖bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a2);/** 計算位圖繪制時左上角的坐標使其位于屏幕中心* 屏幕坐標x軸向左偏移位圖一半的寬度* 屏幕坐標y軸向上偏移位圖一半的高度*/x = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;y = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 繪制位圖 canvas.drawBitmap(bitmap, x, y, mPaint);} }

運行后點擊星星即可變成黃色再點擊變回灰色,當我們不想要顏色過濾的效果時,setColorFilter(null)并重繪視圖即可!那么為什么要叫光照顏色過濾呢?原因很簡單,因為它所呈現的效果就像有色光照在物體上染色一樣~~~哎,不說這方法了,看下一個也是最后一個ColorFilter的子類。

PorterDuffColorFilter

?

PorterDuffColorFilter跟LightingColorFilter一樣,只有一個構造方法

PorterDuffColorFilter(int color, PorterDuff.Mode mode)

這個構造方法也接受兩個值,一個是16進制表示的顏色值這個很好理解,而另一個是PorterDuff內部類Mode中的一個常量值,這個值表示混合模式。那么什么是混合模式呢?混合混合必定是有兩種東西混才行,第一種就是我們設置的color值而第二種當然就是我們畫布上的元素了!,比如這里我們把Color的值設為紅色,而模式設為PorterDuff.Mode.DARKEN變暗:

public class CustomView extends View {private Paint mPaint;// 畫筆private Context mContext;// 上下文環境引用private Bitmap bitmap;// 位圖private int x, y;// 位圖繪制時左上角的起點坐標public CustomView(Context context) {this(context, null);}public CustomView(Context context, AttributeSet attrs) {super(context, attrs);mContext = context;// 初始化畫筆 initPaint();// 初始化資源 initRes(context);}/*** 初始化畫筆*/private void initPaint() {// 實例化畫筆mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);// 設置顏色過濾mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.DARKEN));}/*** 初始化資源*/private void initRes(Context context) {// 獲取位圖bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a);/** 計算位圖繪制時左上角的坐標使其位于屏幕中心* 屏幕坐標x軸向左偏移位圖一半的寬度* 屏幕坐標y軸向上偏移位圖一半的高度*/x = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;y = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 繪制位圖 canvas.drawBitmap(bitmap, x, y, mPaint);} }

我們嘗試在畫布上Draw剛才的那張圖片看看:

?

變暗了……也變紅了……這就是PorterDuff.Mode.DARKEN模式給我們的效果,當然PorterDuff.Mode還有其他很多的混合模式,大家可以嘗試,但是這里要注意一點,PorterDuff.Mode中的模式不僅僅是應用于圖像色彩混合,還應用于圖形混合,比如PorterDuff.Mode.DST_OUT就表示裁剪混合圖,如果我們在PorterDuffColorFilter中強行設置這些圖形混合的模式將不會看到任何對應的效果,關于圖形混合我們將在下面詳解。

setXfermode(Xfermode xfermode)

[只對圖像有效,drawBitmapXXX()]

Xfermode國外有大神稱之為過渡模式,這種翻譯比較貼切但恐怕不易理解,大家也可以直接稱之為圖像混合模式,因為所謂的“過渡”其實就是圖像混合的一種,這個方法跟我們上面講到的setColorFilter蠻相似的

同理可得其必然有一定的子類去實現一些方法供我們使用,查看API文檔發現其果然有三個子類:AvoidXfermode, PixelXorXfermode和PorterDuffXfermode,這三個子類實現的功能要比setColorFilter的三個子類復雜得多,主要是是涉及到圖像處理的一些知識可能對大家來說會比較難以理解,不過我會盡量以通俗的方式闡述它們的作用,那好先來看看我們的第一個子類

AvoidXfermode

首先我要告訴大家的是這個API因為不支持硬件加速在API 16已經過時了(大家可以在HardwareAccel查看那些方法不支持硬件加速)……如果想在高于API 16的機子上測試這玩意,必須現在應用或手機設置中關閉硬件加速,在應用中我們可以通過在AndroidManifest.xml文件中設置application節點下的android:hardwareAccelerated屬性為false來關閉硬件加速:

<applicationandroid:allowBackup="true"android:hardwareAccelerated="false"android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" ><activityandroid:name="com.aigestudio.customviewdemo.activities.MainActivity"android:label="@string/app_name" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity> </application>

AvoidXfermode只有一個含參的構造方法AvoidXfermode(int opColor, int tolerance, AvoidXfermode.Mode mode),其具體實現和ColorFilter一樣都被封裝在C/C++內,它怎么實現我們不管我們只要知道這玩意怎么用就行對吧。AvoidXfermode有三個參數,第一個opColor表示一個16進制的可以帶透明通道的顏色值例如0x12345678,第二個參數tolerance表示容差值,那么什么是容差呢?你可以理解為一個可以標識“精確”或“模糊”的東西,待會我們細講,最后一個參數表示AvoidXfermode的具體模式,其可選值只有兩個:AvoidXfermode.Mode.AVOID或者AvoidXfermode.Mode.TARGET,兩者的意思也非常簡單,我們先來看

AvoidXfermode.Mode.TARGET

在該模式下Android會判斷畫布上的顏色是否會有跟opColor不一樣的顏色,比如我opColor是紅色,那么在TARGET模式下就會去判斷我們的畫布上是否有存在紅色的地方,如果有,則把該區域“染”上一層我們畫筆定義的顏色,否則不“染”色,而tolerance容差值則表示畫布上的像素和我們定義的紅色之間的差別該是多少的時候才去“染”的,比如當前畫布有一個像素的色值是(200, 20, 13),而我們的紅色值為(255, 0, 0),當tolerance容差值為255時,即便(200, 20, 13)并不等于紅色值也會被“染”色,容差值越大“染”色范圍越廣反之則反,空說無憑我們來看看具體的實現和效果:

public class CustomView extends View {private Paint mPaint;// 畫筆private Context mContext;// 上下文環境引用private Bitmap bitmap;// 位圖private AvoidXfermode avoidXfermode;// AV模式private int x, y, w, h;// 位圖繪制時左上角的起點坐標public CustomView(Context context) {this(context, null);}public CustomView(Context context, AttributeSet attrs) {super(context, attrs);mContext = context;// 初始化畫筆 initPaint();// 初始化資源 initRes(context);}/*** 初始化畫筆*/private void initPaint() {// 實例化畫筆mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);/** 當畫布中有跟0XFFFFFFFF色不一樣的地方時候才“染”色*/avoidXfermode = new AvoidXfermode(0XFFFFFFFF, 0, AvoidXfermode.Mode.TARGET);}/*** 初始化資源*/private void initRes(Context context) {// 獲取位圖bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a);/** 計算位圖繪制時左上角的坐標使其位于屏幕中心* 屏幕坐標x軸向左偏移位圖一半的寬度* 屏幕坐標y軸向上偏移位圖一半的高度*/x = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;y = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;w = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 + bitmap.getWidth() / 2;h = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 + bitmap.getHeight() / 2;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 先繪制位圖 canvas.drawBitmap(bitmap, x, y, mPaint);// “染”什么色是由我們自己決定的mPaint.setARGB(255, 211, 53, 243);// 設置AV模式 mPaint.setXfermode(avoidXfermode);// 畫一個位圖大小一樣的矩形 canvas.drawRect(x, y, w, h, mPaint);} }

在高于API 16的測試機上會得到一個矩形的色塊(API 16+的都類似,改ROM和關閉了硬件加速的除外):

?

我們再用低于API 16(或高于API 16但關閉了硬件加速)的測試機運行就會得到另一個不同的效果:

?

大家可以看到,在我們的模式為TARGET容差值為0的時候此時只有當圖片中像色顏色值為0XFFFFFFFF的地方才會被染色,而其他地方不會有改變

AvoidXfermode(0XFFFFFFFF, 0, AvoidXfermode.Mode.TARGET):

而當容差值為255的時候只要是跟0XFFFFFFFF有點接近的地方都會被染色

而另外一種模式

AvoidXfermode.Mode.AVOID

則與TARGET恰恰相反,TARGET是我們指定的顏色是否與畫布的顏色一樣,而AVOID是我們指定的顏色是否與畫布不一樣,其他的都與TARGET類似

?

AvoidXfermode(0XFFFFFFFF, 0, AvoidXfermode.Mode.AVOID):

當模式為AVOID容差值為0時,只有當圖片中像素顏色值與0XFFFFFFFF完全不一樣的地方才會被染色

AvoidXfermode(0XFFFFFFFF, 255, AvoidXfermode.Mode.AVOID):

當容差值為255時,只要與0XFFFFFFFF稍微有點不一樣的地方就會被染色

那么這玩意究竟有什么用呢?比如說當我們只想在白色的區域畫點東西或者想把白色區域的地方替換為另一張圖片的時候就可以采取這種方式!

Xfermode的第二個子類

?

PixelXorXfermode

與AvoidXfermode一樣也在API 16過時了,該類也提供了一個含參的構造方法PixelXorXfermode(int opColor),該類的計算實現很簡單,從官方給出的計算公式來看就是:op ^ src ^ dst,像素色值的按位異或運算,如果大家感興趣,可以自己用一個純色去嘗試,并自己計算異或運算的值是否與得出的顏色值一樣,這里我就不講了,Because it was deprecated and useless。

Xfermode的最后一個子類也是惟一一個沒有過時且沿用至今的子類

PorterDuffXfermode

該類同樣有且只有一個含參的構造方法PorterDuffXfermode(PorterDuff.Mode mode),這個PorterDuff.Mode大家看后是否會有些面熟,它跟上面我們講ColorFilter時候用到的PorterDuff.Mode是一樣的!麻雀雖小五臟俱全,雖說構造方法的簽名列表里只有一個PorterDuff.Mode的參數,但是它可以實現很多酷斃的圖形效果!!而PorterDuffXfermode就是圖形混合模式的意思,其概念最早來自于SIGGRAPH的Tomas Proter和Tom Duff,混合圖形的概念極大地推動了圖形圖像學的發展,延伸到計算機圖形圖像學像Adobe和AutoDesk公司著名的多款設計軟件都可以說一定程度上受到影響,而我們PorterDuffXfermode的名字也來源于這倆人的人名組合PorterDuff,那PorterDuffXfermode能做些什么呢?我們先來看一張API DEMO里的圖片:

這張圖片從一定程度上形象地說明了圖形混合的作用,兩個圖形一圓一方通過一定的計算產生不同的組合效果,在API中Android為我們提供了18種(比上圖多了兩種ADD和OVERLAY)模式:

來定義不同的混合效果,這18種模式Android還為我們提供了它們的計算方式比如LIGHTEN的計算方式為[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)],其中Sa全稱為Source alpha表示源圖的Alpha通道;Sc全稱為Source color表示源圖的顏色;Da全稱為Destination alpha表示目標圖的Alpha通道;Dc全稱為Destination color表示目標圖的顏色,細心的朋友會發現“[……]”里分為兩部分,其中“,”前的部分為“Sa + Da - Sa*Da”這一部分的值代表計算后的Alpha通道而“,”后的部分為“Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)”這一部分的值代表計算后的顏色值,圖形混合后的圖片依靠這個矢量來計算ARGB的值,如果大家感興趣可以查看維基百科中對Alpha合成的解釋:http://en.wikipedia.org/wiki/Alpha_compositing。作為一個猿,我們不需要知道復雜的圖形學計算但是一定要知道這些模式會為我們提供怎樣的效果,當大家看到上面API DEMO給出的效果時一定會覺得PorterDuffXfermode其實就是簡單的圖形交并集計算,比如重疊的部分刪掉或者疊加等等,事實上呢!PorterDuffXfermode的計算絕非是根據于此!上面我們也說了PorterDuffXfermode的計算是要根據具體的Alpha值和RGB值的,既然如此,我們就來看一個比API DEMO稍微復雜的例子來更有力地說明PorterDuffXfermode是如何工作而我們又能用它做些什么,在這個例子中我將用到兩個帶有Alpha通道的漸變圖形Bitmap:

我們將在不同的模式下混合這兩個Bitmap來看看這兩個漸變色的顏色值在不同的混合模式下究竟發生了什么?先看看我們的測試代碼:

@TargetApi(Build.VERSION_CODES.HONEYCOMB) public class PorterDuffView extends View {/** PorterDuff模式常量* 可以在此更改不同的模式測試*/private static final PorterDuff.Mode MODE = PorterDuff.Mode.ADD;private static final int RECT_SIZE_SMALL = 400;// 左右上方示例漸變正方形的尺寸大小private static final int RECT_SIZE_BIG = 800;// 中間測試漸變正方形的尺寸大小private Paint mPaint;// 畫筆private PorterDuffBO porterDuffBO;// PorterDuffView類的業務對象private PorterDuffXfermode porterDuffXfermode;// 圖形混合模式private int screenW, screenH;// 屏幕尺寸private int s_l, s_t;// 左上方正方形的原點坐標private int d_l, d_t;// 右上方正方形的原點坐標private int rectX, rectY;// 中間正方形的原點坐標public PorterDuffView(Context context, AttributeSet attrs) {super(context, attrs);// 實例化畫筆并設置抗鋸齒mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);// 實例化業務對象porterDuffBO = new PorterDuffBO();// 實例化混合模式porterDuffXfermode = new PorterDuffXfermode(MODE);// 計算坐標 calu(context);}/*** 計算坐標* * @param context* 上下文環境引用*/private void calu(Context context) {// 獲取包含屏幕尺寸的數組int[] screenSize = MeasureUtil.getScreenSize((Activity) context);// 獲取屏幕尺寸screenW = screenSize[0];screenH = screenSize[1];// 計算左上方正方形原點坐標s_l = 0;s_t = 0;// 計算右上方正方形原點坐標d_l = screenW - RECT_SIZE_SMALL;d_t = 0;// 計算中間方正方形原點坐標rectX = screenW / 2 - RECT_SIZE_BIG / 2;rectY = RECT_SIZE_SMALL + (screenH - RECT_SIZE_SMALL) / 2 - RECT_SIZE_BIG / 2;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 設置畫布顏色為黑色以便我們更好地觀察 canvas.drawColor(Color.BLACK);// 設置業務對象尺寸值計算生成左右上方的漸變方形 porterDuffBO.setSize(RECT_SIZE_SMALL);/** 畫出左右上方兩個正方形* 其中左邊的的為src右邊的為dis*/canvas.drawBitmap(porterDuffBO.initSrcBitmap(), s_l, s_t, mPaint);canvas.drawBitmap(porterDuffBO.initDisBitmap(), d_l, d_t, mPaint);/** 將繪制操作保存到新的圖層(更官方的說法應該是離屏緩存)我們將在1/3中學習到Canvas的全部用法這里就先follow me*/int sc = canvas.saveLayer(0, 0, screenW, screenH, null, Canvas.ALL_SAVE_FLAG);// 重新設置業務對象尺寸值計算生成中間的漸變方形 porterDuffBO.setSize(RECT_SIZE_BIG);// 先繪制dis目標圖 canvas.drawBitmap(porterDuffBO.initDisBitmap(), rectX, rectY, mPaint);// 設置混合模式 mPaint.setXfermode(porterDuffXfermode);// 再繪制src源圖 canvas.drawBitmap(porterDuffBO.initSrcBitmap(), rectX, rectY, mPaint);// 還原混合模式mPaint.setXfermode(null);// 還原畫布 canvas.restoreToCount(sc);} }

代碼中我們使用到了View的離屏緩沖,也通俗地稱之為層,這個概念很簡單,我們在繪圖的時候新建一個“層”,所有的繪制操作都在該層上而不影響該層以外的圖像,比如代碼中我們在繪制了畫布顏色和左右上方兩個方形后就新建了一個圖層來繪制中間的大正方形,這個方形和左右上方的方形是在兩個不同的層上的:

?

注:圖中所顯示色彩效果與我們的代碼不同,上圖只為演示圖層概念

當我們繪制完成后要通過restore將所有緩沖(層)中的繪制操作還原到畫布以結束繪制,具體關于畫布的知識在自定義控件其實很簡單1/3,這里就不多說了,下面我們看具體各種模式的計算效果

PS:Src為源圖像,意為將要繪制的圖像;Dis為目標圖像,意為我們將要把源圖像繪制到的圖像……是不是感腳很拗口 = = !Fuck……意會意會~~

?

PorterDuff.Mode.ADD

計算方式:Saturate(S + D);Chinese:飽和相加

從計算方式和顯示的結果我們可以看到,ADD模式簡單來說就是對圖像飽和度進行相加,這個模式在應用中不常用,我唯一一次使用它是通過代碼控制RGB通道的融合生成圖片。

?

PorterDuff.Mode.CLEAR

計算方式:[0, 0];Chinese:清除

清除圖像,很好理解不扯了。

?

PorterDuff.Mode.DARKEN

計算方式:[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)];Chinese:變暗

這個模式計算方式目測很復雜,其實效果很好理解,兩個圖像混合,較深的顏色總是會覆蓋較淺的顏色,如果兩者深淺相同則混合,如圖,黃色覆蓋了紅色而藍色和青色因為是跟透明混合所以不變。細心的朋友會發現青色和黃色之間有一層類似橙色的過渡色,這就是混合的結果。在實際的測試中源圖和目標圖的DARKEN混合偶爾會有相反的結果比如紅色覆蓋了黃色,這源于Android對顏色值“深淺”的定義,我暫時沒有在官方查到有關資料不知道是否與圖形圖像學一致。DARKEN模式的應用在圖像色彩方面比較廣泛我們可以利用其特性來獲得不同的成像效果,這點與之前介紹的ColorFilter有點類似。

該模式處理過后,會感覺效果變暗,即進行對應像素的比較,取較暗值,如果色值相同則進行混合;

從算法上看,alpha值變大,色值上如果都不透明則取較暗值,非完全不透明情況下使用上面算法進行計算,受到源圖和目標圖對應色值和alpha值影響;

PorterDuff.Mode.DST

計算方式:[Da, Dc];Chinese:只繪制目標圖像

如Chinese所說,很好理解。

PorterDuff.Mode.DST_ATOP

計算方式:[Sa, Sa * Dc + Sc * (1 - Da)];Chinese:在源圖像和目標圖像相交的地方繪制目標圖像而在不相交的地方繪制源圖像

PorterDuff.Mode.DST_IN

計算方式:[Sa * Da, Sa * Dc];Chinese:只在源圖像和目標圖像相交的地方繪制目標圖像

PorterDuff.Mode.DST_OUT

計算方式:[Da * (1 - Sa), Dc * (1 - Sa)];Chinese:只在源圖像和目標圖像不相交的地方繪制目標圖像

PorterDuff.Mode.DST_OVER

計算方式:[Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc];Chinese:在源圖像的上方繪制目標圖像

這個就不說啦,就是兩個圖片誰在上誰在下的意思

PorterDuff.Mode.LIGHTEN

計算方式:[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)];Chinese:變亮

可以和 DARKEN 對比起來看,DARKEN 的目的是變暗,LIGHTEN 的目的則是變亮,如果在均完全不透明的情況下 ,色值取源色值和目標色值中的較大值

PorterDuff.Mode.MULTIPLY

計算方式:[Sa * Da, Sc * Dc];Chinese:正片疊底

該模式通俗的計算方式很簡單,源圖像素顏色值乘以目標圖像素顏色值除以255即得混合后圖像像素的顏色值,該模式在設計領域應用廣泛,因為其特性黑色與任何顏色混合都會得黑色,在手繪的上色、三維動畫的UV貼圖繪制都有應用,具體效果大家自己嘗試我就不說了

?

PorterDuff.Mode.OVERLAY

計算方式:未給出;Chinese:疊加

這個模式沒有在官方的API DEMO中給出,谷歌也沒有給出其計算方式,在實際效果中其對亮色和暗色不起作用,也就是說黑白色無效,它會將源色與目標色混合產生一種中間色,這種中間色生成的規律也很簡單,如果源色比目標色暗,那么讓目標色的顏色倍增否則顏色遞減。

PorterDuff.Mode.SCREEN

計算方式:[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc];Chinese:濾色

計算方式我不解釋了,濾色產生的效果我認為是Android提供的幾個色彩混合模式中最好的,它可以讓圖像焦媃幻化,有一種色調均和的感覺:

PorterDuff.Mode.SRC

計算方式:[Sa, Sc];Chinese:顯示源圖

只繪制源圖,SRC類的模式跟DIS的其實差不多就不多說了

PorterDuff.Mode.SRC_ATOP

計算方式:[Da, Sc * Da + (1 - Sa) * Dc];Chinese:在源圖像和目標圖像相交的地方繪制源圖像,在不相交的地方繪制目標圖像

PorterDuff.Mode.SRC_IN

計算方式:[Sa * Da, Sc * Da];Chinese:只在源圖像和目標圖像相交的地方繪制源圖像

PorterDuff.Mode.SRC_OUT

計算方式:[Sa * (1 - Da), Sc * (1 - Da)];Chinese:只在源圖像和目標圖像不相交的地方繪制源圖像

PorterDuff.Mode.SRC_OVER

計算方式:[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc];Chinese:在目標圖像的頂部繪制源圖像

?

PorterDuff.Mode.XOR

計算方式:[Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc];Chinese:在源圖像和目標圖像重疊之外的任何地方繪制他們,而在重疊的地方不繪制任何內容

?

一張詳細的圖,看了就明白

更多資料請參考

http://www.youtube.com/watch?v=duefsFTJXzc&feature=results_video&playnext=1&list=PL01724209851DF753

http://ssp.impulsetrain.com/porterduff.html

http://stackoverflow.com/questions/8280027/what-does-porterduff-mode-mean-in-android-graphics-what-does-it-do

?

Paint類中我們還有一個方法沒講

setShader(Shader shader)

這個方法呢其實也沒有什么特別的,那么為什么我們要把它單獨分離出來講那么異類呢?難道它賄賂了我嗎?顯然不是的,哥視金錢如糞土(我的要求很低,只需要一克反物質即可)!怎么可能做出如此下三濫的事情!之所以要把這貨單獨拿出來是為了引出Android在圖形變換中非常重要的一個類!這個類是什么呢?我也先不說,咱還是先來看看Shader:

?

Shader類呢也是個灰常灰常簡單的類,它有五個子類,像PathEffect一樣每個子類都實現了一種Shader,Shader在三維軟件中我們稱之為著色器,其作用嘛就像它的名字一樣是來給圖像著色的或者更通俗的說法是上色!這么說該懂了吧!再不懂去廁所哭去!這五個Shader里最異類的是BitmapShader,因為只有它是允許我們載入一張圖片來給圖像著色,那我們還是先來看看這個怪胎吧

BitmapShader

只有一個含參的構造方法BitmapShader (Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)而其他的四個兄弟姐妹呢都有兩個!它只有一個蛋,又一魂談!那好吧,我們來看看它是什么個效果,順便呢也學習一下Shader的用法先,來看我們熟悉的代碼:

public class ShaderView extends View {private static final int RECT_SIZE = 400;// 矩形尺寸的一半private Paint mPaint;// 畫筆private int left, top, right, bottom;// 矩形坐上右下坐標public ShaderView(Context context, AttributeSet attrs) {super(context, attrs);// 獲取屏幕尺寸數據int[] screenSize = MeasureUtil.getScreenSize((Activity) context);// 獲取屏幕中點坐標int screenX = screenSize[0] / 2;int screenY = screenSize[1] / 2;// 計算矩形左上右下坐標值left = screenX - RECT_SIZE;top = screenY - RECT_SIZE;right = screenX + RECT_SIZE;bottom = screenY + RECT_SIZE;// 實例化畫筆mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);// 獲取位圖Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.a);// 設置著色器mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));}@Overrideprotected void onDraw(Canvas canvas) {// 繪制矩形 canvas.drawRect(left, top, right, bottom, mPaint);} }

如果上面我們沒有設置Shader:

mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));

那么我們Draw出來的圖像一定是一個位于屏幕正中黑色的正方形,但是我們設置了Shader后還是一樣的嗎?看看效果:

?

我靠!這什么玩意!罪過罪過!真是看不懂!別急,Shader.TileMode里有三種模式:CLAMP、MIRROR和REPETA,我們看看其他兩種模式是什么效果呢:

mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR));


誒?這效果還能接受,我們還能看得出一點效果,說白了就是上下左右的鏡像而已,那再看看REPETA:

mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));


這個就更簡單了,明顯的一個重復效果,而REPEAT也就是重復的意思,同理MIRROR也就是鏡像的意思,這個很好理解吧。那第一個CLAMP模式究竟特么的是什么東西呢?看效果根本看不出來,我們不妨換個思維,BitmapShader (Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)的第一個參數是位圖這個很顯然,而后兩個參數則分別表示XY方向上的著色模式,既然可以分開設置,那么我們是不是可以這樣設置一個Shader呢?

mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.MIRROR));

也就是說我們在X軸方向上采取CLAMP模式而Y軸方向上采取MIRROR模式,那么這樣肯定是可行的撒:

?

大家可以看到圖像分為兩部分左邊呢Y軸鏡像了,而右邊像是被拉伸了一樣怪怪的!其實CLAMP的意思就是邊緣拉伸的意思,比如上圖中左邊Y軸鏡像了,而右邊會緊挨著左邊將圖像邊緣上的第一個像素沿X軸復制!產生一種被拉伸的效果!就像扯蛋,不過這里扯的不是蛋而是圖像邊緣的第一個像素,就是這么簡單。但是!作為一個嚴謹的男人必須要又一個嚴謹的態度!這時我就會想,如果兩種模式互換會怎樣呢?比如:

mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.MIRROR, Shader.TileMode.CLAMP));

這樣來看,應該是X軸會鏡像而Y軸會拉伸對吧,看看效果:

?

這…………好像跟我們想象中的不大一樣唉……是我們做錯了嗎?不是的,結合上一個例子大家有沒有注意BitmapShader是先應用了Y軸的模式而X軸是后應用的!所以著色是先在Y軸拉伸了然后再沿著X軸重復對吧(陰笑ing……)?!

來看看另一個Shader叫做
LinearGradient

線性漸變,顧名思義這錘子玩意就是來畫漸變的,實際上Shader的五個子類中除了上面我們說的那個怪胎,還有個變形金剛ComposeShader外其余三個都是漸變只是效果不同而已,而這個LinearGradient線性漸變一說大家估計都懂,先來看張效果圖:

是不是秒懂了!恩,說明你頭腦簡單,這個實現也很簡單,具體代碼跟上面的BitmapShader一樣只是把BitmapShader換成了LinearGradient而已:

mPaint.setShader(new LinearGradient(left, top, right, bottom, Color.RED, Color.YELLOW, Shader.TileMode.REPEAT));

上面我們提到過除了BitmapShader外其他子類都有兩個構造方法,上面我們用到了

LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile)

這是LinearGradient最簡單的一個構造方法,參數雖多其實很好理解x0和y0表示漸變的起點坐標而x1和y1則表示漸變的終點坐標,這兩點都是相對于屏幕坐標系而言的,而color0和color1則表示起點的顏色和終點的顏色,這些即便是213也能懂 - - ……Shader.TileMode上面我們給的是REPEAT重復但是并沒有任何效果,這時因為我們漸變的起點和終點都落在了圖形的兩端,整個漸變shader已經填充了圖形所以不起作用,如果我們改改,把終點坐標變一下:

mPaint.setShader(new LinearGradient(left, top, right - RECT_SIZE, bottom - RECT_SIZE, Color.RED, Color.YELLOW, Shader.TileMode.REPEAT));

此時我們漸變終點坐標落在了圖形的終點上,根據我們的REPEAT模式,會呈現一個漸變重復的效果:

?

僅僅兩種顏色的漸變根本無法滿足我們身體的欲望,太單調乏味!我們是不是可以定義多種顏色漸變呢?答案是必須的,LinearGradient的另一個構造方法

LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile)

就為我們實現了這么一個功能:

mPaint.setShader(new LinearGradient(left, top, right, bottom, new int[] { Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE }, new float[] { 0, 0.1F, 0.5F, 0.7F, 0.8F }, Shader.TileMode.MIRROR));

?

前面四個參數也是定義坐標的不扯了colors是一個int型數組,我們用來定義所有漸變的顏色,positions表示的是漸變的相對區域,其取值只有0到1,上面的代碼中我們定義了一個[0, 0.1F, 0.5F, 0.7F, 0.8F],意思就是紅色到黃色的漸變起點坐標在整個漸變區域(left, top, right, bottom定義了漸變的區域)的起點,而終點則在漸變區域長度 * 10%的地方,而黃色到綠色呢則從漸變區域10%開始到50%的地方以此類推,positions可以為空:

mPaint.setShader(new LinearGradient(left, top, right, bottom, new int[] { Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE }, null, Shader.TileMode.MIRROR));

為空時各種顏色的漸變將會均分整個漸變區域:

SweepGradient
的意思是梯度漸變,也稱之為掃描式漸變,因為其效果有點類似雷達的掃描效果,他也有兩個構造方法:

SweepGradient(float cx, float cy, int color0, int color1)

其實都跟LinearGradient差不多的,簡直沒什么可說的,直接上效果跳過無聊的講解:

mPaint.setShader(new SweepGradient(screenX, screenY, Color.RED, Color.YELLOW));

RadialGradient

?

環形渲染,簡單點就是個圓形中心向四周漸變的效果

RadialGradient (float centerX, float centerY, float radius, int centerColor, int edgeColor, Shader.TileMode tileMode) RadialGradient (float centerX, float centerY, float radius, int centerColor, int edgeColor, Shader.TileMode tileMode)

ComposeShader

就是組合Shader的意思,顧名思義就是兩個Shader組合在一起作為一個新Shader……老掉牙的劇情是吧!同樣,這錘子玩意也有兩個構造方法

ComposeShader (Shader shaderA, Shader shaderB, Xfermode mode) ComposeShader (Shader shaderA, Shader shaderB, PorterDuff.Mode mode)

兩個都差不多的,只不過一個指定了只能用PorterDuff的混合模式而另一個只要是Xfermode下的混合模式都沒問題!

實例

最上面的是BitmapShader效果圖;第二排的左邊是LinearGradient的效果圖;第二排的右邊是RadialGradient的效果圖;第三排的左邊是ComposeShader的效果圖(LinearGradient與RadialGradient的混合效果);第三排的右邊是SweepGradient的效果圖。

MyView.Java源碼 package com.example.android_imageshader;import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ComposeShader; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.RadialGradient; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.SweepGradient; import android.graphics.drawable.BitmapDrawable; import android.view.View;@SuppressLint({ "DrawAllocation", "DrawAllocation", "DrawAllocation" }) public class MyView extends View {Bitmap mBitmap = null; //Bitmap對象 Shader mBitmapShader = null; //Bitmap渲染對象Shader mLinearGradient = null; //線性漸變渲染對象Shader mComposeShader = null; //混合渲染對象Shader mRadialGradient = null; //環形渲染對象Shader mSweepGradient = null; //梯度渲染對象public MyView(Context context) {super(context);//加載圖像資源mBitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.snow)).getBitmap();//創建Bitmap渲染對象mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.MIRROR);//創建線性渲染對象int mColorLinear[] = {Color.RED, Color.GREEN, Color.BLUE, Color.WHITE}; mLinearGradient = new LinearGradient(0, 0, 100, 100, mColorLinear, null, Shader.TileMode.REPEAT);//創建環形渲染對象int mColorRadial[] = {Color.GREEN, Color.RED, Color.BLUE, Color.WHITE};mRadialGradient = new RadialGradient(350, 325, 75, mColorRadial, null, Shader.TileMode.REPEAT);//創建混合渲染對象mComposeShader = new ComposeShader(mLinearGradient, mRadialGradient, PorterDuff.Mode.DARKEN);//創建梯形渲染對象int mColorSweep[] = {Color.GREEN, Color.RED, Color.BLUE, Color.YELLOW, Color.GREEN};mSweepGradient = new SweepGradient(370, 495, mColorSweep, null); }public void onDraw(Canvas canvas) {super.onDraw(canvas);Paint mPaint = new Paint();canvas.drawColor(Color.GRAY); //背景置為灰色//繪制Bitmap渲染的橢圓 mPaint.setShader(mBitmapShader);canvas.drawOval(new RectF(90, 20, 90+mBitmap.getWidth(), 20+mBitmap.getHeight()), mPaint);//繪制線性漸變的矩形 mPaint.setShader(mLinearGradient);canvas.drawRect(10, 250, 250, 400, mPaint);//繪制環形漸變的圓 mPaint.setShader(mRadialGradient);canvas.drawCircle(350, 325, 75, mPaint);//繪制混合漸變(線性與環形混合)的矩形 mPaint.setShader(mComposeShader);canvas.drawRect(10, 420, 250, 570, mPaint);//繪制梯形漸變的矩形 mPaint.setShader(mSweepGradient);canvas.drawRect(270, 420, 470, 570, mPaint);} }

?

注意BitmapShader是從畫布的左上方開始著色,回到我們剛才的問題,這個著色方式必須是這樣的么?顯然不是!在Shader類中有一對setter和getter方法:setLocalMatrix(Matrix localM)和getLocalMatrix(Matrix localM)我們可以利用它們來設置或獲取Shader的變換矩陣,比如上面的例子我還是繪制成一個邊長為800的矩形:

public class ShaderView extends View {private static final int RECT_SIZE = 400;// 矩形尺寸的一半private Paint mPaint;// 畫筆private int left, top, right, bottom;// 矩形坐上右下坐標private int screenX, screenY;public ShaderView(Context context, AttributeSet attrs) {super(context, attrs);// 獲取屏幕尺寸數據int[] screenSize = MeasureUtil.getScreenSize((Activity) context);// 獲取屏幕中點坐標screenX = screenSize[0] / 2;screenY = screenSize[1] / 2;// 計算矩形左上右下坐標值left = screenX - RECT_SIZE;top = screenY - RECT_SIZE;right = screenX + RECT_SIZE;bottom = screenY + RECT_SIZE;// 實例化畫筆mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);// 獲取位圖Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.a);// 實例化一個ShaderBitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);// 實例一個矩陣對象Matrix matrix = new Matrix();// 設置矩陣變換 matrix.setTranslate(left, top);// 設置Shader的變換矩陣 bitmapShader.setLocalMatrix(matrix);// 設置著色器 mPaint.setShader(bitmapShader);// mPaint.setShader(new LinearGradient(left, top, right - RECT_SIZE, bottom - RECT_SIZE, Color.RED, Color.YELLOW, Shader.TileMode.MIRROR));// mPaint.setShader(new LinearGradient(left, top, right, bottom, new int[] { Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE }, null, Shader.TileMode.MIRROR));// mPaint.setShader(new SweepGradient(screenX, screenY, Color.RED, Color.YELLOW));// mPaint.setShader(new SweepGradient(screenX, screenY, new int[] { Color.GREEN, Color.WHITE, Color.GREEN }, null)); }@Overrideprotected void onDraw(Canvas canvas) {// 繪制矩形 canvas.drawRect(left, top, right, bottom, mPaint);// canvas.drawRect(0, 0, screenX * 2, screenY * 2, mPaint); } }

不一樣的是我在給畫筆設置著色器前為我們的著色器設置了一個變換矩陣,讓我們的Shader依據自身的坐標→平移left個單位↓平移top個單位,也就是說原本shader的原點應該是畫布(注意不是屏幕!這里只是剛好畫布更屏幕重合了而已!切記!)的左上方[0,0]的位置,通過變換移至了[left,top]的位置,如果沒問題,Shader此時應該是剛好是從我們矩形的左上方開始著色:

Matrix

Matrix是一個3 x 3的矩陣,他對圖片的處理分為四個基本類型:

1、Translate————平移變換

2、Scale————縮放變換

3、Rotate————旋轉變換

4、Skew————錯切變換

在Android的API里對于每一種變換都提供了三種操作方式:set(用于設置Matrix中的值)、post(后乘,根據矩陣的原理,相當于左乘)、pre(先乘,相當于矩陣中的右乘)。默認時,這四種變換都是圍繞(0,0)點變換的,當然可以自定義圍繞的中心點,通常圍繞中心點。

首先說說平移,在對圖片處理的過程中,最常用的就是對圖片進行平移操作,該方法為setTranslate(),平移意味著在x軸和y軸上簡單地移動圖像。setTranslate方法采用兩個浮點數作為參數,表示在每個軸上移動的數量。第一個參數是圖像將在x軸上移動的數量,而第二個參數是圖像將在y軸上移動的數量。在x軸上使用正數進行平移將向右移動圖像,而使用負數將向左移動圖像。在y軸上使用正數進行平移將向下移動圖像,而使用負數將向上移動圖像。

再看縮放,Matrix類中另一個有用的方法是setScale方法。它采用兩個浮點數作為參數,分別表示在每個軸上所產生的縮放量。第一個參數是x軸的縮放比例,而第二個參數是y軸的縮放比例。如:matrix.setScale(1.5f,1);
比較復雜的就是圖片的旋轉了,內置的方法之一是setRotate方法。它采用一個浮點數表示旋轉的角度。圍繞默認點(0,0),正數將順時針旋轉圖像,而負數將逆時針旋轉圖像,其中默認點是圖像的左上角,如:

Matrix matrix = new Matrix();
matrix.setRotate(15);

另外,也可以使用旋轉的角度及圍繞的旋轉點作為參數調用setRotate方法。選擇圖像的中心點作為旋轉點,如:
matrix.setRotate(15,bmp.getWidth()/2,bmp.getHeight()/2);

對于錯切變換,在數學上又稱為Shear mapping(可譯為“剪切變換”)或者Transvection(縮并),它是一種比較特殊的線性變換。錯切變換的效果就是讓所有點的x坐標(或者y坐標)保持不變,而對應的y坐標(或者x坐標)則按比例發生平移,且平移的大小和該點到x軸(或y軸)的垂直距離成正比。錯切變換,屬于等面積變換,即一個形狀在錯切變換的前后,其面積是相等的。

對于程序中,一個特別有用的方法對是setScale和postTranslate,它們允許跨單個軸(或者兩個軸)翻轉圖像。如果以一個負數縮放,那么會將該圖像繪制到坐標系統的負值空間。由于(0,0)點位于左上角,使用x軸上的負數會導致向左繪制圖像。因此我們需要使用postTranslate方法,將圖像向右移動,如:

matrix.setScale(-1, 1);
matrix.postTranslate(bmp.getWidth(),0);

可以在y軸上做同樣的事情,翻轉圖像以使其倒置。通過將圖像圍繞兩個軸上的中心點旋轉180°,可以實現相同的效果,如
matrix.setScale(1, -1);
matrix.postTranslate(0, bmp.getHeight());

注意每一次setXXX方法都會把前面的變化重置

舉例:

?

1.matrix.preScale(0.5f, 1);???
2.matrix.preTranslate(10, 0);??
3.matrix.postScale(0.7f, 1);????
4.matrix.postTranslate(15, 0);??
等價于:
translate(10, 0) -> scale(0.5f, 1) -> scale(0.7f, 1) -> translate(15, 0)
注意:后調用的pre操作先執行,而后調用的post操作則后執行。

?

set方法一旦調用即會清空之前matrix中的所有變換,例如:
1.matrix.preScale(0.5f, 1);???
2.matrix.setScale(1, 0.6f);???
3.matrix.postScale(0.7f, 1);???
4.matrix.preTranslate(15, 0);??
等價于
translate(15, 0) -> scale(1, 0.6f) ->? scale(0.7f, 1)

?

matrix.preScale (0.5f, 1)將不起作用。

?

可以使用Matrix的getValues(float[])方法去驗證自己不確定的東西,同時呢,我們也可以使用Matrix的setValues(float[])方法來直接給Matrix設置一個矩陣數組

好了,對Matrix的一個簡單介紹就到這里,正如我所說,Matrix的應用是相當廣泛的,不僅僅是在我們的Shader,我們的canvas也有setMatrix(matrix)方法來設置矩陣變換,更常見的是在ImageView中對ImageView進行變換,當我們手指在屏幕上劃過一定的距離后根據這段距離來平移我們的控件,根據兩根手指之間拉伸的距離和相對于上一次旋轉的角度來縮放旋轉我們的圖片:

?

public class MatrixImageView extends ImageView {private static final int MODE_NONE = 0x00123;// 默認的觸摸模式private static final int MODE_DRAG = 0x00321;// 拖拽模式private static final int MODE_ZOOM = 0x00132;// 縮放or旋轉模式private int mode;// 當前的觸摸模式private float preMove = 1F;// 上一次手指移動的距離private float saveRotate = 0F;// 保存了的角度值private float rotate = 0F;// 旋轉的角度private float[] preEventCoor;// 上一次各觸摸點的坐標集合private PointF start, mid;// 起點、中點對象private Matrix currentMatrix, savedMatrix;// 當前和保存了的Matrix對象private Context mContext;// Fuck……public MatrixImageView(Context context, AttributeSet attrs) {super(context, attrs);this.mContext = context;// 初始化 init();}/*** 初始化*/private void init() {/** 實例化對象*/currentMatrix = new Matrix();savedMatrix = new Matrix();start = new PointF();mid = new PointF();// 模式初始化mode = MODE_NONE;/** 設置圖片資源*/Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.mylove);bitmap = Bitmap.createScaledBitmap(bitmap, MeasureUtil.getScreenSize((Activity) mContext)[0], MeasureUtil.getScreenSize((Activity) mContext)[1], true);setImageBitmap(bitmap);}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction() & MotionEvent.ACTION_MASK) {case MotionEvent.ACTION_DOWN:// 單點接觸屏幕時 savedMatrix.set(currentMatrix);start.set(event.getX(), event.getY());mode = MODE_DRAG;preEventCoor = null;break;case MotionEvent.ACTION_POINTER_DOWN:// 第二個點接觸屏幕時preMove = calSpacing(event);if (preMove > 10F) {savedMatrix.set(currentMatrix);calMidPoint(mid, event);mode = MODE_ZOOM;}preEventCoor = new float[4];preEventCoor[0] = event.getX(0);preEventCoor[1] = event.getX(1);preEventCoor[2] = event.getY(0);preEventCoor[3] = event.getY(1);saveRotate = calRotation(event);break;case MotionEvent.ACTION_UP:// 單點離開屏幕時case MotionEvent.ACTION_POINTER_UP:// 第二個點離開屏幕時mode = MODE_NONE;preEventCoor = null;break;case MotionEvent.ACTION_MOVE:// 觸摸點移動時/** 單點觸控拖拽平移*/if (mode == MODE_DRAG) {currentMatrix.set(savedMatrix);float dx = event.getX() - start.x;float dy = event.getY() - start.y;currentMatrix.postTranslate(dx, dy);}/** 兩點觸控拖放旋轉*/else if (mode == MODE_ZOOM && event.getPointerCount() == 2) {float currentMove = calSpacing(event);currentMatrix.set(savedMatrix);/** 指尖移動距離大于10F縮放*/if (currentMove > 10F) {float scale = currentMove / preMove;currentMatrix.postScale(scale, scale, mid.x, mid.y);}/** 保持兩點時旋轉*/if (preEventCoor != null) {rotate = calRotation(event);float r = rotate - saveRotate;currentMatrix.postRotate(r, getMeasuredWidth() / 2, getMeasuredHeight() / 2);}}break;}setImageMatrix(currentMatrix);return true;}/*** 計算兩個觸摸點間的距離*/private float calSpacing(MotionEvent event) {float x = event.getX(0) - event.getX(1);float y = event.getY(0) - event.getY(1);return (float) Math.sqrt(x * x + y * y);}/*** 計算兩個觸摸點的中點坐標*/private void calMidPoint(PointF point, MotionEvent event) {float x = event.getX(0) + event.getX(1);float y = event.getY(0) + event.getY(1);point.set(x / 2, y / 2);}/*** 計算旋轉角度* * @param 事件對象* @return 角度值*/private float calRotation(MotionEvent event) {double deltaX = (event.getX(0) - event.getX(1));double deltaY = (event.getY(0) - event.getY(1));double radius = Math.atan2(deltaY, deltaX);return (float) Math.toDegrees(radius);} }

記得在xml中設置我們MatrixImageView的scaleType="matrix":

<com.aigestudio.customviewdemo.views.MatrixImageViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="matrix" />

?

轉載于:https://www.cnblogs.com/krislight1105/p/5093708.html

總結

以上是生活随笔為你收集整理的转载爱哥自定义View系列--Paint详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。