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。
推薦閱讀:
- Android自定義評分控件的完整實例
- Android新建水平節點進度條示例
- Android自定義View實現圓弧進度的效果
- Android開發Kotlin實現圓弧計步器示例詳解
- Android本地驗證碼的生成代碼