自定義View之案列篇(四):顏色選擇器

2016年的第四篇案例妥色,比以往來得更晚一些...

博客的更新就像 2012 年的第一場雪一樣搪花,比以往來得更晚一些...

博主這段時(shí)間實(shí)在太忙了,本篇案例一直沒有更新嘹害,實(shí)在對不住大家...

案列篇系列包含了自定義 View 許多的知識點(diǎn)撮竿,希望大家能夠知其然知其所以然勃刨,靈活應(yīng)用争占,寫出屬于自己心動的控件贮尉。

下面來看看今天登場的是:

ColorPicker(顏色選擇器)

大家一定還記得 PhotoShop 中的顏色面板辱挥,It's very fashion . 曾經(jīng)幾時(shí)讽营,苦苦的思索它的實(shí)現(xiàn)過程 . . .

先來看看最終的效果圖:

color

具有以下效果:

  • 空心小圓隨著手指的移動而移動

  • 隨著手指的移動更改背景顏色

涉及到的知識點(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è)例子:

color
  • 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)系如下:

color

Shader 類包括了 5 個(gè)直接子類:

  • BitmapShader 用于圖像渲染

  • LinearGradient 用于線性渲染

  • RadialGradient 用于環(huán)形渲染 (放射狀)

  • SweepGradient 用于梯度渲染(掃描狀)

  • ComposeShader 用于混合渲染

這里主要講解后三種渲染躏哩,如果對前面兩種渲染感興趣請鏈接:

圖像渲染(Shader)

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ū)域的填充問題逐样。

效果圖如下:

color
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)的位置百分比此虑。效果圖如下:

color

參考鏈接RadialGradient與水波紋按鈕效果

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)講解了,來看看效果圖:

color
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);
    }

效果圖一欄:

color

注意:盡量避免在 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ì)講了护侮,來看看效果圖:

color

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);
    }

效果圖:

color

隨著手指的移動更改背景顏色,需要重寫 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)注哦

源碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市和媳,隨后出現(xiàn)的幾起案子格遭,更是在濱河造成了極大的恐慌,老刑警劉巖留瞳,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拒迅,死亡現(xiàn)場離奇詭異,居然都是意外死亡她倘,警方通過查閱死者的電腦和手機(jī)璧微,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來硬梁,“玉大人前硫,你說我怎么就攤上這事∮梗” “怎么了屹电?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長跃巡。 經(jīng)常有香客問我危号,道長,這世上最難降的妖魔是什么素邪? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任外莲,我火速辦了婚禮,結(jié)果婚禮上兔朦,老公的妹妹穿的比我還像新娘偷线。我一直安慰自己,他們只是感情好烘绽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布淋昭。 她就那樣靜靜地躺著,像睡著了一般安接。 火紅的嫁衣襯著肌膚如雪翔忽。 梳的紋絲不亂的頭發(fā)上英融,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音歇式,去河邊找鬼驶悟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛材失,可吹牛的內(nèi)容都是我干的痕鳍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼龙巨,長吁一口氣:“原來是場噩夢啊……” “哼笼呆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起旨别,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤诗赌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后秸弛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铭若,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年递览,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了叼屠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绞铃,死狀恐怖镜雨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情憎兽,我是刑警寧澤冷离,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站纯命,受9級特大地震影響西剥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜亿汞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一瞭空、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疗我,春花似錦咆畏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至麦牺,卻和暖如春钮蛛,著一層夾襖步出監(jiān)牢的瞬間鞭缭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工魏颓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留岭辣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓甸饱,卻偏偏與公主長得像沦童,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子叹话,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 系列文章之 Android中自定義View(一)系列文章之 Android中自定義View(二)系列文章之 And...
    YoungerDev閱讀 2,159評論 0 4
  • 通過之前的詳細(xì)分析偷遗,我們知道:在measure中測量了View的大小,在layout階段確定了View的位置驼壶。 完...
    SnowDragonYY閱讀 930評論 0 3
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理鹦肿,服務(wù)發(fā)現(xiàn),斷路器辅柴,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • 今日重陽,泡茶寫詞瞭吃。天與秋光碌嘀,紅葉待賞。 金黃飽滿的柿子掛滿枝頭 孩子們翹起了小腳兒 田間忙收的父母 滿臉堆滿了豐...
    趙小建閱讀 148評論 0 0