可視化數(shù)據(jù)圖 - 自定義扇形餅狀圖
如覺(jué)得文字版本不適合閱讀疮蹦,請(qǐng)點(diǎn)擊這里
組件特性
- 根據(jù)傳入的四種數(shù)據(jù)量大小自動(dòng)顯示扇形的弧度大小以及弧度半徑大小
- 根據(jù)弧度所在的角度自動(dòng)畫(huà)出橫線和斜線位置并顯示狀態(tài)
效果圖
實(shí)現(xiàn)原理
- 將圓弧和線拆分來(lái)看,先畫(huà)圓弧,再畫(huà)線
- 弧度的畫(huà)法,利用sdk的new Recf(start_x, start_y, end_x, end_y)來(lái)限定幅度半徑陨舱,在用canvas.drawArc畫(huà)出多少的弧度
- 畫(huà)線抑堡,線分為斜線和直線(斜線是半徑方向,直線時(shí)水平方向)
線的畫(huà)法有兩種:
方法一:
根據(jù)已知弧度的角度和半徑枚驻,在坐標(biāo)軸上利用正余弦sin和cos計(jì)算出斜線的終點(diǎn)坐標(biāo),用drawLine畫(huà)出即可株旷,直線部分亦可
方法二:
畫(huà)布canvas有一個(gè)重要的api再登,canvas.rotate(角度,x, y)晾剖,這個(gè)方法就是圍繞著某一個(gè)點(diǎn)進(jìn)行旋轉(zhuǎn)锉矢;這剛好可以利用到我們的斜線畫(huà)法上來(lái),步驟如下:
- 畫(huà)斜線 -- 畫(huà)一個(gè)圓弧后齿尽,以圓心為旋轉(zhuǎn)點(diǎn)沽损,旋轉(zhuǎn)一定圓弧角度,屏幕x軸剛好在一般角度上循头,x軸就是我們斜線方向绵估,此時(shí)斜線的終點(diǎn)就是圓弧半徑在加一點(diǎn)距離即可
- 畫(huà)直線 -- 斜線畫(huà)完后,我們?cè)诜聪蛐D(zhuǎn)卡骂,之前旋轉(zhuǎn)多少度国裳,我們就反向旋轉(zhuǎn)多少度,反向旋轉(zhuǎn)的點(diǎn)選取上面斜線的終點(diǎn)全跨,此時(shí)直線的起點(diǎn)就是上面斜線的終點(diǎn)缝左,直線終點(diǎn)我們又是x軸上面加一段距離即可
- 上面完成后,我們要回復(fù)初始化狀態(tài)canvas.restoreCount()恢復(fù)到最初的狀態(tài)浓若,然后繼續(xù)畫(huà)另一個(gè)圓弧的斜線和直線
很明顯上述方法二不需要特別復(fù)雜的計(jì)算即可完成渺杉,我這里也采用了方法二
使用方法
布局layout
<com.jack.sectorview.SectorView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
app:lost_status="@color/colorLightBlue"
app:open_status="@color/colorPink"
app:normal_status="@color/colorGreen"
app:lowpower_status="@color/colorYellow"
android:id="@+id/sector"/>
上面用了我自定義的屬性,分別用以標(biāo)記幾個(gè)扇形圖的顏色挪钓,可以自定義是越,也可以使用我默認(rèn)的顏色
代碼java
//模擬數(shù)據(jù) index 0~3分別對(duì)應(yīng)扇形圖的 狀態(tài)0~3
ArrayList<Integer> testData = new ArrayList<>();
testData.add(1511);
testData.add(1156);
testData.add(7541);
testData.add(5330);
mSectorView = (SectorView) findViewById(R.id.sector);
mSectorView.setmAngelePerStatus(testData);
如上面注釋,數(shù)據(jù)對(duì)應(yīng)關(guān)系看效果圖就可以了诵原;
mSectorView.setmAngelePerStatus(testData);這句代碼的作用就是講每個(gè)數(shù)據(jù)量轉(zhuǎn)化為角度英妓,方法名可能取得不恰當(dāng)
部分代碼解釋
成員
private int[] mAngelePerStatus; //每個(gè)狀態(tài)對(duì)應(yīng)的角度
private int[] mAngeleSort; //對(duì)上面的角度進(jìn)行排序挽放,此數(shù)組存放了上面每個(gè)角度排在第幾位
/**
* 圓心類绍赛,標(biāo)注圓心的位置
*/
private class CirclePoint{
int x;
int y; //圓心的坐標(biāo)
int radius;
int[] radiusLevel = new int[4]; //因?yàn)橛兴膫€(gè)圓弧蔓纠,所以設(shè)置了四個(gè)等級(jí)的圓弧半徑
}
畫(huà)圓弧
/**
* 畫(huà)基礎(chǔ)的扇形,必須先畫(huà)扇形后面再畫(huà)橫線斜線等
* @param canvas
*/
private void drawSector(Canvas canvas){
RectF rectF;
int startAngle = 0;
for(int i = 0; i < mAngelePerStatus.length; i++){
if(mAngelePerStatus[i] <= 0){ //小于等于0說(shuō)明這部分沒(méi)有的
continue;
}
int sweep = mAngelePerStatus[i];
if(i == mAngelePerStatus.length - 1) {
sweep = 360 - startAngle;
}
int currentAngleLevel = mAngeleSort[i];
int currentAngleRadius = mCircle.radiusLevel[currentAngleLevel];
int startX = mCircle.x - currentAngleRadius;
int startY = mCircle.y - currentAngleRadius;
int endX = mCircle.x + currentAngleRadius;
int endY = mCircle.y + currentAngleRadius;
rectF = new RectF(startX, startY, endX, endY);
mPaint.setColor(mColorPerStatus[i]);
canvas.drawArc(rectF, startAngle, sweep, true, mPaint);
startAngle += mAngelePerStatus[i];
}
}
圓弧這個(gè)好理解吗蚌,拿到圓弧角度腿倚,拿到其圓弧半徑,在canvas上畫(huà)出即可蚯妇;在計(jì)算角度的時(shí)候可能出現(xiàn)了1度左右的誤差敷燎,所以為了防止最后一個(gè)角度沒(méi)有畫(huà)到360度的線上,我做了一定的角度補(bǔ)償
畫(huà)斜線和直線
/**
* 畫(huà)線和字符
* @param canvas
*/
private void drawLineAndText(Canvas canvas){
int baseAngle = 0;
int src;
for(int i = 0; i < mAngelePerStatus.length; i++){
src = canvas.save();
if(mAngelePerStatus[i] <= 0){
continue;
}
int currentAngleLevel = mAngeleSort[i];
int rotateAngle = baseAngle + mAngelePerStatus[i] / 2; //需要旋轉(zhuǎn)的角度
//剛好在坐標(biāo)軸上的情況箩言,畫(huà)斜線會(huì)變成垂直水平的線 不好看 稍微偏移一下
if(rotateAngle%90 == 0){
rotateAngle = rotateAngle + 3;
}
int slashLineEndX = mCircle.x + mCircle.radiusLevel[currentAngleLevel] + mSlashLineLength;
canvas.rotate(rotateAngle, mCircle.x, mCircle.y);
mPaint.setColor(mColorPerStatus[i]);
canvas.drawLine(mCircle.x, mCircle.y, slashLineEndX, mCircle.y, mPaint); //畫(huà)斜線
canvas.rotate(-rotateAngle, slashLineEndX, mCircle.y);
int horizonLineEndX;
int textStartY;
int textStartX;
//畫(huà)水平直線時(shí)要考慮在左邊還是右邊硬贯,兩邊起點(diǎn)和終點(diǎn)是有區(qū)別的
if(rotateAngle > 90 && rotateAngle < 270){
horizonLineEndX = mCircle.x + mCircle.radiusLevel[currentAngleLevel] - mHorizonLineLength;
if(horizonLineEndX < 0){ //防止其超出左邊邊界
horizonLineEndX = 0;
}
textStartX = horizonLineEndX;
}else{
horizonLineEndX = mCircle.x + mCircle.radiusLevel[currentAngleLevel] + mHorizonLineLength;
if(horizonLineEndX > mViewWidth){ //防止超出右邊界
horizonLineEndX = mViewWidth;
}
textStartX = slashLineEndX + 5;
}
Log.i(TAG, "horizontal start " + slashLineEndX + " " + horizonLineEndX);
canvas.drawLine(slashLineEndX, mCircle.y, horizonLineEndX, mCircle.y, mPaint); //畫(huà)直線
//畫(huà)字時(shí)考慮上半圓和瞎下半圓時(shí),上半圓字在上面陨收,下半圓字在下面
if(rotateAngle < 180){
textStartY = mCircle.y + 40;
}else{
textStartY = mCircle.y - 20;
}
canvas.drawText(mStatusInfo[i], textStartX, textStartY, mTextPaint);
canvas.restoreToCount(src);
baseAngle += mAngelePerStatus[i];
Log.i(TAG, "baseAngle " + baseAngle + " rotateAngle " + rotateAngle);
}
}
思路就是我最上面的原理一樣饭豹,旋轉(zhuǎn)和反向旋轉(zhuǎn),旋轉(zhuǎn)的時(shí)候如果剛好旋轉(zhuǎn)到90务漩、180拄衰、370這樣的角度上,斜線會(huì)出現(xiàn)垂直和水平的斜線饵骨,這樣不美觀翘悉,所以當(dāng)出現(xiàn)這些情況又做了一些角度補(bǔ)償
相鄰角度太小的情況
測(cè)試的時(shí)候發(fā)現(xiàn)一個(gè)問(wèn)題,相鄰兩個(gè)角度太小如3和6度居触,這種情況導(dǎo)致斜線或直線或漢子會(huì)出現(xiàn)重合的現(xiàn)象妖混,這又是不好的,所以這個(gè)方法是就是用來(lái)處理這個(gè)問(wèn)題的轮洋;采取的策略就是:
- 當(dāng)檢測(cè)出角度小于5度并相鄰角度差也小于5制市,會(huì)強(qiáng)制后面一個(gè)角度變大一點(diǎn),這樣增大了角度間距砖瞧,寫(xiě)字時(shí)就不會(huì)重合
- 最后一個(gè)角度和第一個(gè)角度出現(xiàn)了上述情況后息堂,就不能茫然的把第一個(gè)角度變大,變大可能會(huì)導(dǎo)致第一個(gè)和第二個(gè)又重合了块促,所以就只能把第一個(gè)加的值加小一點(diǎn)荣堰,這樣就可以解決了
- 上述的做法會(huì)帶來(lái)一個(gè)問(wèn)題,就是四個(gè)角度和會(huì)超過(guò)360竭翠;這需要把多出來(lái)的角度算出來(lái)振坚,從四個(gè)角度中最大角度減去多出來(lái)的就可以了
/**
* 角度小于5度的情況,并且相鄰角度相差不大的要相鄰狀態(tài)其中一個(gè)要加一個(gè)角度,
* 否則寫(xiě)字時(shí)文字會(huì)重合在一起
* @param array
*/
private int angleBuChang(int[] array){
int length = array.length - 1;
if(length <= 2){
return 360;
}
int angle0, angle1;
for(int i = 0; i< length + 1; i++){
angle0 = array[i];
if(i + 1 > length){
angle1 = array[0];
}else{
angle1 = array[i+1];
}
int angleRemain = Math.abs(angle1 - angle0);
if(angle0 < 5 && angleRemain < 5){ //這兩個(gè)加數(shù)就是調(diào)整角度差的
if(i+1 > length){
array[0] += angleRemain + 10;
}else {
array[i+1] += angleRemain + 15;
}
}
}
int totalAngle = 0;
for(int i = 0; i < length + 1; i++){
totalAngle += array[i];
}
return totalAngle;
}
綜上斋扰,基本上解決了所有的問(wèn)題了渡八,如還有不正確的啃洋,可以給我指正并修改;