1. 引言
電視項目中需要一個折線圖表示節電數據變化情況,類比 H5 來說,Android 中也應該有比較成熟的控件,經過調研后,發現 MPAndroidChart 功能比較強大,網上也有人說可能是目前 Android 開發最好用的一個三方庫了,功能非常強大,集成簡單。
這里把我的集成使用過程及使用中發現的強大驚喜記錄下來,留作團隊財富,可供后續參考。
首先,簡單的介紹下強大的 MPAndroidChart,它支持常用的各種圖:柱狀圖(橫向,豎向)、線狀圖(多種效果)、餅狀圖、點狀圖,屬性也很簡單,我們使用的時候只需要熟悉控件的 11 各種屬性即可。核心功能如下:
-
支持 x,y 軸縮放
-
支持拖拽
-
支持手指滑動
-
支持高亮顯示
-
支持保存圖表到文件中
-
支持從文件(txt)中讀取數據
-
預先定義顏色模板
-
自動生成標注
-
支持自定義 x,y 軸的顯示標簽
-
支持 x,y 軸動畫
-
支持 x,y 軸設置最大值和附加信息
-
支持自定義字體,顏色,背景,手勢,虛線等
2. 集成
集成很簡單,直接導入作為依賴就可以,以下寫了一個最小集成的例子,主要為了說明步驟:
2.1 引入依賴到工程中
// 項目工程的 build.gradle
repositories {maven { url "https://jitpack.io" }
}// Module的 build.gradle
dependencies {implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
}
2.2?Layout 中添加控件
<!--節電折線圖-->
<com.github.mikephil.charting.charts.LineChartandroid:id="@+id/chart1"android:layout_width="0dp"android:layout_height="0dp"android:layout_alignParentTop="true"android:layout_alignParentBottom="true"android:layout_alignParentStart="true"android:layout_alignParentEnd="true"android:layout_marginTop="@dimen/tvcommon_px131"android:layout_marginBottom="@dimen/tvcommon_px60"android:layout_marginStart="@dimen/tvcommon_px44"android:layout_marginEnd="@dimen/tvcommon_px30" />
2.3 Activity 中使用
// 3.4 節電詳情頁
mChart = findViewById(R.id.chart1)
initChat()
setChatData()
2.3.1?初始化
private fun initChat() {mChart.setBackgroundColor(resources.getColor(R.color.color_00000000))mChart.description.isEnabled = false// 2. X 軸樣式val xAxis: XAxis = mChart.getXAxis()xAxis.setDrawGridLines(false)// xAxis.enableGridDashedLine(10f, 10f, 0f);xAxis.position = XAxis.XAxisPosition.BOTTOM// 3. Y軸樣式mChart.axisRight.isEnabled = false // disable dual axis (only use LEFT axis)var yAxis: YAxis = mChart.axisLeftyAxis.axisMaximum = 200fyAxis.axisMinimum = 0f
}
2.3.2?Activity 中設置數據
public void setChatData(){List<Entry> entries=new ArrayList<>();List<Entry> entries1=new ArrayList<>();entries.add(new Entry(3f,20));entries1.add(new Entry(5f,30));LineDataSet dataSet=new LineDataSet(entries,"數據一");LineDataSet dataSet1=new LineDataSet(entries1,"數據二");List<ILineDataSet> list=new ArrayList<>();list.add(dataSet);list.add(dataSet1);LineData lineData=new LineData(list);mChat.setData(lineData); //將模擬數據用于線形圖,在線形圖顯示}
2.3.3?大功告成
3. 使用總結
MPAndroidChart 的強大之處在于它的很多功能都可定制,只要你有想法,大部分都有解決方法,哪怕一時沒有,只要肯找,說不準就能發現。
電視項目中要求的是一個折線圖,所以這里的使用總結大多集中在折線及我們要實現的效果上,其它未涉及的圖和屬性暫時不寫,后續使用的時候再作探索及總結。
3.1 整體功能及專業術語一覽(網上找的一張圖,鎮樓,哈哈)
3.2 基礎設置 (非數據類型,在初始化時設置)
首先,在 initChat()函數中,進行了一些基礎設置,把一些用不到的功能關閉:
private fun initChat() {mChart.setBackgroundColor(resources.getColor(R.color.color_00000000))mChart.description.isEnabled = falsemChart.setTouchEnabled(false)mChart.setDrawGridBackground(false)mChart.isDragEnabled = falsemChart.setScaleEnabled(false)mChart.setPinchZoom(false)mChart.legend.isEnabled = false
}
3.3 x、y 軸設置 (非數據類型,在初始化時設置)
這個也不屬于數據設置,所以在初始化時進行,如下,分別進行了線寬,線顏色,Label 的字體大小、顏色、還有網格線:
private fun initChat() {...// 2. X 軸樣式val xAxis: XAxis = mChart.getXAxis()xAxis.setDrawGridLines(false)// xAxis.enableGridDashedLine(10f, 10f, 0f);xAxis.position = XAxis.XAxisPosition.BOTTOMxAxis.axisLineWidth = resources.getDimension(R.dimen.tvcommon_px1)xAxis.axisLineColor = resources.getColor(R.color.color_979797)xAxis.textSize = resources.getDimension(R.dimen.tvcommon_sp16)xAxis.textColor = resources.getColor(R.color.white_60alpha)xAxis.valueFormatter = object : ValueFormatter() {override fun getAxisLabel(value: Float, axis: AxisBase?): String {// 自定義 X 軸顯示內容return super.getAxisLabel(value, axis) + "月"}}// 3. Y軸樣式mChart.axisRight.isEnabled = false // disable dual axis (only use LEFT axis)var yAxis: YAxis = mChart.axisLeftyAxis.axisLineWidth = resources.getDimension(R.dimen.tvcommon_px1)yAxis.axisLineColor = resources.getColor(R.color.color_979797)yAxis.textSize = resources.getDimension(R.dimen.tvcommon_sp16)yAxis.textColor = resources.getColor(R.color.white_60alpha)yAxis.setDrawGridLines(true) // horizontal grid linesyAxis.enableGridDashedLine(resources.getDimension(R.dimen.tvcommon_px6),resources.getDimension(R.dimen.tvcommon_px6),0f)yAxis.axisMaximum = 200fyAxis.axisMinimum = 0f
}
3.4 折線類型設置
需要在 填充數據時,給 LineDataSet 設置一個 mode,如下
private fun setChatData() {...val set1 = LineDataSet(values, "DataSet 1")set1.mode = LineDataSet.Mode.HORIZONTAL_BEZIER // 折線類型
}
setMode(LineDataSet.Mode mode),設置模式有四種: 1.CUBIC_BEZIER 立方曲線 2.LINEAR 直線 3.STEPPED 階梯 4.HORIZONTAL_BEZIER 水平曲線
電視項目這里需要的是一個曲線,所以設置為 HORIZONTAL_BEZIER
3.5 頂點設置
頂點可進行 icon 繪制、小圓點(實心/空心)、自定義值顯示
private fun setChatData() {...val set1 = LineDataSet(values, "DataSet 1")set1.mode = LineDataSet.Mode.HORIZONTAL_BEZIER // 折線類型// 頂點set1.setValueFormatter(object : ValueFormatter() {override fun getFormattedValue(value: Float): String {return "" // 這里返回空,即頂點不顯示值,否則不太好看}})set1.valueTextSize = resources.getDimension(R.dimen.tvcommon_sp12)set1.setDrawIcons(false) // 不顯示端點的iconset1.setDrawCircles(false) // 顯示頂點圓quanset1.setDrawCircleHole(true) // 空心還是實心
}
3.6 折線及填充設置
private fun setChatData() {...// 折線set1.color = resources.getColor(R.color.blue_gray_32B5E6)set1.setCircleColor(resources.getColor(R.color.blue_gray_32B5E6))set1.lineWidth = resources.getDimension(R.dimen.tvcommon_px2)set1.circleRadius = resources.getDimension(R.dimen.tvcommon_px4)// 折線包裹起來的區域set1.fillFormatter = IFillFormatter { dataSet, dataProvider -> mChart.getAxisLeft().getAxisMinimum() }if (Utils.getSDKInt() >= 18) {// drawables only supported on api level 18 and aboveval drawable = ContextCompat.getDrawable(this, R.drawable.shape_chat_fill_blue)set1.fillDrawable = drawable} else {set1.fillColor = R.color.blue_gray_32B5E6}set1.setDrawFilled(true)
}
3.7 X 軸自定義顯示 (非數據類型,在初始化時設置)
再回到 X 軸,由于項目要求,X 軸要顯示節電的日期,但是構造數據時只是一個數組,默認顯示的是數組的下標,所以這里需要自定義
private fun initChat() {... // 2. X 軸樣式val xAxis: XAxis = mChart.getXAxis()...xAxis.valueFormatter = object : ValueFormatter() {override fun getAxisLabel(value: Float, axis: AxisBase?): String {// 自定義 X 軸顯示內容,這里可以通過 axis 中的 postion 從數據中進行一個映射,找到它對應的日期return super.getAxisLabel(value, axis) + "月"}}
}
4. 踩坑及解決
4.1 寬度或 margin 跟自己設置的不一樣
1. 效果出來后,發現最右側不是我設置的,比我預計的靠左了,離邊比較遠,達不到 UI 設計的效果,如下:
2. 這里的設置應該不是通過簡單的 XML 位置屬性就能修改的了,因為這屬于 MPAndroidChat 控件的內部了,所以得深入 MPAndroidChat,找到產生間隙的原因,于是開始翻源碼
3. 經過翻閱源碼得知,它內部有一個 minOffset
@Override
public void calculateOffsets() {...float minOffset = Utils.convertDpToPixel(mMinOffset);mViewPortHandler.restrainViewPort(Math.max(minOffset, offsetLeft),Math.max(minOffset, offsetTop),Math.max(minOffset, offsetRight),Math.max(minOffset, offsetBottom));...
}
public void setMinOffset(float minOffset) {mMinOffset = minOffset;
}
??再往下查,原來 mMinOffset,是可能通過 setMinOffset()設置進去的,所以在我們的代碼 initChat()中添加上一行,再看效果,OK 啦~
private fun initChat() {...mChart.minOffset = 0f...
}
4.2 設置 x 軸的 Label 后,發現最底下一層有一點顯示不全,被截斷了,如下
1. 分析原因
(1)這個應該也是 MPAndroidChat 內部實現導致的,修改外部 Layout 中的 margin, padding 應該不生效,果然,試過之后,沒有生效,問題依舊
(2)16sp 時顯示是這個效果,而縮小字號后,比如 12sp, 就沒有問題,能夠顯示,猜測,是它內部寫死了一個距離,但看源碼內的相關說明,它的高度是自動計算的,如下
/*** Class representing the x-axis labels settings. Only use the setter methods to* modify it. Do not access public variables directly. Be aware that not all* features the XLabels class provides are suitable for the RadarChart.** @author Philipp Jahoda*/
public class XAxis extends AxisBase {/*** width of the x-axis labels in pixels - this is automatically* calculated by the computeSize() methods in the renderers*/public int mLabelWidth = 1;/*** height of the x-axis labels in pixels - this is automatically* calculated by the computeSize() methods in the renderers*/public int mLabelHeight = 1;
(3)按理說不應該出現這種情況,難道是 MPAndroidChat 的 Bug,是不是高版本就好了呢,于是上網查了一下,https://gitee.com/jiangsongbai/MPAndroidChart, Github 打不開,到這里的一個 fork 上看一下,發現我用的已經是最新版了 v3.1.0 (吐槽:版本號竟然帶個 v,看來不專業啊~)
(4)小插曲:由于項目中使用的是 dimen 中定義的 tvcommon_sp16,在不同分辨率下,可能不一樣,我原先是寫死的,抱著一點小希望,在代碼中使用 dimen 試一下,期望字體能夠變小一點,不觸發此問題,結果又失望了
xAxis.textSize = resources.getDimension(R.dimen.tvcommon_sp16)
2. extraBottomOffset
(1)再翻閱源碼,還是在 BarLineChatBase.java 中的 calculateOffsets() 函數,發現了端倪,如下:
@Override
public void calculateOffsets() {....offsetTop += getExtraTopOffset();offsetRight += getExtraRightOffset();offsetBottom += getExtraBottomOffset();offsetLeft += getExtraLeftOffset();float minOffset = Utils.convertDpToPixel(mMinOffset);mViewPortHandler.restrainViewPort(Math.max(minOffset, offsetLeft),Math.max(minOffset, offsetTop),Math.max(minOffset, offsetRight),Math.max(minOffset, offsetBottom));....
}
(2)這里有一個 getExtraBottomOffset(),再往下看,它是在基類 chat.java 中,有一個 mExtraBottomOffset,
/*** @return the extra offset to be appended to the viewport's bottom*/
public float getExtraBottomOffset() {return mExtraBottomOffset;
}
(3)看注釋,像是有點用,嘗試一下
private fun initChat() {....mChart.minOffset = 0fmChart.extraBottomOffset = resources.getDimension(R.dimen.tvcommon_px2) // 因為X軸的Label字體設為 16sp 后,最底下有一點顯示不全,需要在這里打上一個小補丁。....
}
(4)大功告成~
4.3 頂點的數據顯示想要隔位置顯示效果
跟產品討論的時候,把 UI 原先設計的選中點的值顯示出來的效果去掉了(因為電視上沒有觸摸,通過遙控器交互暫時先不做選中效果),如果顯示出來所有的數據,會顯得比較凌亂,所以想進行隔幾個顯示或什么效果,研究后,發現重截函數中沒有位置信息,無法進行計算是哪一個 postion,暫時無法做到不同的效果,只能統一顯示或不顯示,后續再進行深入研究,看是否能夠做到
private fun setChatData() {...val set1 = LineDataSet(values, "DataSet 1")set1.mode = LineDataSet.Mode.HORIZONTAL_BEZIER // 折線類型// 頂點set1.setValueFormatter(object : ValueFormatter() {override fun getFormattedValue(value: Float): String {return "" // 這里返回空,即頂點不顯示值,否則不太好看}})...
}
5. 小結
這里記錄了在電視項目中使用 MPAndroidChat 的一些使用心得,重點集中在折線上,通過對它的各種屬性進行修改自定義了自己的 UI 界面,達到與 UI 設計圖一樣的效果,也發掘出了 minOffset、extraBottomOffset 這樣的小眾屬性的使用場景及效果,希望能夠引起大家的一些共鳴,發現問題時,快速找到解決方法。
6. 團隊介紹
「三翼鳥數字化技術平臺-場景設計交互平臺」主要負責設計工具的研發,包括營銷設計工具、家電VR設計和展示、水電暖通前置設計能力,研發并沉淀素材庫,構建家居家裝素材庫,集成戶型庫、全品類產品庫、設計方案庫、生產工藝模型,打造基于戶型和風格的AI設計能力,快速生成算量和報價;同時研發了門店設計師中心和項目中心,包括設計師管理能力和項目經理管理能力。實現了場景全生命周期管理,同時為水,空氣,廚房等產業提供商機管理工具,從而實現了以場景貫穿的B端C端全流程系統。