網站首頁 編程語言 正文
MPAndroidChart自定義圖表
聲明:本文MPChart 代表的就是 MPAndroidChart。
1. 自定義Chart的Attribute
我們回憶一下自定義View的過程里,通常我們會將一些屬性控制Attribute通過自定義View的構造方法傳入,然后繪制或者layout的情況下使用這些屬性。Attribute類中的屬性,通過讀取attr.xml 中自定義的 View的 declare-styleable,在xml layoutInflate 時加載賦值給Attribute類。而MPChart的圖表同樣也是View,通過傳入Attribute屬性進行自定義相關的繪制,工欲善其事必先利其器。
例如,我們自定義一個CustomChart 繼承自BarChart,傳入CustomBarChartAttr 包含自定義的一些屬性控制變量的集合類, 類似圖1.0將一些公共的屬性放入BaseChartAttrs里。
圖1.0 自定義屬性Attribute
通過屬性控制,可以將我們自定義的一些差異性的數據,需要用戶設定的比如顏色,大小、圓角的半徑等暴露出來;對于共性的屬性比如坐標相關的,四個坐標,只顯示Bottom的XAxis,跟右邊的YAxis, 就可以直接在自定義的BarChart中寫死,工程需求不雷同于MPChart這種庫的提供者,他們需要考慮的是各色用戶的各種需求,所以需要考慮到API的全面性,而我們需要做的是盡量抽取共性到一個公共的類里,然后需求里的可變因素,減少出錯的幾率,將差異性的東西通過這里比如Attribute去設定,或者后續還會提到在Entry里包含屬性,Entry能夠具體到Chart里某一個item的屬性,而Attribute 一般只能控制到Chart, 或者這些Item的共有屬性上。
2. Render 自定義繪制邏輯
上篇文章里我們介紹了Chart的繪制,分別由負責各個小部件的Render去繪制,當自定義的需求只是需改局部比如圖表的主體的時候,而XAxis、YAxis、邊框等的繪制不用改變,則只要替換負責主體繪制的Render對象,例如MPChart原本庫里的Chart里有一個負責繪制圖表的主體的DataRenderer mRenderer; 這里只需替換該Render對象,將自定義的繪制邏輯加在自定義的Render里。
@Override
protected void init() {
super.init();
mRenderer = new CustomBarChartRenderer(this, mAnimator, mViewPortHandler);
}
這里自定義的是BarChart類型,因此CustomBarChartRenderer 繼承自BarChartRenderer, 這樣可以免去很多BarChart中原本的繪制,而只需關注需求所要的改動。
當拿到Renderer之后,我們可以隨心所欲地去繪制各種需求,比如如下繪制帶小紅花的7天的柱狀圖:
圖1.1 flowerBarChart
從上篇文章里我們了解到BarChart的繪制會在drawDataSet()方法里,涉及到繪制矩形位置的信息存儲在BarBuffer里,然后通過Transformer轉為RectF的坐標點,這里有7個矩形,所以Buffer里存儲有 4 * 7 = 28個值的數組,每次取四個值組成RectF, 然后就在RectF里繪制背景,繪制Bitmap、繪制各色各樣的需求都Okay。
圖1.2 FlowerBarChart 的繪制。
3. DataBuffer
接下來我們看一個實際的生產環境里的一種圖表的繪制,涉及到需要處理 Buffer 數據的存儲轉化。我們先看IPhone的 蘋果健康的一個數據圖表:
圖1.3 蘋果健康心率圖表
MPChart內目測是沒有這種類型圖表的,那么拿到這個我們該如何做呢?首先分析圖形中每個Item可能會有若干個小圓角矩形組成,如示例圖中綠色框里有三個小矩形組成,原來的BarBuffer數組4個值對應一個RectF在這里不在適用了,筆者這里提供的一種解決方案如下:每個Entry還是對應一個Item,這里自定義了一種SegmentEntry繼承自BarEntry,SegmentEntry 里包含了若干矩形繪制對應的數據model, SegmentRectModel, 因為同屬于一個Item,這些同屬于Item的小矩形都有相同的XAxis坐標屬性,所以SegmentRectModel 里只需保存YAxis對應所需的信息。
public class SegmentRectModel {
public float topValue;
public float bottomValue;
public int rectColor = -1;
public int boardColor = -1;
public int boardWidth = 0;//dp
}
open class SegmentBarEntry : RecyclerBarEntry {
@JvmField
var rectValueModelList: MutableList<SegmentRectModel>? = null
private var segmentRange = 0 // 每種業務數據的segmentRange不一樣,心率、血氧、血壓等。
var maxValue = 0
var minValue = 0
}
SegmentBarEntry內部存有 List 小矩形對應的業務數據。
這里簡單介紹一下如何生成這一些列的小區間SegmentRectModel[bottom, top] , 以心率值為例。比如這一天下來穿戴設備產生了一下的心率值:
【45, 46, 48, 49, 52, 67, 69, 70, 75, 78, 90, 93, 94, 97, 99, 103, 158, 164, 169, 170】
這里引入一個參數用來將這些散點數據歸并成 數區間,Segment_range。可能不同種類的數據,這個值不同,比如血氧 segment_range_spo 跟心率的 segment_range_hrm 取值不一樣,假如這里的segment_range_hrm取值5,那么上面的心率值計算后得到的區間列表為:
【[45, 52], [67,78], [90, 103], [158], [164,170]】這里有個單點值,可以寫成[158, 158], 由此對應成SegmentRectModel的 bottom, top 值, 單點在小矩形中退化成為一個小圓點。
這些SegmentEntry 最總已集合的形式存入到DataSet中,然后計算生成Buffer數據,這里的Buffer計算方式改變,所以專程定義一個類用來包含這種SegmentEntry的數據轉化邏輯。
//將DataSet中的SegmentEntry 的List<SegmentRectModel>的size累加給到Buffer
private int computeBufferSize(IBarDataSet dataSet){
int rectListSizeSum = 0;
int entryCount = dataSet.getEntryCount();
for (int i = 0; i < entryCount; i++) {
BarEntry entry = dataSet.getEntryForIndex(i);
if (entry instanceof SegmentBarEntry) {
SegmentBarEntry segmentBarEntry = (SegmentBarEntry) entry;
List<SegmentRectModel> rectList = segmentBarEntry.rectValueModelList;
rectListSizeSum += rectList.size();
}
}
return rectListSizeSum;
}
int bufferSize = computeBufferSize(set) * 4;//乘以4 表示矩形。
mBarBuffers[i] = new SegmentBarChartBuffer(bufferSize, barData.getDataSetCount(), set.isStacked());
在自定義的SegmentBarChartBuffer的feed() 內計算并保存SegmentEntry 中的系列小矩形經過Transformer轉化前的數據。
圖1.4 SegmentBarChartBuffer
轉化成Buffer 內的數據后,至此交給Transformer,最后會生成一系列的小矩形,同一Item Entry下面的RectF擁有相同的X軸坐標值,繪制的Render也不再考慮這些差異性,從Buffer數組的0號index開始取,每次取4個繪制RectF,需要注意的是當bottom - top 值小于 width時,可能退化成一條線。需要將它處理成一個圓點。
圖1.5 SegmentChart的繪制
至此,整個SegmentChart的繪制講解完了,比較完整的一個流程,涉及到圖表的整體屬性Attribute定義,Item Entry根據需求自定義,然后自定義Buffer數據的feed方式,最終交給Render去繪制的邏輯。最后看一下繪制的效果圖。
圖1.6 MPChart自定義心率圖表
原文鏈接:https://juejin.cn/post/7136150356676837413#heading-2
相關推薦
- 2022-07-29 Django?狀態保持搭配與存儲的實現_python
- 2022-07-22 HttpClient如何自定義重試方法
- 2022-05-02 C++?多繼承詳情介紹_C 語言
- 2022-07-26 TensorFlow使用keras報錯ImportError: cannot import name
- 2023-10-10 前端根據后端數據生成表格 行列合并 指定表頭
- 2022-11-06 pandas?dataframe?drop函數介紹_python
- 2023-07-13 react中useEffect基本用法及底層機制
- 2023-02-27 pandas?實現?in?和?not?in?的用法及使用心得_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支