Android 身高體重曲線的實現

Merbng發表於2018-09-12

場景

產品需求,需要增加一個身高體重曲線的模組,設計圖如下

設計圖.png
體重曲線也是這樣。

準備

先來一張實現的粗略效果圖

實現效果.gif

曲線圖,折線圖等,大多使用流行強大的MPandroidChart庫,這裡也是。 在網上找了一些類似的效果,都不是很符合設計圖,

效果1.gif

效果2.gif
效果3.png

實現

直接附上程式碼,註釋都很清楚。主要呼叫庫的方法



        mLineChart = binding.lineChart
        mLineChart.setDrawBorders(true)

        var xAxis = mLineChart.xAxis
        /*設定X軸的位置(預設在上方)*/
        xAxis.position = XAxis.XAxisPosition.BOTTOM
        /*設定X軸座標之間的最小間隔*/
        xAxis.granularity = 1f
        xAxis.mAxisMaximum = 72f
        /*設定X軸值為字串*/
        xAxis.setValueFormatter(object : IAxisValueFormatter {
            override fun getFormattedValue(value: Float, axis: AxisBase?): String {
                return PinyinUtils.getFormatXLabel(value.toInt())
            }
        })
        /* 右側Y軸不顯示*/
        var axisRight = mLineChart.axisRight
        axisRight.isEnabled = false
        /*隱藏圖例*/
        mLineChart.legend.isEnabled = false
        /*隱藏描述*/
        var description = Description()
        description.isEnabled = false
        mLineChart.description = description
        /*X,Y軸同時縮放,false則X,Y軸單獨縮放,預設false*/
//        mLineChart.setPinchZoom(true);
        mLineChart.setScaleEnabled(false)
        // 重置所有縮放與拖動,使圖示完全符合其邊界
//        mLineChart.fitScreen();
        /* y軸是否自動縮放;當縮放時,y軸的顯示會自動根據x軸範圍內資料的最大最小值而調整。財務報表比較有用,預設false*/
        mLineChart.setAutoScaleMinMaxEnabled(true);

        mLineChart.setExtraLeftOffset(10f);   // 這個與上面的區別是不會忽略其自己計算的偏移。

//        mLineChart.setDoubleTapToZoomEnabled(false);//雙擊螢幕縮放
//        mLineChart.setScaleEnabled(false);
//        mLineChart.setScaleXEnabled(true);
//        mLineChart.setScaleYEnabled(false);


/*        var axisLeft = mLineChart.getAxisLeft()
//        axisLeft.axisMinimum = 45f
        //如果設定為true那麼下面方法設定最小間隔生效,預設為false
        axisLeft.setGranularityEnabled(true);
        //設定Y軸的值之間的最小間隔。這可以用來避免價值複製當放大到一個地步,小數設定軸不再數允許區分兩軸線之間的值。
        axisLeft.setGranularity(10f);
        binding.viewModel?.requestGrowthData("1")*/
        initHeightWidth(true)

//初始化
    fun initHeightWidth(isHeight: Boolean) {
        binding.tvTopTip.text = if (isHeight) "身高(cm)" else "體重(kg)"
        mLineChart.setExtraLeftOffset(if (isHeight) 10f else 5f);   // 這個與上面的區別是不會忽略其自己計算的偏移。

        var axisLeft = mLineChart.getAxisLeft()
        //如果設定為true那麼下面方法設定最小間隔生效,預設為false
        axisLeft.setGranularityEnabled(true)
        axisLeft.setGranularity(if (isHeight) 10f else 1f)
        axisLeft.axisMinimum = if (isHeight) 45f else 1f

        var xAxis = mLineChart.xAxis
        /*設定X軸的位置(預設在上方)*/
        xAxis.position = XAxis.XAxisPosition.BOTTOM
        /*設定X軸座標之間的最小間隔*/
        xAxis.granularity = 1f
        xAxis.mAxisMaximum = 72f
        /*設定X軸值為字串*/
        xAxis.setValueFormatter(object : IAxisValueFormatter {
            override fun getFormattedValue(value: Float, axis: AxisBase?): String {
                return PinyinUtils.getFormatXLabel(value.toInt())
            }
        })



        binding.viewModel?.requestGrowthData(if (isHeight) "1" else "0")

    }
//設定資料
 fun setData(data: GrowthBean?) {
        xList.clear()
        //一個LineDataSet就是一條線
        var entriesMax = mutableListOf<Entry>()
        var entriesMin = mutableListOf<Entry>()
        var entriesUser = mutableListOf<Entry>()
        data?.max?.forEach {
            entriesMax.add(Entry(it.x, it.y))
        }
        data?.min?.forEach {
            xList.add("${it.x}")
            entriesMin.add(Entry(it.x, it.y))
        }
        data?.user?.forEach {
            entriesUser.add(Entry(it.x, it.y))
        }

        var set97 = LineDataSet(entriesMax, "97%")
        set97.setColor(Color.parseColor("#FF7449"));
        set97.setLineWidth(2f);
        set97.setFillAlpha(65);
        set97.setHighLightColor(Color.rgb(244, 117, 117));
        set97.setDrawCircleHole(false);
        set97.setDrawCircles(false)
        set97.setDrawValues(false)
        set97.setDrawFilled(true)
        set97.setFillColor(Color.parseColor("#FFC9C3"));
        set97.label = "97%"
        set97.setDrawHighlightIndicators(false)


        var set3 = LineDataSet(entriesMin, "3%")
        set3.setColor(Color.parseColor("#FF7449"))
        set3.setLineWidth(2f)
        set3.setDrawCircleHole(false)
        set3.setDrawCircles(false)
        set3.setDrawValues(false)
        set3.label = "3%"
        set3.setDrawHighlightIndicators(false)

        Collections.sort(entriesUser, EntryXComparator())

        var setUser = LineDataSet(entriesUser, "寶寶")
        setUser.setColor(Color.parseColor("#FD5A7B"))
        setUser.setLineWidth(2f)
        setUser.setDrawCircleHole(false)
        setUser.setDrawCircles(true)
        setUser.setDrawValues(true)
        setUser.setCircleRadius(5f)
        setUser.setCircleColor(Color.parseColor("#FD5A7B"))
        setUser.setDrawHighlightIndicators(false)
        var lineData: LineData
        if (entriesUser.size > 0) {
            lineData = LineData(set97, set3, setUser)
        } else {
            lineData = LineData(set97, set3)
        }
        //設定資料
        mLineChart.setData(lineData)

        //設定一頁最大顯示個數為6,超出部分就滑動
        val ratio = xList.size.toFloat() / 6.toFloat()
        //顯示的時候是按照多大的比率縮放顯示,1f表示不放大縮小
        mLineChart.zoom(ratio, 1f, 0f, 1f)

        mLineChart.isScaleYEnabled = false
        mLineChart.setNoDataText("暫無資料")
        //可以設定一條警戒線,如下:
        val ll = LimitLine(data!!.monthAge.toFloat(), "今日")
        ll.lineColor = Color.parseColor("#FF7449")
        ll.lineWidth = 1f
        ll.enableDashedLine(10f, 10f, 0f);
        ll.textColor = Color.parseColor("#FF5A7D")
        ll.textSize = 12f
        ll.setLabelPosition(LimitLine.LimitLabelPosition.RIGHT_BOTTOM)
        mLineChart.xAxis.addLimitLine(ll)
        //移到某個位置
        mLineChart.moveViewTo(data!!.monthAge.toFloat() - 3, if (isHeight) 160f else 80f, YAxis.AxisDependency.RIGHT)
        /*渲染區間背景*/
        set97.setFillFormatter(MyFillFormatter(set3))
        mLineChart.renderer = MyLineLegendRenderer(mLineChart, mLineChart.animator, mLineChart.viewPortHandler)

    }
複製程式碼

還有一個效果沒有實現,就是滑動懸浮的線上的數。

重點

渲染區間背景 是自定義的FillFormatter

public class MyFillFormatter implements IFillFormatter {
    private ILineDataSet boundaryDataSet;

    public MyFillFormatter() {
        this(null);
    }
    //Pass the dataset of other line in the Constructor
    public MyFillFormatter(ILineDataSet boundaryDataSet) {
        this.boundaryDataSet = boundaryDataSet;
    }

    @Override
    public float getFillLinePosition(ILineDataSet dataSet, LineDataProvider dataProvider) {
        return 0;
    }

    //Define a new method which is used in the LineChartRenderer
    public List<Entry> getFillLineBoundary() {
        if(boundaryDataSet != null) {
            return ((LineDataSet) boundaryDataSet).getValues();
        }
        return null;
    }}
複製程式碼

呼叫:

 /*渲染區間背景*/
        set97.setFillFormatter(MyFillFormatter(set3))
        mLineChart.renderer = MyLineLegendRenderer(mLineChart, mLineChart.animator, mLineChart.viewPortHandler)
複製程式碼

這裡是在Stack Overflow裡看到的 參考連結1 參考連結2

#最後 附上參考的文章

相關文章