Android繪制雙折線圖的方法

本文實例為大傢分享瞭Android繪制雙折線圖的具體代碼,供大傢參考,具體內容如下

自定義View實現雙折線圖,可點擊,點擊後帶標簽描述,暫未實現拖動的功能,實現效果如下:

代碼如下:

首先,自定義佈局屬性:

<declare-styleable name="LineChart">
    <!--type2.LineChart(雙折線圖)-->
    <attr name="maxYValue" format="integer" />
    <attr name="yLabelCount" format="integer" />
    <attr name="xLabelTextSize" format="dimension" />
    <attr name="xLabelTextColor" format="color" />
    <attr name="xLabelTextMarginTop" format="dimension" />
    <attr name="showYLabelText" format="boolean" />
    <attr name="yLabelTextSize" format="dimension" />
    <attr name="yLabelTextColor" format="color" />
    <attr name="yLabelTextMarginLeft" format="dimension" />
    <attr name="axisWidth" format="dimension" />
    <attr name="axisColor" format="color" />
    <attr name="showScale" format="boolean" />
    <attr name="scaleLength" format="dimension" />
    <attr name="showGrid" format="boolean" />
    <attr name="gridWidth" format="dimension" />
    <attr name="gridDashInterval" format="dimension" />
    <attr name="gridDashLength" format="dimension" />
    <attr name="gridColor" format="color" />
    <attr name="lineWidth" format="dimension" />
    <attr name="lineColor1" format="color" />
    <attr name="lineColor2" format="color" />
    <attr name="labelWidth" format="dimension" />
    <attr name="labelHeight" format="dimension" />
    <attr name="labelBackgroundColor" format="color" />
    <attr name="labelRadius" format="dimension" />
    <attr name="labelTextSize" format="dimension" />
    <attr name="labelTextColor" format="color" />
    <attr name="labelArrowWidth" format="dimension" />
    <attr name="labelArrowHeight" format="dimension" />
    <attr name="labelArrowOffset" format="dimension" />
    <attr name="labelArrowMargin" format="dimension" />
    <attr name="clickAble" format="boolean" />
    <attr name="leftMargin" format="dimension" />
    <attr name="topMargin" format="dimension" />
    <attr name="rightMargin" format="dimension" />
    <attr name="bottomMargin" format="dimension" />
</declare-styleable>

LineChart的實現如下:

class LineChart @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
 
    companion object {
        private val DEFAULT_MAX_YVALUE = 5000
        private val DEFAULT_YLABEL_COUNT = 4
        private val DEFAULT_XLABEL_TEXT_SIZE = SizeUtil.sp2px(10f)
        private val DEFAULT_XLABEL_TEXT_COLOR = Color.parseColor("#999999")
        private val DEFAULT_XLABEL_TEXT_MARGIN_TOP = SizeUtil.dp2px(10f)
        private val DEFAULT_SHOW_YLABEL_TEXT = false
        private val DEFAULT_YLABEL_TEXT_SIZE = SizeUtil.sp2px(11f)
        private val DEFAULT_YLABEL_TEXT_COLOR = Color.BLACK
        private val DEFAULT_YLABEL_TEXT_MARGIN_LEFT = SizeUtil.dp2px(15f)
        private val DEFAULT_AXIS_WIDTH = SizeUtil.dp2px(0.5f)
        private val DEFAULT_AXIS_COLOR = Color.parseColor("#F1F1F1")
        private val DEFAULT_SHOW_SCALE = true
        private val DEFAULT_SCALE_LENGTH = SizeUtil.dp2px(4f)
        private val DEFAULT_SHOW_GRID = true
        private val DEFAULT_GRID_WIDTH = SizeUtil.dp2px(0.5f)
        private val DEFAULT_GRID_DASH_INTERVAL = SizeUtil.dp2px(1f)
        private val DEFAULT_GRID_DASH_LENGTH = SizeUtil.dp2px(2f)
        private val DEFAULT_GRID_COLOR = Color.parseColor("#F1F1F1")
        private val DEFAULT_LINE_WIDTH = SizeUtil.dp2px(1.5f)
        private val DEFAULT_LINE_COLOR1 = Color.parseColor("#60BF56")
        private val DEFAULT_LINE_COLOR2 = Color.parseColor("#108EE9")
        private val DEFAULT_LABEL_WIDTH = SizeUtil.dp2px(135f)
        private val DEFAULT_LABEL_HEIGHT = SizeUtil.dp2px(78f)
        private val DEFAULT_LABEL_BACKGROUND_COLOR = Color.WHITE
        private val DEFAULT_LABEL_RADIUS = SizeUtil.dp2px(3f)
        private val DEFAULT_LABEL_TEXT_SIZE = SizeUtil.sp2px(11f)
        private val DEFAULT_LABEL_TEXT_COLOR = Color.parseColor("#333333")
        private val DEFAULT_LABEL_ARROW_WIDTH = SizeUtil.dp2px(8f)
        private val DEFAULT_LABEL_ARROW_HEIGHT = SizeUtil.dp2px(2f)
        private val DEFAULT_LABEL_ARROW_OFFSET = SizeUtil.dp2px(31f)
        private val DEFAULT_LABEL_ARROW_MARGIN = SizeUtil.dp2px(14.5f)
        private val DEFAULT_CLICKABLE = true
        private val DEFAULT_LEFT_MARGIN = SizeUtil.dp2px(15f)
        private val DEFAULT_TOP_MARGIN = SizeUtil.dp2px(118f)
        private val DEFAULT_RIGHT_MARGIN = SizeUtil.dp2px(15f)
        private val DEFAULT_BOTTOM_MARGIN = SizeUtil.dp2px(70f)
    }
 
    //Y軸最大值
    var maxYValue: Int = DEFAULT_MAX_YVALUE
    //Y軸上的刻度值個數
    var yLabelCount: Int = DEFAULT_YLABEL_COUNT
    //X軸刻度值文本字體大小
    var xLabelTextSize: Float = DEFAULT_XLABEL_TEXT_SIZE
    //X軸刻度值文本字體顏色
    var xLabelTextColor: Int = DEFAULT_XLABEL_TEXT_COLOR
    //X軸刻度值文本到X軸的上邊距
    var xLabelTextMarginTop: Float = DEFAULT_XLABEL_TEXT_MARGIN_TOP
    //是否顯示Y軸刻度值文本
    var showYLabelText: Boolean = DEFAULT_SHOW_YLABEL_TEXT
    //Y軸刻度值文本字體大小
    var yLabelTextSize: Float = DEFAULT_YLABEL_TEXT_SIZE
    //Y軸刻度值文本字體顏色
    var yLabelTextColor: Int = DEFAULT_YLABEL_TEXT_COLOR
    //Y軸刻度值文本到屏幕左側的左邊距
    var yLabelTextMarginLeft: Float = DEFAULT_YLABEL_TEXT_MARGIN_LEFT
    //X軸寬度
    var axisWidth: Float = DEFAULT_AXIS_WIDTH
    //X軸顏色
    var axisColor: Int = DEFAULT_AXIS_COLOR
    //是否顯示軸線上的小刻度線,默認顯示
    var showScale: Boolean = DEFAULT_SHOW_SCALE
    //X軸上的小刻度線長度
    var scaleLength: Float = DEFAULT_SCALE_LENGTH
    //是否顯示網格,默認顯示
    var showGrid: Boolean = DEFAULT_SHOW_GRID
    //網格線寬度
    var gridWidth: Float = DEFAULT_GRID_WIDTH
    //網格線組成虛線的線段之間的間隔
    var gridDashInterval: Float = DEFAULT_GRID_DASH_INTERVAL
    //網格線組成虛線的線段長度
    var gridDashLength: Float = DEFAULT_GRID_DASH_LENGTH
    //網格線顏色
    var gridColor: Int = DEFAULT_GRID_COLOR
    //折線寬度
    var lineWidth: Float = DEFAULT_LINE_WIDTH
    //折線一顏色
    var lineColor1: Int = DEFAULT_LINE_COLOR1
    //折線二顏色
    var lineColor2: Int = DEFAULT_LINE_COLOR2
    //標簽的矩形寬度
    var labelWidth: Float = DEFAULT_LABEL_WIDTH
    //標簽的矩形高度
    var labelHeight: Float = DEFAULT_LABEL_HEIGHT
    //標簽背景顏色
    var labelBackgroundColor = DEFAULT_LABEL_BACKGROUND_COLOR
    //標簽的矩形圓角
    var labelRadius: Float = DEFAULT_LABEL_RADIUS
    //標簽內文本字體大小
    var labelTextSize: Float = DEFAULT_LABEL_TEXT_SIZE
    //標簽內文本字體顏色
    var labelTextColor: Int = DEFAULT_LABEL_TEXT_COLOR
    //標簽的箭頭寬度
    var labelArrowWidth: Float = DEFAULT_LABEL_ARROW_WIDTH
    //標簽的箭頭高度
    var labelArrowHeight: Float = DEFAULT_LABEL_ARROW_HEIGHT
    //標簽的箭頭到標簽左側或右側的偏移量
    var labelArrowOffset: Float = DEFAULT_LABEL_ARROW_OFFSET
    //標簽的箭頭到坐標軸最上方的下邊距
    var labelArrowMargin: Float = DEFAULT_LABEL_ARROW_MARGIN
    //是否可點擊
    var clickAble: Boolean = DEFAULT_CLICKABLE
    //坐標軸到View左側的邊距,多出來的空間可以用來繪制Y軸刻度文本
    var leftMargin: Float = DEFAULT_LEFT_MARGIN
    //坐標軸到View頂部的邊距,多出來的空間可以用來繪制標簽信息
    var topMargin: Float = DEFAULT_TOP_MARGIN
    //坐標軸到View右側的邊距
    var rightMargin: Float = DEFAULT_RIGHT_MARGIN
    //坐標軸到View底部的邊距,多出來的空間可以用來繪制X軸刻度文本
    var bottomMargin: Float = DEFAULT_BOTTOM_MARGIN
 
    private var mCurrentDrawIndex = 0
 
    private lateinit var mAxisPaint: Paint     //繪制軸線和軸線上的小刻度線
    private lateinit var mGridPaint: Paint     //繪制網格線
    private lateinit var mLinePaint: Paint     //繪制折線
    private lateinit var mLabelPaint: Paint    //繪制最上方標簽
    private lateinit var mLabelBgPaint: Paint  //繪制標簽背景,帶陰影效果
    private lateinit var mTextPaint: Paint     //繪制文本
    private lateinit var mLabelRectF: RectF    //最上方的標簽對應的矩形
 
    private var mWidth: Int = 0
    private var mHeight: Int = 0
    private var mXPoint: Float = 0f   //原點的X坐標
    private var mYPoint: Float = 0f   //原點的Y坐標
    private var mXScale: Float = 0f   //X軸刻度長度
    private var mYScale: Float = 0f   //Y軸刻度長度
    private var mXLength: Float = 0f  //X軸長度
    private var mYLength: Float = 0f  //Y軸長度
    private var mClickIndex: Int = 0  //點擊時的下標
 
    private var mDataList1: MutableList<Float> = mutableListOf()     //折線一(交易收益)對應數據
    private var mDataList2: MutableList<Float> = mutableListOf()     //折線二(返現收益)對應數據
    //記錄每個數據點的X、Y坐標
    private var mDataPointList1: MutableList<PointF> = mutableListOf()
    private var mDataPointList2: MutableList<PointF> = mutableListOf()
    private var mXLabelList: MutableList<String> = mutableListOf()  //X軸刻度值
    private var mYLabelList: MutableList<String> = mutableListOf()  //Y軸刻度值
 
    init {
        setLayerType(LAYER_TYPE_SOFTWARE, null)  //關閉硬件加速,解決在部分手機無法實現虛線效果
        attrs?.let {
            parseAttribute(getContext(), it)
        }
        initPaint()
        setYLable()
    }
 
    //初始化Y軸刻度值
    private fun setYLable() {
        mYLabelList.clear()
        val increment = maxYValue / yLabelCount.toFloat()
        for (i in 0..yLabelCount) {
            var text = ""
            if (i == 0) {
                text = "0"
            } else {
                val value = (increment * i * 100).toInt() / 100f
                if (value == value.toInt().toFloat()) {
                    text = value.toInt().toString()
                } else {
                    text = value.toString()
                }
            }
            mYLabelList.add(text)
        }
    }
 
    //獲取佈局屬性並設置屬性默認值
    private fun parseAttribute(context: Context, attrs: AttributeSet) {
        val ta = context.obtainStyledAttributes(attrs, R.styleable.LineChart)
        maxYValue = ta.getInt(R.styleable.LineChart_maxYValue, DEFAULT_MAX_YVALUE)
        yLabelCount = ta.getInt(R.styleable.LineChart_yLabelCount, DEFAULT_YLABEL_COUNT)
        xLabelTextSize = ta.getDimension(R.styleable.LineChart_xLabelTextSize, DEFAULT_XLABEL_TEXT_SIZE)
        xLabelTextColor = ta.getColor(R.styleable.LineChart_xLabelTextColor, DEFAULT_XLABEL_TEXT_COLOR)
        xLabelTextMarginTop = ta.getDimension(R.styleable.LineChart_xLabelTextMarginTop, DEFAULT_XLABEL_TEXT_MARGIN_TOP)
        showYLabelText = ta.getBoolean(R.styleable.LineChart_showYLabelText, DEFAULT_SHOW_YLABEL_TEXT)
        yLabelTextSize = ta.getDimension(R.styleable.LineChart_yLabelTextSize, DEFAULT_YLABEL_TEXT_SIZE)
        yLabelTextColor = ta.getColor(R.styleable.LineChart_yLabelTextColor, DEFAULT_YLABEL_TEXT_COLOR)
        yLabelTextMarginLeft = ta.getDimension(R.styleable.LineChart_yLabelTextMarginLeft, DEFAULT_YLABEL_TEXT_MARGIN_LEFT)
        axisWidth = ta.getDimension(R.styleable.LineChart_axisWidth, DEFAULT_AXIS_WIDTH)
        axisColor = ta.getColor(R.styleable.LineChart_axisColor, DEFAULT_AXIS_COLOR)
        showScale = ta.getBoolean(R.styleable.LineChart_showScale, DEFAULT_SHOW_SCALE)
        scaleLength = ta.getDimension(R.styleable.LineChart_scaleLength, DEFAULT_SCALE_LENGTH)
        showGrid = ta.getBoolean(R.styleable.LineChart_showGrid, DEFAULT_SHOW_GRID)
        gridWidth = ta.getDimension(R.styleable.LineChart_gridWidth, DEFAULT_GRID_WIDTH)
        gridDashInterval = ta.getDimension(R.styleable.LineChart_gridDashInterval, DEFAULT_GRID_DASH_INTERVAL)
        gridDashLength = ta.getDimension(R.styleable.LineChart_gridDashLength, DEFAULT_GRID_DASH_LENGTH)
        gridColor = ta.getColor(R.styleable.LineChart_gridColor, DEFAULT_GRID_COLOR)
        lineWidth = ta.getDimension(R.styleable.LineChart_lineWidth, DEFAULT_LINE_WIDTH)
        lineColor1 = ta.getColor(R.styleable.LineChart_lineColor1, DEFAULT_LINE_COLOR1)
        lineColor2 = ta.getColor(R.styleable.LineChart_lineColor2, DEFAULT_LINE_COLOR2)
        labelWidth = ta.getDimension(R.styleable.LineChart_labelWidth, DEFAULT_LABEL_WIDTH)
        labelHeight = ta.getDimension(R.styleable.LineChart_labelHeight, DEFAULT_LABEL_HEIGHT)
        labelBackgroundColor = ta.getColor(R.styleable.LineChart_labelBackgroundColor, DEFAULT_LABEL_BACKGROUND_COLOR)
        labelRadius = ta.getDimension(R.styleable.LineChart_labelRadius, DEFAULT_LABEL_RADIUS)
        labelTextSize = ta.getDimension(R.styleable.LineChart_labelTextSize, DEFAULT_LABEL_TEXT_SIZE)
        labelTextColor = ta.getColor(R.styleable.LineChart_labelTextColor, DEFAULT_LABEL_TEXT_COLOR)
        labelArrowWidth = ta.getDimension(R.styleable.LineChart_labelArrowWidth, DEFAULT_LABEL_ARROW_WIDTH)
        labelArrowHeight = ta.getDimension(R.styleable.LineChart_labelArrowHeight, DEFAULT_LABEL_ARROW_HEIGHT)
        labelArrowOffset = ta.getDimension(R.styleable.LineChart_labelArrowMargin, DEFAULT_LABEL_ARROW_OFFSET)
        labelArrowMargin = ta.getDimension(R.styleable.LineChart_labelArrowMargin, DEFAULT_LABEL_ARROW_MARGIN)
        clickAble = ta.getBoolean(R.styleable.LineChart_clickAble, DEFAULT_CLICKABLE)
        leftMargin = ta.getDimension(R.styleable.LineChart_leftMargin, DEFAULT_LEFT_MARGIN)
        topMargin = ta.getDimension(R.styleable.LineChart_topMargin, DEFAULT_TOP_MARGIN)
        rightMargin = ta.getDimension(R.styleable.LineChart_rightMargin, DEFAULT_RIGHT_MARGIN)
        bottomMargin = ta.getDimension(R.styleable.LineChart_bottomMargin, DEFAULT_BOTTOM_MARGIN)
        ta.recycle()
    }
 
    //初始化畫筆
    private fun initPaint() {
        mAxisPaint = Paint()
        with(mAxisPaint) {
            isAntiAlias = true
            color = axisColor
            strokeWidth = axisWidth
        }
        mGridPaint = Paint()
        with(mGridPaint) {
            isAntiAlias = true
            color = gridColor
            strokeWidth = gridWidth
            setPathEffect(DashPathEffect(floatArrayOf(gridDashLength, gridDashInterval), 0f))  //設置虛線效果
        }
        mLinePaint = Paint()
        with(mLinePaint) {
            isAntiAlias = true
            strokeWidth = lineWidth
            style = Paint.Style.STROKE
        }
        mLabelPaint = Paint()
        with(mLabelPaint) {
            isAntiAlias = true
        }
        mLabelBgPaint = Paint()
        with(mLabelBgPaint) {
            isAntiAlias = true
            color = labelBackgroundColor
        }
        mTextPaint = Paint()
        with(mTextPaint) {
            isAntiAlias = true
            textAlign = Paint.Align.CENTER
        }
        mLabelRectF = RectF()
    }
 
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)
        var height = 0
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize
        } else {
            height = SizeUtil.dp2px(308f).toInt()
            if (heightMode == MeasureSpec.AT_MOST) {
                height = Math.min(height, heightSize)
            }
        }
        setMeasuredDimension(measuredWidth, height)
    }
 
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        val touchX = event?.getX() ?: 0f
        for (i in 0..mDataPointList1.size - 1) {
            val centerX = mDataPointList1[i].x
            var beginX = centerX - mXScale / 2f
            var endX = centerX + mXScale / 2f
            if (i == 0) {
                beginX = 0f
            }
            if (i == mDataPointList1.size - 1) {
                endX = mWidth.toFloat()
            }
            if (beginX < touchX && touchX < endX) {
                mClickIndex = i
                invalidate()
                break
            }
        }
        return true
    }
 
    override fun onDraw(canvas: Canvas?) {
        canvas?.let {
            initSize(width, height)    //初始化尺寸信息
            drawCoordinate(it)         //繪制坐標軸
            drawLine(it)               //繪制折線
            drawLabel(it)              //繪制點擊後的效果
            drawBottomDescription(it)  //繪制底部類型說明
        }
    }
 
    //初始化尺寸信息
    private fun initSize(width: Int, height: Int) {
        mWidth = width
        mHeight = height
        mXLength = mWidth - leftMargin - rightMargin
        mYLength = mHeight - topMargin - bottomMargin
        mXPoint = leftMargin
        mYPoint = mHeight - bottomMargin
        mXScale = mXLength / (mXLabelList.size - 1)
        mYScale = mYLength / yLabelCount
        mDataPointList1.clear()
        if (hasOnlyOneData()) {
            mDataPointList1.add(PointF(mXPoint + mXLength / 2f, calculateYPosition(mDataList1.get(0))))  //居中
        } else {
            for (i in 0..mDataList1.size - 1) {
                mDataPointList1.add(PointF(mXPoint + i * mXScale, calculateYPosition(mDataList1.get(i))))
            }
        }
        mDataPointList2.clear()
        if (hasOnlyOneData()) {
            mDataPointList2.add(PointF(mXPoint + mXLength / 2f, calculateYPosition(mDataList2.get(0))))  //居中
        } else {
            for (i in 0..mDataList2.size - 1) {
                mDataPointList2.add(PointF(mXPoint + i * mXScale, calculateYPosition(mDataList2.get(i))))
            }
        }
    }
 
    //繪制坐標軸
    private fun drawCoordinate(canvas: Canvas) {
        //繪制X軸
        canvas.drawLine(mXPoint - axisWidth / 2f, mYPoint, mXPoint + mXLength + axisWidth / 2f, mYPoint, mAxisPaint)
        with(mTextPaint) {
            textSize = xLabelTextSize
            color = xLabelTextColor
        }
        val fm = mTextPaint.getFontMetrics()
        val yOffset = mYPoint + xLabelTextMarginTop - fm.ascent
        for (i in 0..mXLabelList.size - 1) {
            //繪制X軸的刻度值文本
            if (i == 0) {  //第一個刻度值文本
                if (hasOnlyOneData()) {  //隻有一條數據時居中顯示
                    mTextPaint.textAlign = Paint.Align.CENTER
                    canvas.drawText(mXLabelList[i], mDataPointList1[i].x, yOffset, mTextPaint)
                } else {
                    mTextPaint.textAlign = Paint.Align.LEFT
                    canvas.drawText(mXLabelList[i], mXPoint, yOffset, mTextPaint)
                }
            } else if (i == mXLabelList.size - 1) {  //最後一個刻度值文本
                mTextPaint.textAlign = Paint.Align.RIGHT
                canvas.drawText(mXLabelList[i], mXPoint + mXLength, yOffset, mTextPaint)
            } else {
                mTextPaint.textAlign = Paint.Align.CENTER
                canvas.drawText(mXLabelList[i], mXPoint + i * mXScale, yOffset, mTextPaint)
            }
            //繪制X軸上的小刻度線
            if (showScale) {
                canvas.drawLine(
                    mXPoint + i * mXScale,
                    mYPoint,
                    mXPoint + i * mXScale,
                    mYPoint - scaleLength,
                    mAxisPaint
                )
            }
        }
        for (i in 0..yLabelCount - 1) {
            //繪制網格線:橫刻線
            if (showGrid) {
                mGridPaint.color = gridColor
                canvas.drawLine(
                    mXPoint,
                    mYPoint - (i + 1) * mYScale,
                    mXPoint + mXLength,
                    mYPoint - (i + 1) * mYScale,
                    mGridPaint
                )
            }
            //繪制Y軸上的刻度值
            if (showYLabelText) {
                with(mTextPaint) {
                    textSize = yLabelTextSize
                    color = yLabelTextColor
                    textAlign = Paint.Align.LEFT
                }
                if (i == 0) {
                    canvas.drawText(mYLabelList[i], yLabelTextMarginLeft, mYPoint, mTextPaint)
                }
                val yLabelFm = mTextPaint.getFontMetrics()
                val yLabelYOffset = mYPoint + (yLabelFm.descent - yLabelFm.ascent) / 2f - yLabelFm.descent - (i + 1) * mYScale
                canvas.drawText(mYLabelList[i + 1], yLabelTextMarginLeft, yLabelYOffset, mTextPaint)
            }
        }
    }
 
    //繪制折線
    private fun drawLine(canvas: Canvas) {
        if (mDataList1 == null || mDataList1.size <= 0 || mDataList2 == null || mDataList2.size <= 0) {
            return
        }
        if (hasOnlyOneData()) {  //處理隻有一條數據的情況
            //繪制第一條直線
            mLinePaint.color = lineColor1
            canvas.drawLine(mXPoint, mDataPointList1[0].y, mXPoint + mXLength, mDataPointList1[0].y, mLinePaint)
            //繪制第二條直線
            mLinePaint.color = lineColor2
            canvas.drawLine(mXPoint, mDataPointList2[0].y, mXPoint + mXLength, mDataPointList2[0].y, mLinePaint)
            return
        }
        for (i in 0..mDataPointList1.size - 2) {
            if (i <= mCurrentDrawIndex) {
                //繪制第一條折線
                //繪制折線
                mLinePaint.color = lineColor1
                canvas.drawLine(
                    mDataPointList1[i].x, mDataPointList1[i].y,
                    mDataPointList1[i + 1].x, mDataPointList1[i + 1].y, mLinePaint
                )
                //繪制折線交點
                canvas.drawCircle(mDataPointList1[i].x, mDataPointList1[i].y, lineWidth * 1.5f, mLinePaint)
                mLinePaint.color = Color.WHITE
                canvas.drawCircle(mDataPointList1[i].x, mDataPointList1[i].y, lineWidth * 0.5f, mLinePaint)
 
                //繪制第二條折線
                //繪制折線
                mLinePaint.color = lineColor2
                canvas.drawLine(
                    mDataPointList2[i].x, mDataPointList2[i].y,
                    mDataPointList2[i + 1].x, mDataPointList2[i + 1].y, mLinePaint
                )
                //繪制折線交點
                canvas.drawCircle(mDataPointList2[i].x, mDataPointList2[i].y, lineWidth * 1.5f, mLinePaint)
                mLinePaint.color = Color.WHITE
                canvas.drawCircle(mDataPointList2[i].x, mDataPointList2[i].y, lineWidth * 0.5f, mLinePaint)
 
                //繪制最後一個折線交點
                if (i == mDataPointList1.size - 2) {
                    mLinePaint.color = lineColor1
                    canvas.drawCircle(
                        mDataPointList1[mDataPointList1.size - 1].x,
                        mDataPointList1[mDataPointList1.size - 1].y,
                        lineWidth * 1.5f,
                        mLinePaint
                    )
                    mLinePaint.color = Color.WHITE
                    canvas.drawCircle(
                        mDataPointList1[mDataPointList1.size - 1].x,
                        mDataPointList1[mDataPointList1.size - 1].y,
                        lineWidth * 0.5f,
                        mLinePaint
                    )
 
                    mLinePaint.color = lineColor2
                    canvas.drawCircle(
                        mDataPointList2[mDataPointList2.size - 1].x,
                        mDataPointList2[mDataPointList2.size - 1].y,
                        lineWidth * 1.5f,
                        mLinePaint
                    )
                    mLinePaint.color = Color.WHITE
                    canvas.drawCircle(
                        mDataPointList2[mDataPointList2.size - 1].x,
                        mDataPointList2[mDataPointList2.size - 1].y,
                        lineWidth * 0.5f,
                        mLinePaint
                    )
                }
            }
        }
    }
 
    //計算數值對應的Y坐標
    private fun calculateYPosition(data: Float): Float = mYPoint - data / maxYValue * mYLength
 
    //繪制點擊後的詳情展示
    private fun drawLabel(canvas: Canvas) {
        if (clickAble && mDataList1.size > 0) {
            //繪制點擊後的豎刻線
            mLabelPaint.color = Color.parseColor("#EBEBEB")
            mLabelPaint.strokeWidth = DEFAULT_GRID_WIDTH * 2
            canvas.drawLine(
                mDataPointList1[mClickIndex].x,
                mYPoint,
                mDataPointList1[mClickIndex].x,
                topMargin - labelArrowMargin,
                mLabelPaint
            )
            //繪制點擊後的折線交點
            mLabelPaint.color = lineColor1
            canvas.drawCircle(
                mDataPointList1[mClickIndex].x,
                mDataPointList1[mClickIndex].y,
                lineWidth * 2.3f,
                mLabelPaint
            )
            mLabelPaint.color = lineColor2
            canvas.drawCircle(
                mDataPointList2[mClickIndex].x,
                mDataPointList2[mClickIndex].y,
                lineWidth * 2.3f,
                mLabelPaint
            )
            //繪制最上方標簽信息
            with(mLabelRectF) {
                bottom = topMargin - labelArrowMargin - labelArrowHeight
                top = bottom - labelHeight;
                left = mDataPointList1[mClickIndex].x - labelArrowWidth / 2f - labelArrowOffset
                right = left + labelWidth
                //處理點擊第一項出現標簽偏離整個折線圖現象
                if (left < 0) {
                    left = SizeUtil.dp2px(5f)
                    right = left + labelWidth
                }
                //處理點擊最後一項出現標簽偏離整個折線圖現象
                if (right > mWidth) {
                    right = mWidth.toFloat() - SizeUtil.dp2px(5f)
                    left = right - labelWidth
                }
            }
            //繪制圓角矩形
            mLabelBgPaint.setShadowLayer(
                SizeUtil.dp2px(12f),  //陰影效果
                SizeUtil.dp2px(2.5f),
                SizeUtil.dp2px(1.5f),
                Color.parseColor("#C7C7C7")
            )
            canvas.drawRoundRect(mLabelRectF, labelRadius, labelRadius, mLabelBgPaint)
            //繪制箭頭
            val arrowPath = Path()
            with(arrowPath) {
                moveTo(mDataPointList1[mClickIndex].x, topMargin - labelArrowMargin)
                val baseY = topMargin - labelArrowMargin - labelArrowHeight - SizeUtil.dp2px(1f)
                lineTo(mDataPointList1[mClickIndex].x - labelArrowWidth / 2f, baseY)
                lineTo(mDataPointList1[mClickIndex].x + labelArrowWidth / 2f, baseY)
                close()
            }
            mLabelPaint.color = labelBackgroundColor
            canvas.drawPath(arrowPath, mLabelPaint)
            mLabelPaint.color = Color.parseColor("#F1F1F1")
            mLabelPaint.strokeWidth = gridWidth
            canvas.drawLine(
                mLabelRectF.left + SizeUtil.dp2px(10f),
                mLabelRectF.bottom - SizeUtil.dp2px(52f),
                mLabelRectF.right - SizeUtil.dp2px(10f),
                mLabelRectF.bottom - SizeUtil.dp2px(52f), mLabelPaint
            )
            //繪制文字
            with(mTextPaint) {
                color = labelTextColor
                textSize = labelTextSize
                textAlign = Paint.Align.LEFT
            }
            canvas.drawText(
                mXLabelList[mClickIndex],
                mLabelRectF.left + SizeUtil.dp2px(9.5f),
                mLabelRectF.bottom - SizeUtil.dp2px(61f), mTextPaint
            )
            canvas.drawText(
                "交易收益  ¥${mDataList1[mClickIndex]}",
                mLabelRectF.left + SizeUtil.dp2px(19.5f),
                mLabelRectF.bottom - SizeUtil.dp2px(32.5f), mTextPaint
            )
            canvas.drawText(
                "返現收益  ¥${mDataList2[mClickIndex]}",
                mLabelRectF.left + SizeUtil.dp2px(19.5f),
                mLabelRectF.bottom - SizeUtil.dp2px(12.5f), mTextPaint
            )
            mTextPaint.color = lineColor1
            canvas.drawCircle(
                mLabelRectF.left + SizeUtil.dp2px(12.5f),
                mLabelRectF.bottom - SizeUtil.dp2px(36f),
                SizeUtil.dp2px(2.5f), mTextPaint
            )
            mTextPaint.color = lineColor2
            canvas.drawCircle(
                mLabelRectF.left + SizeUtil.dp2px(12.5f),
                mLabelRectF.bottom - SizeUtil.dp2px(16f),
                SizeUtil.dp2px(2.5f), mTextPaint
            )
        }
    }
 
    //繪制底部類型說明
    private fun drawBottomDescription(canvas: Canvas) {
        if (mDataList1 == null || mDataList1.size == 0 || mDataList2 == null || mDataList2.size == 0) {
            return
        }
        mTextPaint.color = lineColor1
        val centerX1 = mWidth / 2f - SizeUtil.dp2px(75.5f)
        canvas.drawCircle(
            centerX1, mHeight - SizeUtil.dp2px(20f),
            SizeUtil.dp2px(3.5f), mTextPaint
        )
        mTextPaint.color = lineColor2
        val centerX2 = mWidth / 2f + SizeUtil.dp2px(16f)
        canvas.drawCircle(
            centerX2, mHeight - SizeUtil.dp2px(20f),
            SizeUtil.dp2px(3.5f), mTextPaint
        )
        mTextPaint.color = Color.WHITE
        canvas.drawCircle(
            centerX1, mHeight - SizeUtil.dp2px(20f),
            SizeUtil.dp2px(2.2f), mTextPaint
        )
        canvas.drawCircle(
            centerX2, mHeight - SizeUtil.dp2px(20f),
            SizeUtil.dp2px(2.2f), mTextPaint
        )
        with(mTextPaint) {
            color = labelTextColor
            textSize = SizeUtil.sp2px(12f)
        }
        canvas.drawText(
            "交易收益", centerX1 + SizeUtil.dp2px(8f),
            mHeight - SizeUtil.dp2px(15.5f), mTextPaint
        )
        canvas.drawText(
            "返現收益", centerX2 + SizeUtil.dp2px(8f),
            mHeight - SizeUtil.dp2px(15.5f), mTextPaint
        )
    }
 
    //格式化標簽內的數值文本
    private fun formatValue(value: Float): String {
        val scale = maxYValue / yLabelCount.toFloat()
        if (scale < 10 && (value != value.toInt().toFloat()) && (value >= 0.01f)) {
            return "${(value * 100).toInt().toFloat() / 100}"  //保留2位小數,但不四舍五入
        }
        return "${value.toInt()}"
    }
 
    //是否隻有一條數據
    private fun hasOnlyOneData(): Boolean = mDataList1.size == 1 && mDataList2.size == 1 && mXLabelList.size == 1
 
    //設置數據,startAnim:是否開啟動畫,動畫默認一條一條折線的畫
    //list1和list2的數據個數要相同,dateList的數據個數大於等於list1和list2的數據個數
    fun drawData(list1: MutableList<Float>, list2: MutableList<Float>, dateList: MutableList<String>, startAnim: Boolean = false) {
        if (list1.size != list2.size) {
            throw RuntimeException("the size of list1 must be equal to the size of list2")
        }
        if (dateList.size < list1.size) {
            throw RuntimeException("the size of dateList can not less than the size of list1")
        }
        var maxValue = 0f
        for (item in list1) {
            if (maxValue <= item) {
                maxValue = item
            }
        }
        for (item in list2) {
            if (maxValue <= item) {
                maxValue = item
            }
        }
        mDataList1 = list1
        mDataList2 = list2
        mXLabelList = dateList
        maxYValue = calculateMaxValue(maxValue)
        mClickIndex = 0
        setYLable()  //重新設置Y軸刻度值
        if (startAnim) {
            val animator = ValueAnimator.ofInt(0, mDataList1.size - 2)
            animator.setDuration(1500)
            animator.addUpdateListener {
                mCurrentDrawIndex = it.getAnimatedValue() as Int
                invalidate()
            }
            animator.interpolator = LinearInterpolator()
            animator.start()
        } else {
            mCurrentDrawIndex = mDataList1.size - 2
            invalidate()
        }
    }
 
    //計算Y軸最大值和單位,計算規則:最高位數加1取整
    private fun calculateMaxValue(value: Float): Int {
        val valueStr = value.toLong().toString()
        val length = valueStr.length  //整數的位數
        val unit = Math.pow(10.0, (length - 1).toDouble()).toInt()
        if (value == 0f) {
            return DEFAULT_MAX_YVALUE  //如果最大值是0,即所有數據都是0,取默認的最大值
        } else if (value % unit == 0f) {
            return value.toInt()
        } else {
            return ((value / unit).toInt() + 1) * unit
        }
    }
 
}

使用舉例:

private fun createType2Data(count: Int, isDateMore: Boolean = false, startAnim: Boolean = false, showYLabelText: Boolean = false) {
        val list1: MutableList<Float> = mutableListOf()
        val list2: MutableList<Float> = mutableListOf()
        val dateList: MutableList<String> = mutableListOf()
        for (i in 0..count) {
            list1.add(Random.nextDouble(80.0).toFloat())
            list2.add(Random.nextDouble(80.0).toFloat())
            dateList.add(DateUtil.getDistanceDateByDay(i - count, DateUtil.M_D))
        }
        if (isDateMore) {
            dateList.add(DateUtil.getDistanceDateByDay(1, DateUtil.M_D))
        }
        if (showYLabelText) {
            binding.type2Lc.leftMargin = SizeUtil.dp2px(40f)
        } else {
            binding.type2Lc.leftMargin = SizeUtil.dp2px(15f)
        }
        binding.type2Lc.showYLabelText = showYLabelText
        binding.type2Lc.drawData(list1, list2, dateList, startAnim)
 }

以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: