Android自定義View實(shí)現(xiàn)多邊形統(tǒng)計(jì)圖示例代碼
前言
最近利用空閑時間學(xué)習(xí)了自定義View的一些知識,為了鞏固,寫了一個小東西,順便分享出來,下面話不多說了,來一起看看詳細(xì)的介紹吧。
簡介
一個多邊形統(tǒng)計(jì)圖。邊數(shù),每個方向的值,每個點(diǎn)的文字等等都是可以設(shè)置的。

下面就來分析一下這個自定義View
這個view由以下幾個部分組成
- M層N邊形
- 中心到各頂點(diǎn)的連線
- 填充區(qū)域
- 文字
@Override
protected void onDraw(Canvas canvas) {
if (!canDraw()) {
return;
}
canvas.translate(width / 2, height / 2);
computeMaxPoint();
drawPolygon(canvas);
drawLine(canvas);
drawArea(canvas);
drawText(canvas);
}
我們一步一步來說明
繪制多邊形
繪制多邊形主要用到的是Path這個東西。具體的思路就是先計(jì)算好每個點(diǎn)的位置,同Path的lineTo方法連接起來,然后繪制。
我的做法是先算出最大的半徑(再之后還會用到,建議單獨(dú)存起來),然后根據(jù)所在層數(shù)來計(jì)算每一層的半徑,利用cos函數(shù)各sin函數(shù)計(jì)算出每一層各頂點(diǎn)的位置。
計(jì)算最大半徑并且保存頂點(diǎn)
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
width = w;
height = h;
maxRadius = (float) ((width / 2) * 0.8);
postInvalidate();
}
/*
計(jì)算最大半徑,之后的位置都是基于最大半徑的比例
*/
public void computeMaxPoint() {
maxPointXList = new ArrayList<>();
maxPointYList = new ArrayList<>();
for (int i = 0; i < eageCount; i++) {
float currentAngle = i * angle - 90;
float currentX = (float) (maxRadius * Math.cos((currentAngle / 180) * Math.PI));
float currentY = (float) (maxRadius * Math.sin((currentAngle / 180) * Math.PI));
maxPointXList.add(currentX);
maxPointYList.add(currentY);
}
}
注意:cos和sin都是按照弧度制計(jì)算的,要換算。
這里解釋一下為currentAngle什么要減去90度
按照android的坐標(biāo)系,如果不減去90度直接乘上cos的話,第一個頂點(diǎn)會默認(rèn)在中心的右側(cè),而一般的認(rèn)知是第一個點(diǎn)在正上方,所以減去90度
按照比例和層數(shù)邊數(shù)繪制多邊形
/*
繪制多邊形和每一層
*/
private void drawPolygon(Canvas canvas) {
Path path = new Path();
for (int i = 0; i < loopCount; i++) {
path.reset();
//依據(jù)最大半徑和角度來判斷每一層點(diǎn)的位置
float rate = computeRate(i + 1, loopCount);
for (int j = 0; j < eageCount; j++) {
float currentX = maxPointXList.get(j) * rate;
float currentY = maxPointYList.get(j) * rate;
if (j == 0) {
path.moveTo(currentX, currentY);
} else {
path.lineTo(currentX, currentY);
}
}
path.close();
canvas.drawPath(path, eagePaint);
}
}
代碼還是很容易的吧,要是看不懂的話自己動手算算就知道了,很容易計(jì)算各個點(diǎn)的位置。
繪制連線
由于之前保存了頂點(diǎn)的坐標(biāo),這個就很容易了
/*
畫出從中心向各頂點(diǎn)的連線
*/
private void drawLine(Canvas canvas) {
Path path = new Path();
for (int i = 0; i < eageCount; i++) {
path.reset();
path.lineTo(maxPointXList.get(i), maxPointYList.get(i));
canvas.drawPath(path, eagePaint);
}
}
繪制覆蓋區(qū)域
這個原理其實(shí)和繪制多邊形是一樣的,就是對頂點(diǎn)坐標(biāo)乘的比例發(fā)生了變化。每個方向的數(shù)值是由用戶傳遞進(jìn)來的。
/*
繪制個方向值覆蓋的區(qū)域
*/
private void drawArea(Canvas canvas) {
Path path = new Path();
//原理就是用path根據(jù)各方向值創(chuàng)建一個封閉的區(qū)域,然后填充
for (int i = 0; i < eageCount; i++) {
float rate = pointValue.get(i);
float currentX = maxPointXList.get(i) * rate;
float currentY = maxPointYList.get(i) * rate;
if (i == 0) {
path.moveTo(currentX, currentY);
} else {
path.lineTo(currentX, currentY);
}
}
path.close();
canvas.drawPath(path, areaPaint);
}
繪制文字
說實(shí)話,前面的沒有什么難點(diǎn),但是唯獨(dú)繪制文字有許多麻煩事。主要是文字是默認(rèn)自左向右的,最上面和最先面的文字倒是沒啥,左側(cè)和右側(cè)的文字就會出現(xiàn)問題了,文字會繪制到多邊形上,看起來特別難受。這里我的解決辦法就是前面圖中看到的,讓字跟著多邊形的頂點(diǎn)位置一起旋轉(zhuǎn)。
/*
繪制文字
*/
private void drawText(Canvas canvas) {
if (pointName == null) {
return;
}
//繪制文字的難點(diǎn)在于無法最好的適配屏幕的位置,會發(fā)生難以控制的偏倚
for (int i = 0; i < pointName.size(); i++) {
//解決辦法就是讓文字在不同的角度也發(fā)生旋轉(zhuǎn),并且在x軸上減去一定的數(shù)值來保證正確的位置
float currentAngle = i * angle;
//180度需要也別的處理,讓它正著顯示,不然就是倒著的
if (currentAngle == 180) {
float currentX = maxPointXList.get(i) * 1.1f;
float currentY = maxPointYList.get(i) * 1.1f;
canvas.drawText(pointName.get(i), currentX - (textPaint.getTextSize() / 4)
* (pointName.get(i).length()), currentY, textPaint);
} else {
canvas.save();
float currentX = maxPointXList.get(0) * 1.1f;
float currentY = maxPointYList.get(0) * 1.1f;
//旋轉(zhuǎn)畫布,達(dá)到旋轉(zhuǎn)文字的效果
canvas.rotate(currentAngle);
canvas.drawText(pointName.get(i), currentX - (textPaint.getTextSize() / 4)
* (pointName.get(i).length()), currentY, textPaint);
canvas.restore();
}
}
}
到這里,整個組件就繪制完成了
額外的屬性
如果單純只是想畫出這個組件來,其實(shí)沒啥難度。我們可以在加一些別的東西讓他更加實(shí)用。
動畫效果
利用屬性動畫的知識,我們可以做到讓中間的填充區(qū)域慢慢的擴(kuò)散出來。原理也簡單,就是把0到1用屬性計(jì)算展開,當(dāng)做一個演化的比例,讓各個方向的值乘上這個數(shù)值,繪制一個比原先覆蓋區(qū)域小的區(qū)域就可以了。
/*
用屬性動畫繪制組件
*/
public void draw() {
if (canDraw()) {
final Float[] trueValues = pointValue.toArray(new Float[pointValue.size()]);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
valueAnimator.setDuration(1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float rate = animation.getAnimatedFraction();
for (int i = 0; i < pointValue.size(); i++) {
pointValue.set(i, trueValues[i] * rate);
}
invalidate();
}
});
valueAnimator.start();
}
}
定義xml屬性
我們正常使用系統(tǒng)組件的時候都會寫一大堆的xml來控制我們組件的屬性,自定義View也可以嘗試這些
首先在value下創(chuàng)建atts文件

然后指定你想要的屬性名稱和類型

再然后就是讓atts和我們的view聯(lián)系上。這個也簡單,仔細(xì)觀察View的構(gòu)造方法中的參數(shù),有這么一個玩意 AttributeSet attrs
public PolygonView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public PolygonView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
它就是聯(lián)系xml和view的紐帶
xmlns:app="http://schemas.android.com/apk/res-auto" <com.totoro.xkf.polygonview.PolygonView android:id="@+id/pv_polygon_view" android:layout_width="match_parent" android:layout_height="match_parent" app:areaColor="@android:color/holo_blue_light" app:eageColor="@android:color/black" app:eageCount="6" app:loopCount="4" app:textColor="@android:color/black" />
public void init(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Polygon);
initPaint();
setTextColor(typedArray.getColor(R.styleable.Polygon_textColor, Color.BLACK));
setLoopCount(typedArray.getInteger(R.styleable.Polygon_loopCount, 0));
setEageCount(typedArray.getInteger(R.styleable.Polygon_eageCount, 0));
setAreaColor(typedArray.getColor(R.styleable.Polygon_areaColor, Color.BLUE));
setEageColor(typedArray.getColor(R.styleable.Polygon_eageColor, Color.GRAY));
typedArray.recycle();
}
xmlns:app=http://schemas.android.com/apk/res-auto
這個東西不能忘了
快速使用
感謝你看到這里,如果你想使用這個組件但是不想自己寫的話歡迎訪問
項(xiàng)目Github里面有講如何添加依賴,導(dǎo)入組件
如果能幫到你的話,不勝榮幸?。?!
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
- android自定義環(huán)形統(tǒng)計(jì)圖動畫
- Android自定義條形對比統(tǒng)計(jì)圖
- Android自定義View——扇形統(tǒng)計(jì)圖的實(shí)現(xiàn)代碼
- Android編程實(shí)現(xiàn)canvas繪制餅狀統(tǒng)計(jì)圖功能示例【自動適應(yīng)條目數(shù)量與大小】
- Android編程實(shí)現(xiàn)canvas繪制柱狀統(tǒng)計(jì)圖功能【自動計(jì)算寬高及分度值、可左右滑動】
- Android 實(shí)現(xiàn)會旋轉(zhuǎn)的餅狀統(tǒng)計(jì)圖實(shí)例代碼
- Android自定義控件橫向柱狀統(tǒng)計(jì)圖
相關(guān)文章
Android如何動態(tài)調(diào)整應(yīng)用字體大小詳解
這篇文章主要給大家介紹了關(guān)于Android如何動態(tài)調(diào)整應(yīng)用字體大小的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05
Android開發(fā)5:應(yīng)用程序窗口小部件App Widgets的實(shí)現(xiàn)(附demo)
本篇文章主要介紹了android應(yīng)用程序窗口小部件App Widgets的實(shí)現(xiàn),具有一定的參考價值,有需要的可以了解一下。2016-11-11
Android?Studio實(shí)現(xiàn)簡單計(jì)算器開發(fā)
這篇文章主要為大家詳細(xì)介紹了Android?Studio實(shí)現(xiàn)簡單計(jì)算器開發(fā),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05
Android APK文件在電腦(PC虛擬機(jī))上面運(yùn)行方法
APK是Android系統(tǒng)的發(fā)布的工程包,很多時候我們想在電腦上而非Android手機(jī)上面運(yùn)行它,需要的朋友可以了解下2012-12-12
Android如何實(shí)現(xiàn)底部菜單固定到底部
這篇文章主要介紹了Android如何實(shí)現(xiàn)底部菜單固定到底部,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-09-09
android設(shè)備間實(shí)現(xiàn)無線投屏的示例代碼
Android提供了MediaProjection來實(shí)現(xiàn)錄屏,通過MediaProjection可以獲取當(dāng)前屏幕的視頻流,而視頻流需要通過編解碼來壓縮進(jìn)行傳輸,通過MediaCodec可實(shí)現(xiàn)視頻的編碼和解碼,這篇文章主要介紹了android設(shè)備間實(shí)現(xiàn)無線投屏,需要的朋友可以參考下2022-06-06

