2016年的第四篇案例妥色,比以往來得更晚一些...
博客的更新就像 2012 年的第一場雪一樣搪花,比以往來得更晚一些...
博主這段時(shí)間實(shí)在太忙了,本篇案例一直沒有更新嘹害,實(shí)在對不住大家...
案列篇系列包含了自定義 View 許多的知識點(diǎn)撮竿,希望大家能夠知其然知其所以然勃刨,靈活應(yīng)用争占,寫出屬于自己心動的控件贮尉。
下面來看看今天登場的是:
ColorPicker(顏色選擇器)
大家一定還記得 PhotoShop 中的顏色面板辱挥,It's very fashion . 曾經(jīng)幾時(shí)讽营,苦苦的思索它的實(shí)現(xiàn)過程 . . .
先來看看最終的效果圖:
具有以下效果:
空心小圓隨著手指的移動而移動
隨著手指的移動更改背景顏色
涉及到的知識點(diǎn):
Color.HSVToColor
Shader
interface
接下來就對涉及到的知識點(diǎn)以及效果進(jìn)行逐一的講解洋闽。
Color.HSVToColor
顏色是由 int 型的數(shù)表示颊郎,由 4 個(gè)字節(jié)組成带射,分別是 A R G B微渠,這個(gè) int 型的值是確定的搭幻,透明度的值只能存在 A 這個(gè)字節(jié)上,不能存在顏色的字節(jié)上逞盆。存儲的方式為 (alpha << 24) | (red << 16) | (green << 8) | blue 每一部分的取值范圍都是 0-255 檀蹋,0 表示沒有,255 表示填滿了云芦。不透明的黑色的值是 0xff000000俯逾,不透明的白色的值是 0xffffffff
方法預(yù)覽:
public static int HSVToColor(@Size(3) float hsv[]) {
return HSVToColor(0xFF, hsv);
}
把 HSV 的內(nèi)容轉(zhuǎn)化成 color贸桶,其中 alpha 設(shè)置成 0xff,參數(shù) hsv 有三個(gè)成員桌肴,hsv[0] 的范圍是 [0,360) 表示色彩皇筛,hsv[1] 范圍 [0,1] 表示飽和度,hsv[2] 范圍 [0,1] 表示值坠七,如果它們的值超出范圍水醋,那么它們會被截?cái)喑煞秶鷥?nèi)的值。
相關(guān)鏈接 RGB to HSV color conversion
文字的描述是比較抽象的灼捂,下面來看看一個(gè)例子:
hsv[1] (飽和度)hsv[2] (值) 不變的情況下离例,hsv[0] 逐漸增大,圓的色彩也在不斷的變化
hsv[0] (色彩)hsv[2] (值) 不變的情況下悉稠,hsv[1] 逐漸減小宫蛆,圓的飽和度也隨著減小 (效果類似透明度的變化)
hsv[0] (色彩)hsv[1] (飽和度) 不變的情況下,hsv[2] 逐漸減小的猛,圓的值也隨著減小 (逐漸轉(zhuǎn)變成黑色)
看看繪制 onDraw 的方法:
canvas.drawCircle(getWidth()/2, getHeight()/2, 200, colorWheelPaint);
圓心設(shè)置為控件的中心點(diǎn)耀盗,半徑為 200px 繪制圓。
監(jiān)聽 SeekBar 的進(jìn)度改變:
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
switch (seekBar.getId()) {
case R.id.sb_color:
mColorPicker.setHSVColor(i);
break;
}
}
動態(tài)設(shè)置色彩卦尊。接著看看 setHSVColor 方法:
重繪:
/**
* @param color 0~360
*/
public void setHSVColor(int color) {
colorHSV[0] = color;
colorWheelPaint.setColor(Color.HSVToColor(colorHSV));
postInvalidate();
}
源碼在文章的結(jié)尾處叛拷。
Shader
Shader 類專門用來渲染圖像以及一些幾何圖形。Shader 類與是一個(gè)空類岂却,它的功能的實(shí)現(xiàn)忿薇,主要是靠它的派生類來實(shí)現(xiàn)的。繼承關(guān)系如下:
Shader 類包括了 5 個(gè)直接子類:
BitmapShader 用于圖像渲染
LinearGradient 用于線性渲染
RadialGradient 用于環(huán)形渲染 (放射狀)
SweepGradient 用于梯度渲染(掃描狀)
ComposeShader 用于混合渲染
這里主要講解后三種渲染躏哩,如果對前面兩種渲染感興趣請鏈接:
RadialGradient
RadialGradient 放射漸變署浩,即它會向一個(gè)放射源一樣,向外放射扫尺。
構(gòu)造函數(shù):
RadialGradient(float centerX, float centerY, float radius, int centerColor, int edgeColor, Shader.TileMode tileMode)
//多色漸變
RadialGradient(float centerX, float centerY, float radius, int[] colors, float[] stops, Shader.TileMode tileMode)
1筋栋、 兩色漸變構(gòu)造函數(shù)使用實(shí)例
下面我們來看一下兩色漸變構(gòu)造函數(shù)的使用方法:
RadialGradient(float centerX, float centerY, float radius, int centerColor, int edgeColor, Shader.TileMode tileMode)
兩色漸變的構(gòu)造函數(shù)的各項(xiàng)參數(shù)意義如下:
centerX:漸變中心點(diǎn)X坐標(biāo)
centerY:漸變中心點(diǎn)Y坐標(biāo)
radius:漸變半徑
centerColor:漸變的起始顏色,即漸變中心點(diǎn)的顏色,取值類型必須是八位的0xAARRGGBB色值正驻!透明底Alpha值不能省略弊攘,不然不會
顯示出顏色。edgeColor:漸變結(jié)束時(shí)的顏色姑曙,即漸變圓邊緣的顏色襟交,同樣,取值類型必須是八位的0xAARRGGBB色值伤靠!
TileMode:用于指定當(dāng)控件區(qū)域大于指定的漸變區(qū)域時(shí)婿着,空白區(qū)域的顏色填充方式。
其中 TileMode 的取值有:
- TileMode.CLAMP 用邊緣色彩填充多余空間
- TileMode.REPEAT 重復(fù)原圖像來填充多余空間
- TileMode.MIRROR 重復(fù)使用鏡像模式的圖像來填充多余空間
來看個(gè)簡單的例子:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mRadialGradient = new RadialGradient(getWidth() / 2, getHeight() / 2, 200, 0xffff0000, 0xffffff00,
Shader.TileMode.REPEAT);
mPaint.setShader(mRadialGradient);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, 200, mPaint);
}
漸變的中心點(diǎn)為空間的中心點(diǎn),漸變半徑為 200px竟宋,漸變的起始顏色為紅色,漸變結(jié)束的顏色為黃色形纺,填充方式為重復(fù)原圖填充丘侠。注意我們畫的圓的大小與所構(gòu)造的放射漸變的大小是一樣的,所以不存在空白區(qū)域的填充問題逐样。
效果圖如下:
2蜗字、多色漸變構(gòu)造函數(shù)使用實(shí)例
多色漸變的構(gòu)造函數(shù)如下:
RadialGradient(float centerX, float centerY, float radius, int[] colors, float[] stops, Shader.TileMode tileMode)
這里與兩色漸變不同的是兩個(gè)參數(shù):
int[] colors:表示所需要的漸變顏色數(shù)組,長度大于等于2脂新。
float[] stops:表示每個(gè)漸變顏色所在的位置百分點(diǎn)挪捕,取值 0-1,數(shù)量必須與 colors 數(shù)組保持一致争便,不然直接 crash 级零,一般第一個(gè)數(shù)值取0,最后一個(gè)數(shù)值取 1滞乙;如果第一個(gè)數(shù)值和最后一個(gè)數(shù)值并沒有取 0 和 1奏纪,比如我們這里取一個(gè)位置數(shù)組:{0.2,0.5,0.8},起始點(diǎn)是 0.2 百分比位置斩启,結(jié)束點(diǎn)是 0.8 百分比位置序调,而 0-0.2 百分比位置和 0.8-1.0 百分比的位置都是沒有指定顏色的。而這些位置的顏色就是根據(jù)我們指定的 TileMode 空白區(qū)域填充模式來自行填充M么亍发绢!有時(shí)效果我們是不可控的。所以為了方便起見垄琐,建議大家 stops 數(shù)組的起始和終止數(shù)值設(shè)為 0 和 1边酒。
多色漸變的例子:
int[] colors = new int[]{0xffff0000,0xff00ff00,0xff00ffff,0xff0000ff};
float[] stops = new float[]{0f,0.3f,0.6f,1f};
mRadialGradient = new RadialGradient(getWidth() / 2, getHeight() / 2, 200, colors, stops, Shader.TileMode.CLAMP);
mPaint.setShader(mRadialGradient);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, 200, mPaint);
構(gòu)造了四色顏色的數(shù)值,以及對應(yīng)的位置百分比此虑。效果圖如下:
SweepGradient
梯度渲染甚纲,掃描漸變,類似衛(wèi)星掃描的效果朦前。
構(gòu)造函數(shù)預(yù)覽:
//兩色
public SweepGradient(float cx, float cy, int color0, int color1)
//多色
public SweepGradient(float cx, float cy,
int colors[], float positions[])
1介杆、 兩色漸變構(gòu)造函數(shù)使用實(shí)例
//兩色
public SweepGradient(float cx, float cy, int color0, int color1)
SweepGradient 與 RadialGradient 類似,下面來看看他的各項(xiàng)參數(shù):
cx 漸變中心點(diǎn)X坐標(biāo)
cy 漸變中心點(diǎn)Y坐標(biāo)
color0:掃描開始的顏色韭寸,即中心點(diǎn)和水平最右點(diǎn)的連線顏色春哨,取值類型必須是八位的0xAARRGGBB色值!透明底Alpha值不能省略恩伺,不然不會顯示出顏色赴背。
color1:掃描結(jié)束的顏色,即中心點(diǎn)和水平最左點(diǎn)的連線顏色,取值類型必須是八位的0xAARRGGBB色值凰荚!
掃描的角度為360度燃观,color0 ,color1平分360度便瑟,color0 順時(shí)針掃描了 (0-180)缆毁,color1 順時(shí)針掃描了(180-360)。由于掃描的半徑可以無限大到涂,所以這里沒有填充方式的參數(shù)脊框。
來看個(gè)簡單的例子:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mSweepGradient = new SweepGradient(getWidth() / 2, getHeight() / 2, 0xffff0000, 0xffffff00);
mPaint.setShader(mSweepGradient);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, 200, mPaint);
}
各個(gè)參數(shù)的含義上面已經(jīng)講解了,來看看效果圖:
2践啄、 多色漸變構(gòu)造函數(shù)使用實(shí)例
方法預(yù)覽:
//多色
public SweepGradient(float cx, float cy,
int colors[], float positions[])
這里與兩色漸變不同的是兩個(gè)參數(shù):
int[] colors:表示所需要的漸變顏色數(shù)組浇雹,長度大于等于2。
float[] positions:表示每個(gè)漸變顏色所掃描的相對位置屿讽,取值 0-1昭灵,數(shù)量必須與 colors 數(shù)組保持一致,不然直接 crash 聂儒,一般第一個(gè)數(shù)值取0虎锚,最后一個(gè)數(shù)值取1;如果第一個(gè)數(shù)值和最后一個(gè)數(shù)值并沒有取 0 和 1衩婚,繪圖可能會產(chǎn)生意想不到的結(jié)果窜护。可以為 null非春,漸變顏色間隔均勻柱徙。
修改一下上面的例子:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int [] colors=new int[]{0xffff0000, 0xffffff00,0xffff00ff};
mSweepGradient = new SweepGradient(getWidth() / 2, getHeight() / 2, colors, null);
mPaint.setShader(mSweepGradient);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, 200, mPaint);
}
效果圖一欄:
注意:盡量避免在 onDraw 方法中新建對象,我這里主要是為了演示方便奇昙。
ComposeShader(組合渲染)
構(gòu)造方法預(yù)覽:
public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)
參數(shù)含義:
shaderA 目標(biāo)渲染器
shaderB 源渲染器
mode 渲染器組合的模式
mode 具體參考 自定義控件三部曲之繪圖篇(十)——Paint之setXfermode(一)
我們將放射渲染器以及掃描渲染器組合在一起:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int [] colors=new int[]{0xffff0000, 0xffffff00,0xffff00ff};
SweepGradient sweepGradient = new SweepGradient(getWidth()/2, getHeight()/2, colors, null);
RadialGradient radialGradient = new RadialGradient(getWidth()/2, getHeight()/2,
radius, 0xFFFFFFFF, 0x00FFFFFF, Shader.TileMode.CLAMP);
ComposeShader composeShader = new ComposeShader(sweepGradient, radialGradient, PorterDuff.Mode.SRC_OVER);
mPaint.setShader(composeShader);
canvas.drawCircle(getWidth()/2,getHeight()/2,200,mPaint);
}
這里的參數(shù)我就不再細(xì)講了护侮,來看看效果圖:
ColorPicker的具體實(shí)現(xiàn)
如果對自定義 View 大體流程還不是很熟悉的話。請鏈接 自定義View之繪圖篇(一):基礎(chǔ)圖形的繪制 系列的文章储耐。
onMeasure 方法略過 . . .
onSizeChanged 方法:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX = w / 2;
centerY = h / 2;
radius = Math.min(centerX, centerY);
//生成色輪
createColorWheel();
}
賦值中心點(diǎn)坐標(biāo)羊初,半徑為寬,高一半的最小值什湘。具體來看看 createColorWheel 方法:
private void createColorWheel() {
int colorCount = 12;
int colorAngleStep = 360 / 12;
int colors[] = new int[colorCount];
float hsv[] = new float[]{0f, 1f, 1f};
for (int i = 0; i < colors.length; i++) {
hsv[0] = (i * colorAngleStep + 180) % 360;
colors[i] = Color.HSVToColor(hsv);
}
SweepGradient sweepGradient = new SweepGradient(centerX, centerY, colors, null);
RadialGradient radialGradient = new RadialGradient(centerX, centerY,
radius, 0xFFFFFFFF, 0x00FFFFFF, Shader.TileMode.CLAMP);
ComposeShader composeShader = new ComposeShader(sweepGradient, radialGradient, PorterDuff.Mode.SRC_OVER);
colorWheelPaint.setShader(composeShader);
}
主要是把色輪分成 12 等份长赞,求出每份的色彩,并且每份的飽和度和值都為 1闽撤,然后生成大小為 12 間隔均勻的掃描渲染器得哆;新建不透明到透明半徑為 radius 的放射渲染器;通過掃描渲染器作為目標(biāo)渲染器哟旗,放射渲染器作為源渲染器生成組合渲染器贩据,并設(shè)置給 Paint 栋操。接著進(jìn)行繪制:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(centerX, centerY, 200, colorWheelPaint);
}
繪制大小為 radius 的圓:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(centerX, centerY, radius, colorWheelPaint);
}
效果圖:
隨著手指的移動更改背景顏色,需要重寫 onTouchEvent 方法:
@Override
public boolean onTouchEvent(MotionEvent event) {
ViewParent parent = getParent();
if (parent != null)
parent.requestDisallowInterceptTouchEvent(true);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
int x = (int) event.getX();
int y = (int) event.getY();
int cx = x - centerX;
int cy = y - centerY;
double d = Math.sqrt(cx * cx + cy * cy);
if (d <= radius) {
colorHSV[0] = (float) (Math.toDegrees(Math.atan2(cy, cx)) + 180f);
colorHSV[1] = Math.max(0f, Math.min(1f, (float) (d / radius)));
if (onSeekColorListener != null) {
touchCircleY = y;
touchCircleX = x;
onSeekColorListener.onSeekColorListener(getColor());
postInvalidate();
}
}
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
根據(jù) X饱亮,Y 軸的偏移量矾芙,可以計(jì)算出當(dāng)前觸摸點(diǎn)與中心點(diǎn)連線與水平方向的角度:
Math.toDegrees(Math.atan2(cy, cx)
并把角度賦值給 HSV 數(shù)組的色彩值。
通過當(dāng)前觸摸點(diǎn)到中心點(diǎn)的距離/半徑 獲取到飽和度:
HSV 飽和度 = Math.max(0f, Math.min(1f, (float) (d / radius)))
通過賦值當(dāng)前觸摸點(diǎn)坐標(biāo)近上,繪制觸摸的空心小圓:
touchCircleY = y;
touchCircleX = x;
通過依賴倒轉(zhuǎn)原則(接口)蠕啄,把獲取到的顏色值公開:
onSeekColorListener.onSeekColorListener(getColor());
最后調(diào)用:
postInvalidate();
重繪空心小圓。
源碼
如果本文有幫到你戈锻,記得加關(guān)注哦