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

表盤
周圈藍(lán)色的漸變條(分針)就是本次要實(shí)現(xiàn)的東西。
1、拆分
首先,熟悉Canvas的朋友應(yīng)該知道它可以畫出各種形狀,但偏偏沒有一頭是圓的環(huán)形(這里不考慮使用path繪制)。
所以我們不得不把它拆分為2個(gè)形狀:圓環(huán)與圓.
2、繪制圓環(huán)
繪制圓環(huán)有很多種方法,比如畫2個(gè)圓取補(bǔ)集之類的。這里直接使用canvas.drawArc()函數(shù)來(lái)畫。
先看看函數(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)切橢圓。如果給的是正方形那畫出來(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)的x和y,剩下2個(gè)同理。
第二參數(shù)是開始角度。由于屏幕坐標(biāo)系關(guān)系,默認(rèn)x軸正方向(就是水平向右)為0度。
第三個(gè)參數(shù)是圓環(huán)掃過(guò)的角度,順時(shí)針為正。
第四個(gè)參數(shù)比較重要。這里為true則畫出來(lái)的是扇形(即連接圓心),為false畫出來(lái)的是圓弧。我們要畫的是圓環(huán),自然填false.
第五個(gè)參數(shù)就是畫筆了,可以定義顏色粗細(xì)(即圓環(huán)寬度)等等。漸變問題稍后再說(shuō)。這里要主要設(shè)置下畫筆的style為stroke,否則畫出來(lái)的只有邊框沒有填充。
下面是畫純色圓環(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)建畫筆:
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));
//畫弧
canvas.drawArc(rect, -90, degree, false, mMinPaint);
至此一個(gè)不是很好看的圓環(huán)就出來(lái)了~

3、我要圓圓的頭
這個(gè)實(shí)現(xiàn)很簡(jiǎn)單,只要在頭部畫一個(gè)直徑=寬度的圓即可。問題在于這個(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(α)
于是可以畫出圓圓的頭部:
//定義頭部畫筆
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://www.dhdzp.com/article/252972.htm
在畫圓弧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)問題,默認(rèn)是從0度開始。而這里作為一個(gè)表盤,需要從-90°開始??上Р]有函數(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]);

適配小圓顏色
猛一看似乎沒問題了,但如果細(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)顏色往下一顏色漸變的起始位置。若此顏色之前/之后沒有顏色,則顯示純色。
這么說(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°開始漸變,第二個(gè)元素表示漸變提前結(jié)束,最后的那一塊是純色。這樣一來(lái)便可融為一體。

適配漸變顏色
至此,一個(gè)較為完美的漸變環(huán)就完成了 真是不容易啊 -.-
想做個(gè)進(jìn)度圈或者Loading動(dòng)畫的朋友們到此就足夠了。
但是!?。?/strong>如果你和我一樣做的是表盤,請(qǐng)繼續(xù)往下看。
5、不能嚴(yán)絲合縫?逼死強(qiáng)迫癥
由于美工手抖或者奇葩的屏幕形狀,背景圖常常難以與代碼畫的東西完美契合。比如上圖中12與10的位置,明顯偏差了。妄圖讓美工搞定這個(gè)問題只能做夢(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ù)雜的問題又來(lái)了:頭部的小圓沒有與圓環(huán)貼合準(zhǔn)確。
6、治理調(diào)皮的小圓
上面那個(gè)圖因?yàn)槠撇皇呛車?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ù)雜很多很多,上面的問題可以抽象成如下數(shù)學(xué)題目:

已知圖上所有的字母,橢圓是矩形的內(nèi)切橢圓,求橢圓上的點(diǎn)的坐標(biāo)xy與α的關(guān)系。
坐標(biāo)系不允許變換。
最后要寫成x=f(α); y=g(α)的形式
過(guò)程我就不寫啦,直接上答案:
x = b + c / 2 * (cosα + 1)
y = a - d / 2 * (sinα - 1)
修正一下畫頭部小圓的代碼:
//頭部圓
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,基本上完美了。
以上就是Android Canva實(shí)現(xiàn)漸變進(jìn)度條的詳細(xì)內(nèi)容,更多關(guān)于Android Canva漸變進(jìn)度條的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android WebView實(shí)現(xiàn)截長(zhǎng)圖功能
這篇文章主要為大家詳細(xì)介紹了Android截長(zhǎng)圖的一種實(shí)現(xiàn)方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05
Android實(shí)現(xiàn)文件存儲(chǔ)案例
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)文件存儲(chǔ)案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
Android實(shí)戰(zhàn)打飛機(jī)游戲之怪物(敵機(jī))類的實(shí)現(xiàn)(4)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)戰(zhàn)打飛機(jī)游戲之怪物(敵機(jī))類的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-07-07
Android自定義View繪制四位數(shù)隨機(jī)碼
這篇文章主要介紹了Android自定義View繪制四位數(shù)隨機(jī)碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
Android為textView設(shè)置setText的時(shí)候報(bào)錯(cuò)的講解方案
今天小編就為大家分享一篇關(guān)于Android為textView設(shè)置setText的時(shí)候報(bào)錯(cuò)的講解方案,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03
Flutter中顯示條件Widget的實(shí)現(xiàn)方式
在 Flutter 日常開發(fā)中經(jīng)常會(huì)遇見這樣的需求,如: 只有用戶是 VIP 時(shí),才能展示某個(gè)入口或者某個(gè)模塊,這樣的需求在開發(fā)業(yè)務(wù)需求中多如牛毛,那你是如何來(lái)優(yōu)雅的實(shí)現(xiàn)的呢,本文將給大家介紹Flutter中顯示條件Widget的實(shí)現(xiàn)方式,需要的朋友可以參考下2024-04-04
Android編程實(shí)現(xiàn)讀取工程中的txt文件功能
這篇文章主要介紹了Android編程實(shí)現(xiàn)讀取工程中的txt文件功能,結(jié)合實(shí)例形式詳細(xì)分析了Android讀取txt文件的原理、操作步驟與相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-02-02
Android獲取系統(tǒng)時(shí)間以及網(wǎng)絡(luò)時(shí)間
這篇文章主要為大家詳細(xì)介紹了Android獲取系統(tǒng)時(shí)間以及網(wǎng)絡(luò)時(shí)間的方法,感興趣的小伙伴們可以參考一下2016-07-07

