Android Canvas 漸變進(jìn)度條的實(shí)現(xiàn)

標(biāo)題說(shuō)漸變進(jìn)度條是為了方便理解,這里本身的項(xiàng)目背景是一款表盤(pán)的分針。先上圖:


表盤(pán)

周圈藍(lán)色的漸變條(分針)就是本次要實(shí)現(xiàn)的東西。

1谦炬、拆分

首先,熟悉Canvas的朋友應(yīng)該知道它可以畫(huà)出各種形狀节沦,但偏偏沒(méi)有一頭是圓的環(huán)形(這里不考慮使用path繪制)键思。
所以我們不得不把它拆分為2個(gè)形狀:圓環(huán).

2、繪制圓環(huán)

繪制圓環(huán)有很多種方法甫贯,比如畫(huà)2個(gè)圓取補(bǔ)集之類(lèi)的吼鳞。這里直接使用canvas.drawArc()函數(shù)來(lái)畫(huà)。
先看看函數(shù)原型:

void drawArc (RectF oval, 
                float startAngle, 
                float sweepAngle, 
                boolean useCenter, 
                Paint paint)

drawArc()有2個(gè)重載函數(shù)叫搁,此處只用到其一赔桌,另一個(gè)很是相似,只不過(guò)把oval參數(shù)換成了具體的4個(gè)值渴逻。
第一個(gè)參數(shù)是一個(gè)矩形疾党,所繪制的圓環(huán)將會(huì)是此矩形的內(nèi)切橢圓。如果給的是正方形那畫(huà)出來(lái)的就是正圓環(huán)了惨奕。RectF的構(gòu)造函數(shù)有4個(gè)參數(shù)分別是left top right bottom雪位,直接看字面意思不是很好理解。其實(shí)就是矩形左上和右下2個(gè)點(diǎn)的坐標(biāo)梨撞。left top分別是左上頂點(diǎn)的xy雹洗,剩下2個(gè)同理。
第二參數(shù)是開(kāi)始角度聋袋。由于屏幕坐標(biāo)系關(guān)系队伟,默認(rèn)x軸正方向(就是水平向右)為0度穴吹。
第三個(gè)參數(shù)是圓環(huán)掃過(guò)的角度幽勒,順時(shí)針為正。
第四個(gè)參數(shù)比較重要港令。這里為true則畫(huà)出來(lái)的是扇形(即連接圓心)啥容,為false畫(huà)出來(lái)的是圓弧锈颗。我們要畫(huà)的是圓環(huán),自然填false.
第五個(gè)參數(shù)就是畫(huà)筆了咪惠,可以定義顏色粗細(xì)(即圓環(huán)寬度)等等击吱。漸變問(wèn)題稍后再說(shuō)。這里要主要設(shè)置下畫(huà)筆的stylestroke遥昧,否則畫(huà)出來(lái)的只有邊框沒(méi)有填充覆醇。

下面是畫(huà)純色圓環(huán)的代碼:

/*為了便于說(shuō)明,先定義幾個(gè)變量*/

float mMinOvalR; //圓環(huán)外接矩形邊長(zhǎng)的一半
float mMinWidth; //圓環(huán)寬度
float mMinOffsetY; //外接矩形top屬性向上的偏移(這個(gè)下文會(huì)解釋)
float degree = 315; //圓弧掃過(guò)的角度

//創(chuàng)建畫(huà)筆:
mMinPaint = new Paint();
mMinPaint.setColor(Color.BLUE); //先隨便給個(gè)顏色
mMinPaint.setAntiAlias(true); //啟用抗鋸齒
mMinPaint.setDither(true); //啟用抗顏色抖動(dòng)(可以讓漸變更平緩)
mMinPaint.setStyle(Paint.Style.STROKE);
mMinPaint.setStrokeWidth(mMinWidth); //設(shè)置寬度

//外接矩形
//由于手表屏幕是正圓炭臭,所以canvas正好是正方形永脓,于是可以用下面方法算出矩形頂點(diǎn)坐標(biāo)。
RectF rect = new RectF(canvas.getWidth() / 2 - mMinOvalR,
        canvas.getHeight() / 2 - mMinOvalR,
        canvas.getWidth() - (canvas.getWidth() / 2 - mMinOvalR),
        canvas.getHeight() - (canvas.getHeight() / 2 - mMinOvalR));
//畫(huà)弧
canvas.drawArc(rect, -90, degree, false, mMinPaint);

至此一個(gè)不是很好看的圓環(huán)就出來(lái)了~

3鞋仍、我要圓圓的頭

這個(gè)實(shí)現(xiàn)很簡(jiǎn)單常摧,只要在頭部畫(huà)一個(gè)直徑=寬度的圓即可。問(wèn)題在于這個(gè)圓心坐標(biāo)是多少呢威创?

如圖落午,根據(jù)初等數(shù)學(xué)知識(shí)不難算出,圓弧上的小圓圓心坐標(biāo)
x = rect.left + rect.width() / 2f + mMinOvalR * Math.cos(α)
y = rect.top + rect.height() / 2f + mMinOvalR * Math.sin(α)

于是可以畫(huà)出圓圓的頭部:

//定義頭部畫(huà)筆
mMinCirclePaint = new Paint();
mMinCirclePaint.setColor(Color.BLUE);
mMinCirclePaint.setAntiAlias(true);

degree -= 90; //抵消屏幕坐標(biāo)系差異
degree = (float) (Math.PI / 180f * degree); //換成弧度
canvas.drawCircle((float) (rect.left + rect.width() / 2f + mMinOvalR * Math.cos(degree)), //圓心x
        (float) (rect.top + rect.height() / 2f + mMinOvalR * Math.sin(degree)), //圓心y
        mMinWidth / 2f,  //半徑
        mMinCirclePaint);

看到頭部已經(jīng)變成圓的了肚豺。

4溃斋、漸變來(lái)啦

可以使用Android提供的掃描渲染器SweepGradient實(shí)現(xiàn)需要的漸變,關(guān)于SweepGradient可以參考http://blog.csdn.net/t12x3456/article/details/10473225
在畫(huà)圓弧canvas.drawArc之前加上下面代碼:

//先創(chuàng)建一個(gè)渲染器
SweepGradient mSweepGradient = new SweepGradient(canvas.getWidth() / 2,
        canvas.getHeight() / 2, //以圓弧中心作為掃描渲染的中心以便實(shí)現(xiàn)需要的效果
        mMinColors, //這是我定義好的顏色數(shù)組详炬,包含2個(gè)顏色:#35C3D7盐类、#2894DD
        null);
//把漸變?cè)O(shè)置到筆刷
mMinPaint.setShader(mSweepGradient);
加上漸變

漸變效果是有了,但是漸變起始角度似乎有點(diǎn)問(wèn)題呛谜,默認(rèn)是從0度開(kāi)始在跳。而這里作為一個(gè)表盤(pán),需要從-90°開(kāi)始隐岛∶睿可惜并沒(méi)有函數(shù)來(lái)直接指定起始角度。所以只好利用矩陣將整個(gè)漸變逆時(shí)針轉(zhuǎn)90°實(shí)現(xiàn)需要的效果聚凹。
在創(chuàng)建漸變后割坠、設(shè)置到筆刷前加入下面代碼:

//旋轉(zhuǎn)漸變
Matrix matrix = new Matrix();
matrix.setRotate(-90f, canvas.getWidth() / 2, canvas.getHeight() / 2);
mSweepGradient.setLocalMatrix(matrix);
調(diào)整漸變角度

OK,這樣漸變就差不多了妒牙。
但是小圓很突(nan)兀(kan)彼哼,只需要把他設(shè)置漸變的最后一個(gè)顏色即可。
mMinCirclePaint.setColor(mMinColors[1]);

適配小圓顏色

猛一看似乎沒(méi)問(wèn)題了湘今,但如果細(xì)看敢朱,發(fā)現(xiàn)小圓還是有一丟丟突兀。造成這種情況的原因是:圓環(huán)到了與小圓重合的時(shí)候其實(shí)還在漸變過(guò)程中,并不是等于漸變終止顏色拴签。真正的漸變終止在360°處(即繞一整圈)孝常。解決方案有2種。一:讓小圓也進(jìn)行漸變蚓哩。二:讓圓環(huán)的漸變提前結(jié)束构灸。
方法一太復(fù)雜,由于小圓不是很大岸梨,這里直接用方法二就好喜颁。
那么如何控制漸變的位置呢?這就要用到構(gòu)造漸變器的最后一個(gè)參數(shù)啦~

最后一個(gè)參數(shù)是float數(shù)組曹阔,元素個(gè)數(shù)與顏色個(gè)數(shù)相同洛巢。每個(gè)元素的取值范圍都是[0,1]用于表示在圓環(huán)的位置,0對(duì)應(yīng)0°(起始)次兆,1對(duì)應(yīng)360°(結(jié)束)稿茉,且必須單調(diào)遞增。每個(gè)元素控制著對(duì)應(yīng)顏色往下一顏色漸變的起始位置芥炭。若此顏色之前/之后沒(méi)有顏色漓库,則顯示純色。
這么說(shuō)有點(diǎn)抽象园蝠,來(lái)看個(gè)例子:
假設(shè)當(dāng)前是白→黑漸變渺蒿。最后一個(gè)參數(shù)是{ 0.25f, 0.5f }
那么實(shí)際效果是0°90°是純白色,90°180°是漸變過(guò)程彪薛,180°~360°是純黑色茂装。
這樣是不是可以理解了?
于是掃描漸變器可以這樣創(chuàng)建:

//創(chuàng)建漸變
SweepGradient mSweepGradient = new SweepGradient(canvas.getWidth() / 2,
        canvas.getHeight() / 2,
        mMinColors,
        new float[]{0f, degree / 360f - 0.017f});
// 從圖肉眼不難觀察出半個(gè)小圓大概占了6°的范圍(刻度一格是6°)
// 6 / 360 = 0.017
//第一個(gè)元素為0表示從0°開(kāi)始漸變善延,第二個(gè)元素表示漸變提前結(jié)束少态,最后的那一塊是純色。這樣一來(lái)便可融為一體易遣。
適配漸變顏色

至此彼妻,一個(gè)較為完美的漸變環(huán)就完成了 真是不容易啊 -.-
想做個(gè)進(jìn)度圈或者Loading動(dòng)畫(huà)的朋友們到此就足夠了。
但是6姑!G惹浮!如果你和我一樣做的是表盤(pán)揩魂,請(qǐng)繼續(xù)往下看幽邓。

5、不能?chē)?yán)絲合縫火脉?逼死強(qiáng)迫癥

由于美工手抖或者奇葩的屏幕形狀牵舵,背景圖常常難以與代碼畫(huà)的東西完美契合茅特。比如上圖中12與10的位置,明顯偏差了棋枕。妄圖讓美工搞定這個(gè)問(wèn)題只能做夢(mèng)想想,最后這鍋還得程序猿背妒峦。
表面上似乎很簡(jiǎn)單重斑,只需要修改下圓環(huán)的外接矩形RectF即可:

//外切矩形
//在原來(lái)基礎(chǔ)上加上了偏移像素mMinOffsetY用來(lái)貼合背景
RectF rect = new RectF(canvas.getWidth() / 2 - mMinOvalR-mMinOffsetY,
        canvas.getHeight() / 2 - mMinOvalR-mMinOffsetY,
        canvas.getWidth() - (canvas.getWidth() / 2 - mMinOvalR),
        canvas.getHeight() - (canvas.getHeight() / 2 - mMinOvalR));
貼合素材

現(xiàn)在12點(diǎn)鐘位置相對(duì)來(lái)說(shuō)已經(jīng)貼合地不錯(cuò)。此時(shí)一個(gè)很棘手很復(fù)雜的問(wèn)題又來(lái)了:頭部的小圓沒(méi)有與圓環(huán)貼合準(zhǔn)確肯骇。

6窥浪、治理調(diào)皮的小圓

上面那個(gè)圖因?yàn)槠撇皇呛車(chē)?yán)重或許還看不出來(lái)。那么請(qǐng)看看下面這個(gè):

夸張的偏移

造成這種錯(cuò)位的本質(zhì)原因是:經(jīng)過(guò)微調(diào)后的矩形不再是正方形笛丙,我們的圓環(huán)也不再是正圓而是橢圓漾脂,但是小圓圈的位置還是按照正圓計(jì)算的,于是造成了的偏離胚鸯。對(duì)癥下藥骨稿,把小圓位置的計(jì)算方法改成橢圓就ok了。
學(xué)過(guò)初等數(shù)學(xué)的應(yīng)該知道姜钳,橢圓的計(jì)算比正圓復(fù)雜很多很多坦冠,上面的問(wèn)題可以抽象成如下數(shù)學(xué)題目:


已知圖上所有的字母,橢圓是矩形的內(nèi)切橢圓哥桥,求橢圓上的點(diǎn)的坐標(biāo)xy與α的關(guān)系辙浑。
坐標(biāo)系不允許變換。
最后要寫(xiě)成x=f(α); y=g(α)的形式

過(guò)程我就不寫(xiě)啦拟糕,直接上答案:
x = b + c / 2 * (cosα + 1)
y = a - d / 2 * (sinα - 1)

修正一下畫(huà)頭部小圓的代碼:

//頭部圓
mMinCirclePaint.setColor(isInAmbientMode() ? mMinShimmerColors[1] : mMinColors[1]);
degree = 90f - degree; //抵消屏幕坐標(biāo)系差異
degree = (float) (Math.PI / 180f * degree);
float a = rect.top,
        b = rect.left,
        c = rect.width(),
        d = rect.height();
canvas.drawCircle((float) (b + c / 2f * (Math.cos(degree) + 1)),
        (float) (a + -1 * d / 2f * (Math.sin(degree) - 1)),
        mMinWidth / 2f, mMinCirclePaint);
最終成品

OK判呕,基本上完美了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末送滞,一起剝皮案震驚了整個(gè)濱河市侠草,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌犁嗅,老刑警劉巖梦抢,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異愧哟,居然都是意外死亡奥吩,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)蕊梧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)霞赫,“玉大人,你說(shuō)我怎么就攤上這事肥矢《怂ィ” “怎么了叠洗?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)旅东。 經(jīng)常有香客問(wèn)我灭抑,道長(zhǎng),這世上最難降的妖魔是什么抵代? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任腾节,我火速辦了婚禮,結(jié)果婚禮上荤牍,老公的妹妹穿的比我還像新娘案腺。我一直安慰自己,他們只是感情好康吵,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布劈榨。 她就那樣靜靜地躺著,像睡著了一般晦嵌。 火紅的嫁衣襯著肌膚如雪同辣。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天惭载,我揣著相機(jī)與錄音邑闺,去河邊找鬼。 笑死棕兼,一個(gè)胖子當(dāng)著我的面吹牛陡舅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播伴挚,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼靶衍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了茎芋?” 一聲冷哼從身側(cè)響起颅眶,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎田弥,沒(méi)想到半個(gè)月后涛酗,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡偷厦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年商叹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片只泼。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡剖笙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出请唱,到底是詐尸還是另有隱情弥咪,我是刑警寧澤过蹂,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站聚至,受9級(jí)特大地震影響酷勺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜扳躬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一脆诉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坦报,春花似錦、人聲如沸狂鞋。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)骚揍。三九已至字管,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間信不,已是汗流浹背嘲叔。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抽活,地道東北人硫戈。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像下硕,于是被迫代替她去往敵國(guó)和親丁逝。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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