原文地址:http://makerchen.com/2016/05/29/android-alibaba/
廢話不多說薪介,先看下效果:
該效果一眼看上去比較簡單祠饺,但其涉及的知識點還是挺多的。尤其是需要讀者對android.graphics包下的API有一定的了解汁政。
先對涉及到的知識點羅列如下道偷,還不是很了解的讀者可以先自行百度做個簡單的涉獵,對后續(xù)文章的理解會有很大幫助记劈。
- Paint勺鸦、Canvas這兩個基礎的類必須熟悉。
- 用作渲染的Shader類及其子類目木,以及后文中使用的是SweepGradient梯度渲染换途,用作漸變圓環(huán),需要了解。
- canvas.save() & canvas.restore() 的作用與關系怀跛。
- 由Paint引申的PathEffect距贷、PorterDuffXfermode,已經(jīng)Matrix等類要有個基本的概念吻谋。
- 圖層繪制的一些概念忠蝗。
- 臟矩形技術。
如果你已經(jīng)基本了解了上面涉及到的知識點漓拾。
OK阁最。那接下來我們就一步一步實現(xiàn)這個效果。
1.環(huán)形漸變
或許大家都有印象骇两,在ApiDemos中提供過一個例子仿照PS做的取色器效果速种。有興趣的讀者可以具體查看ApiDemos下的ColorPickerDialog的實現(xiàn)。這里我們參考他的寫法低千,就可以做出一個簡單的環(huán)形漸變了配阵。
當然ColorPickerDialog中的核心代碼也正是使用了剛才所提及的SweepGradient類用作渲染。該類屬于Shader的子類示血,當然其兄弟類還有BitmapShader位圖渲染棋傍、LinearGradient線性渲染、RadialGradient環(huán)形渲染难审、SweepGradient梯度渲染以及ComposeShader組合渲染瘫拣。網(wǎng)上有一大堆關于他們的介紹,可以做出很多很棒的效果。此處不展開告喊。
核心代碼如下:
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);// 漸變色環(huán)畫筆麸拄,抗鋸齒
private final int[] mColors = new int[] { 0xffff0000, 0xffffff00, 0xff00ff00,
0xff00ffff,0xff0000ff,0xffff00ff };// 漸變色環(huán)顏色
Shader s = new SweepGradient(0, 0, mColors, null);
mPaint.setShader(s);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(40);
float r = CENTER_X - mPaint.getStrokeWidth() * 0.5f;
canvas.save() ;
canvas.translate(CENTER_X, CENTER_X);// 移動中心
canvas.rotate(150);
canvas.drawOval(new RectF(-r, -r, r, r), mPaint);// 畫出色環(huán)和中心園
canvas.restore();
效果如圖1所示:
代碼講解:
從參考效果圖上看,顏色是有紅色漸變(并非線性漸變黔姜,這里我們先按照簡單的實現(xiàn))為綠色拢切,而且效果并非為一個整圓。為了計算方便地淀,我們假設該圓環(huán)的角度為240度失球。
如圖2所示
我們已知SweepGradient是一個360度均勻分布的漸變,我們一共設置了6個漸變色:從紅色(ff0000)到紫色(ff00ff),使其均勻分布在圓環(huán)上。
而繪制圓的時候帮毁,我們先將canvas的原點(在android2D圖形系統(tǒng)中其坐標系原點在視圖左上角)通過
canvas.translate()
平移至了圓環(huán)的中心點实苞。在此我們使用canvas.rotate()
旋轉(zhuǎn)操作,旋轉(zhuǎn)150度烈疚,使其紅色漸變的開始位置處于圖片左下方(此處正確的理解應該是這樣:由于我們對畫布旋轉(zhuǎn)了150度黔牵,所以我們在繪制完圓環(huán)之后,通過restore()方法又使得畫布回歸到原來位置爷肝,從而達到了將紅色漸變位于左下方的目的)猾浦。調(diào)整完canvas之后陆错,我們通過canvas.drawOval()
將圓繪制上去。最后將畫布回歸到原來的位置金赦。此處還使用了
canvas.save()
與canvas.restore()
組合操作音瓷。簡單介紹一下:由于此處我們對畫布有平移、旋轉(zhuǎn)操作夹抗。為了不造成對后續(xù)繪制的影響绳慎,使其復雜度增加。我們使用save()和restore()的組合來使得畫布回歸到它原來的位置漠烧。此舉有時候會對性能產(chǎn)生一定的影響杏愤,本文只是step by step的實現(xiàn)教程,而且此效果并不會強依賴于性能已脓,所以性能在此處先放一邊珊楼。文末我會注明可以優(yōu)化的點,供大家思考度液、討論厕宗。在這里調(diào)用完restore()的表象就是canvas的原點又回到了視圖的左上角。關于具體對
canvas.save()
和canvas.restore()
的解釋恨诱,網(wǎng)上有一大堆媳瞪。這里不詳細展開。大致可以理解為save()為保存當前canvas狀態(tài)照宝,restore則為恢復上一次save()的狀態(tài)。
2.繪制內(nèi)圓
核心代碼如下
paintMiddleCircle.setColor(Color.GRAY);
paintInnerCircle.setColor(Color.GRAY);
paintMiddleCircle.setStrokeWidth(4);
paintInnerCircle.setStrokeWidth(4);
paintMiddleCircle.setStyle(Paint.Style.STROKE);
paintInnerCircle.setStyle(Paint.Style.STROKE);
PathEffect effects = new DashPathEffect(new float[]{5,5,5,5},1);
paintInnerCircle.setPathEffect(effects);
canvas.save() ;
canvas.translate(CENTER_X, CENTER_X);
canvas.drawCircle(0, 0, CENTER_X * 5 / 8, paintInnerCircle);
canvas.drawCircle(0, 0, CENTER_X * 3 / 4, paintMiddleCircle);
canvas.restore();
效果如圖3所示
代碼講解:
該功能比較簡單句葵。
在此處需要了解PathEffect及其子類的作用厕鹃,這里我們使用DashPathEffect繪制虛線。
細心的讀者還可以發(fā)現(xiàn)乍丈,我們使用的繪制圓形的方法不一樣剂碴。前面使用的是drawOval繪制橢圓,而在此處使用的是drawCircle直接畫圓轻专,效果都一樣忆矛。具體區(qū)別可以自己體會,一個是框死了畫內(nèi)切橢圓请垛,另一個是直接畫圓催训。
3.繪制輔助線
核心代碼如下
paintGap1.setColor(Color.WHITE);
paintGap2.setColor(Color.WHITE);
paintGap1.setStrokeWidth(2);
paintGap2.setStrokeWidth(4);
int a = (int) (2 * CENTER_X - mPaint.getStrokeWidth());
for ( int i=0;i<=60; i++) {
canvas.save() ;
canvas.rotate(-(-30 + 4 * i), CENTER_X, CENTER_X);
if ( i % 10 == 0 ) {
canvas.drawLine( a ,CENTER_X, 2 * CENTER_X, CENTER_X, paintGap2);
} else {
canvas.drawLine( a ,CENTER_X, 2 * CENTER_X, CENTER_X, paintGap1);
}
canvas.restore();
}
效果如圖4所示
代碼講解
在上面,我們曾假設了圓弧的角度為240度宗收。便于計算漫拭,我們將該圓弧劃分為6個區(qū),每個區(qū)占40度,每個區(qū)有10個小間隔混稽,每個小間隔的角度就是4度采驻。由于圓弧有30度是在水平線以下的审胚,所以我們的循環(huán)規(guī)則是上述代碼。canvas.rotate(-(-30 + 4 * i), CENTER_X, CENTER_X);
此處由于CENTER_X==CENTER_Y==r
,將上述代碼修改為canvas.rotate(-(-30 + 4 * i), CENTER_X, CENTER_Y);
或許更容易理解礼旅。rotate中參數(shù)>0為順時針旋轉(zhuǎn)膳叨,<0為逆時針旋轉(zhuǎn)。
4.圓環(huán)變圓弧
到目前為止痘系,我們畫的還只是個漸變圓環(huán)懒鉴,與效果圓弧還有些不同。下面我們將圓環(huán)處理為圓弧碎浇。
** 核心代碼如下 **
width = MeasureSpec.getSize(widthMeasureSpec);
height = (int) ( ( Math.tan(Math.PI / 6) + 1 ) * width / 2 ) ;
Path path = new Path();
path.moveTo(CENTER_X, CENTER_X);
path.lineTo(0, height);
path.lineTo(width, height);
path.lineTo(CENTER_X, CENTER_X);
path.close();
canvas.drawPath(path, paintBg);
效果圖5如下
** 代碼講解:**
首先我們需要調(diào)整視圖的高度临谱。在這之前我們都是令
width==height
,保證繪制出一個整圓∨В現(xiàn)在根據(jù)我們的假設圓弧度數(shù)240度悉默,其在水平線以下為30度,即PI/6苟穆。由數(shù)學公式計算得知抄课,其視圖高度為 height = r * tan(PI/6) + r
。這還不夠雳旅,調(diào)整完視圖的高度跟磨,我們需要將一些雜線,從視圖中除去攒盈,讓其看上去更像是個圓弧抵拘。
如圖6所示未去雜線的時候
我們利用圖層互相遮罩的原理。以圓心和視圖的兩個頂點型豁,連接成一個三角形僵蛛,可以達到掩蓋其與雜線的目的。也就是后面代碼的作用迎变。
記住在onDraw時候的一個原則:先畫的在畫布下方充尉,后畫的在畫布上方,后畫的會覆蓋先畫的衣形。從而達到圖5的效果驼侠。
5.文字的繪制
** 核心代碼如下**
private static final String[] text = {"950","極好","700","優(yōu)秀","650","良好","600","中等","550","較差","350","很差","150"};
for ( int i=0;i<=12;i++) {
canvas.save();
canvas.rotate(-(-120 + 20 * i ), CENTER_X, CENTER_X);
canvas.drawText(text[i],CENTER_X - 20 ,CENTER_X * 3 / 16,paintText);
canvas.restore();
}
效果圖7如下
** 代碼講解 **
我們已知每個區(qū)為40度。從參考效果圖上可以看出每隔20度就會有一段文字谆吴。我們知道在繪制文字的時候倒源,都是從左往右寫的。所以我們在旋轉(zhuǎn)畫布的時候纪铺,起始點需要在原來的基礎上再加90度相速,即逆時針旋轉(zhuǎn)120度,然后繪入文字鲜锚。當然這段繪制的過程需要在繪制三角形之后突诬,否則部分文字會被三角形的遮罩遮蓋起來苫拍。
6.最后的動效
if ( isSetReferValue ) {
float r1 = CENTER_X * 6 / 8 ;
canvas.save();
canvas.translate(CENTER_X, CENTER_X);
canvas.drawArc(new RectF(-r1, -r1, r1, r1), -210, currentRotateAngle, false, paintMiddleArc);
canvas.rotate( - 30 + currentRotateAngle );
Matrix matrix = new Matrix();
matrix.preTranslate(-r1 - bitmapWidth * 3/ 8,-bitmapHeight/2);
canvas.drawBitmap(bitmapLocation,matrix,paintBitmap);
canvas.restore();
}
public void setReferValue ( int referValue ,final RotateListener rotateListener) {
isSetReferValue = !isSetReferValue ;
if ( referValue <= 150 ) {
totalRotateAngle = 0f ;
} else if ( referValue <= 550 ) {
totalRotateAngle = ( referValue - 150 ) * 80 / 400f ;
} else if ( referValue <= 700 ) {
totalRotateAngle = ( referValue - 550 ) * 120 / 150f + 80 ;
} else if ( referValue <= 950 ) {
totalRotateAngle = ( referValue - 700 ) * 40 / 250f + 200;
} else {
totalRotateAngle = 210f ;
}
rotateAngle = totalRotateAngle / 60 ;
new Thread(new Runnable() {
@Override
public void run() {
boolean rotating = true ;
float value = 350;
while (rotating) {
try {
Thread.sleep(16);
} catch (InterruptedException e) {
e.printStackTrace();
}
currentRotateAngle += rotateAngle;
if ( currentRotateAngle >= totalRotateAngle ) {
currentRotateAngle = totalRotateAngle;
rotating = false;
}
if ( null != rotateListener) {
if ( currentRotateAngle <= 80 ) {
value = 350 + ( currentRotateAngle / 80 ) * 400 ;
} else if ( currentRotateAngle <= 200 ) {
value = 550 + ( ( currentRotateAngle - 80 )/ 120 ) * 150 ;
} else {
value = 700 + ( ( currentRotateAngle - 200 ) / 40 ) * 250 ;
}
rotateListener.rotate(currentRotateAngle,value);
}
postInvalidate();
}
}
}).start();
}
效果圖8如下
代碼講解
繪制的代碼中。首先我們要了解到繪制圓弧的方法為canvas.drawArc()
,此處我們要從左下角開始繪制圓弧旺隙,所以我們的起始旋轉(zhuǎn)角度為-210度绒极。
由于我們此處的原點在圓心。圖片要跟隨著已知的旋轉(zhuǎn)角度進行旋轉(zhuǎn)蔬捷。我們知道針對canvas.rotate()
方法垄提,當旋轉(zhuǎn)角度>0的時候,是順時針旋轉(zhuǎn)周拐;<0為逆時針旋轉(zhuǎn)铡俐。由于此處我們圖片的箭頭朝向向右,為了保證圖片的朝向指向圓心妥粟。我們旋轉(zhuǎn)的規(guī)則為- 30 + currentRotateAngle
,保證每一次在繪制圖形的時候审丘,都是在(x,y)為(-r1 - bitmapWidth * 3/ 8,-bitmapHeight/2)這個位置的時候繪制。最后恢復canvas勾给。
關于在計算totalRotateAngle
滩报、currentRotateAngle
以及 value
的時候,都是些簡單的算法播急。夾雜著很多硬編碼脓钾,耐心點應該可以讀懂,不做過多解釋桩警。
實現(xiàn)的七七八八可训,大致思路應該是這樣。
一些問題
- 在上文也提到了生真,參考的效果圖沉噩,并非是一個平滑的漸變。仔細觀察的話柱蟀,在600處有處瞬斷的跡象。
解決思路:利用上面講到過的PorterDuffXfermode蚜厉,將兩段不同的環(huán)形漸變长已,拼接而成。到達此效果昼牛。 - 關于優(yōu)化
- onDraw()方法中术瓮,canvas.save()與canvas.restore()方法多次使用,造成不必要的性能浪費贰健。
- 在執(zhí)行箭頭轉(zhuǎn)動效果的時候胞四,不需要在canvas上每次全部都重新繪制。只需要繪制需要繪制的部分區(qū)域即可伶椿,即臟矩形辜伟。在這里也就是箭頭所滾動范圍內(nèi)的部分區(qū)域圓環(huán)氓侧。讀者可以自行實現(xiàn)。
- 關于多線程
細心的人可以發(fā)現(xiàn)方法setReferValue()
,并沒有考慮多線程的情況导狡。此處只是demo约巷,場景也有限。沒做特殊處理旱捧。有興趣的讀者可以自行實現(xiàn)独郎。
后記
之前一直沒有記錄博客的習慣。現(xiàn)在寫完兩篇枚赡,發(fā)現(xiàn)將代碼翻譯成文不是一件容易的事氓癌。代碼在周三就基本完成了,文章也是一直拖著到現(xiàn)在才整理出來發(fā)布贫橙。要將每一個知識點贪婉,能夠簡單的表述出來,是比較難的一件事情料皇。落筆成文同面對面與人講述谓松,會不太一樣。以后要多加強這方面的練習践剂。也希望讀者們能夠一起來嘗試記錄鬼譬。遺留的問題,都不是很難逊脯,讀者可以自行嘗試的去實現(xiàn)优质。今天腦子有點疼,就寫到此了军洼。
源代碼在此下載:http://pan.baidu.com/s/1kTKUowJ
enjoy it巩螃!
想及時了解最新信息。掃一掃匕争,添加關注微信公眾號