Android自定义折线图控件的完整步骤

目录
  • 前言
  • 概述
  • 原点
    • 计算Y轴宽度
    • 计算X轴高度
  • X轴
    • 绘制轴线
    • X轴刻度间隔
    • 网格线、文本
  • Y轴
    • 计算Y轴分布
    • 刻度间隔、网格线、文本
  • 折线
  • 代码
  • 总结

前言

日前,有一个“折现图”的需求,如下图所示:

概述

如何自定义折线图?首先将折线图的绘制部分拆分成三部分:

  • 原点
  • X轴
  • Y轴
  • 折线

原点

第一步,需要定义出“折线图”原点的位置,由图得:

可以发现,原点的位置由X轴、Y轴所占空间决定:

OriginX:Y轴宽度
OriginY:View高度 - X轴高度

计算Y轴宽度

思路:遍历Y轴的绘制文字,用画笔测量其最大宽度,在加上其左右Margin间距即Y轴宽度

Y轴宽度 = Y轴MarginLeft + Y轴最大文字宽度 + Y轴MariginRight

计算X轴高度

思路:获取X轴画笔FontMetrics,根据其top、bottom计算出X轴文字高度,在加上其上下Margin间距即X轴高度

val fontMetrics = xAxisTextPaint.fontMetrics
val lineHeight = fontMetrics.bottom - fontMetrics.top
xAxisHeight = lineHeight + xAxisOptions.textMarginTop + xAxisOptions.textMarginBottom

X轴

第二步,根据原点位置,绘制X轴轴线、网格线、文本

绘制轴线

绘制轴线比较简单,沿原点向控件右侧画一条直线即可

if (xAxisOptions.isEnableLine) {
    xAxisLinePaint.strokeWidth = xAxisOptions.lineWidth
    xAxisLinePaint.color = xAxisOptions.lineColor
    xAxisLinePaint.pathEffect = xAxisOptions.linePathEffect
    canvas.drawLine(originX, originY, width.toFloat(), originY, xAxisLinePaint)
}

X轴刻度间隔

在绘制网格线、文本之前需要先计算X轴的刻度间隔:

这里处理的方式比较随意,直接将X轴等分7份即可(因为需要显示近7天的数据)

xGap = (width - originX) / 7

网格线、文本

网格线:只需要根据X轴的刻度,沿Y轴方向依次向控件顶部,画直线即可

文本:文本需要通过画笔,提前测量出待绘制文本的区域,然后计算出居中位置绘制即可

xAxisTexts.forEachIndexed { index, text ->
    val pointX = originX + index * xGap
    //刻度线
    if (xAxisOptions.isEnableRuler) {
        xAxisLinePaint.strokeWidth = xAxisOptions.rulerWidth
        xAxisLinePaint.color = xAxisOptions.rulerColor
        canvas.drawLine(
            pointX, originY,
            pointX, originY - xAxisOptions.rulerHeight,
            xAxisLinePaint
        )
    }
    //网格线
    if (xAxisOptions.isEnableGrid) {
        xAxisLinePaint.strokeWidth = xAxisOptions.gridWidth
        xAxisLinePaint.color = xAxisOptions.gridColor
        xAxisLinePaint.pathEffect = xAxisOptions.gridPathEffect
        canvas.drawLine(pointX, originY, pointX, 0f, xAxisLinePaint)
    }
    //文本
    bounds.setEmpty()
    xAxisTextPaint.textSize = xAxisOptions.textSize
    xAxisTextPaint.color = xAxisOptions.textColor
    xAxisTextPaint.getTextBounds(text, 0, text.length, bounds)
    val fm = xAxisTextPaint.fontMetrics
    val fontHeight = fm.bottom - fm.top
    val fontX = originX + index * xGap + (xGap - bounds.width()) / 2f
    val fontBaseline = originY + (xAxisHeight - fontHeight) / 2f - fm.top
    canvas.drawText(text, fontX, fontBaseline, xAxisTextPaint)
}

Y轴

第三步:根据原点位置,绘制Y轴轴线、网格线、文本

计算Y轴分布

个人认为,这里是自定义折线图的一个难点,这里经过查阅资料,使用该文章中的算法:

基于魔数数组的坐标轴刻度取值算法

/**
 * 根据Y轴最大值、数量获取Y轴的标准间隔
 */
private fun getYInterval(maxY: Int): Int {
    val yIntervalCount = yAxisCount - 1
    val rawInterval = maxY / yIntervalCount.toFloat()
    val magicPower = floor(log10(rawInterval.toDouble()))
    var magic = 10.0.pow(magicPower).toFloat()
    if (magic == rawInterval) {
        magic = rawInterval
    } else {
        magic *= 10
    }
    val rawStandardInterval = rawInterval / magic
    val standardInterval = getStandardInterval(rawStandardInterval) * magic
    return standardInterval.roundToInt()
}

/**
 * 根据初始的归一化后的间隔,转化为目标的间隔
 */
private fun getStandardInterval(x: Float): Float {
    return when {
        x <= 0.1f -> 0.1f
        x <= 0.2f -> 0.2f
        x <= 0.25f -> 0.25f
        x <= 0.5f -> 0.5f
        x <= 1f -> 1f
        else -> getStandardInterval(x / 10) * 10
    }
}

刻度间隔、网格线、文本

Y轴的轴线、网格线、文本剩下的内容与X轴的处理方式几乎一致

//绘制Y轴
//轴线
if (yAxisOptions.isEnableLine) {
    yAxisLinePaint.strokeWidth = yAxisOptions.lineWidth
    yAxisLinePaint.color = yAxisOptions.lineColor
    yAxisLinePaint.pathEffect = yAxisOptions.linePathEffect
    canvas.drawLine(originX, 0f, originX, originY, yAxisLinePaint)
}
yAxisTexts.forEachIndexed { index, text ->
    //刻度线
    val pointY = originY - index * yGap
    if (yAxisOptions.isEnableRuler) {
        yAxisLinePaint.strokeWidth = yAxisOptions.rulerWidth
        yAxisLinePaint.color = yAxisOptions.rulerColor
        canvas.drawLine(
            originX,
            pointY,
            originX + yAxisOptions.rulerHeight,
            pointY,
            yAxisLinePaint
        )
    }
    //网格线
    if (yAxisOptions.isEnableGrid) {
        yAxisLinePaint.strokeWidth = yAxisOptions.gridWidth
        yAxisLinePaint.color = yAxisOptions.gridColor
        yAxisLinePaint.pathEffect = yAxisOptions.gridPathEffect
        canvas.drawLine(originX, pointY, width.toFloat(), pointY, yAxisLinePaint)
    }
    //文本
    bounds.setEmpty()
    yAxisTextPaint.textSize = yAxisOptions.textSize
    yAxisTextPaint.color = yAxisOptions.textColor
    yAxisTextPaint.getTextBounds(text, 0, text.length, bounds)
    val fm = yAxisTextPaint.fontMetrics
    val x = (yAxisWidth - bounds.width()) / 2f
    val fontHeight = fm.bottom - fm.top
    val y = originY - index * yGap - fontHeight / 2f - fm.top
    canvas.drawText(text, x, y, yAxisTextPaint)
}

折线

折线的连接,这里使用的是Path,将一个一个坐标点连接,最后将Path绘制,就形成了图中的折线图

//绘制数据
path.reset()
points.forEachIndexed { index, point ->
    val x = originX + index * xGap + xGap / 2f
    val y = originY - (point.yAxis.toFloat() / yAxisMaxValue) * (yGap * (yAxisCount - 1))
    if (index == 0) {
        path.moveTo(x, y)
    } else {
        path.lineTo(x, y)
    }
    //圆点
    circlePaint.color = dataOptions.circleColor
    canvas.drawCircle(x, y, dataOptions.circleRadius, circlePaint)
}
pathPaint.strokeWidth = dataOptions.pathWidth
pathPaint.color = dataOptions.pathColor
canvas.drawPath(path, pathPaint)

值得注意的是:坐标点X根据间隔是相对确定的,而坐标点Y则需要进行百分比换算

代码

折线图LineChart

package com.vander.pool.widget.linechart
import android.content.Context
import android.graphics.*
import android.text.TextPaint
import android.util.AttributeSet
import android.view.View
import java.text.DecimalFormat
import kotlin.math.floor
import kotlin.math.log10
import kotlin.math.pow
import kotlin.math.roundToInt
class LineChart : View {
    private var options = ChartOptions()
    /**
     * X轴相关
     */
    private val xAxisTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
    private val xAxisLinePaint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val xAxisTexts = mutableListOf<String>()
    private var xAxisHeight = 0f
    /**
     * Y轴相关
     */
    private val yAxisTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
    private val yAxisLinePaint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val yAxisTexts = mutableListOf<String>()
    private var yAxisWidth = 0f
    private val yAxisCount = 5
    private var yAxisMaxValue: Int = 0
    /**
     * 原点
     */
    private var originX = 0f
    private var originY = 0f
    private var xGap = 0f
    private var yGap = 0f
    /**
     * 数据相关
     */
    private val pathPaint = Paint(Paint.ANTI_ALIAS_FLAG).also {
        it.style = Paint.Style.STROKE
    }
    private val circlePaint = Paint(Paint.ANTI_ALIAS_FLAG).also {
        it.color = Color.parseColor("#79EBCF")
        it.style = Paint.Style.FILL
    }
    private val points = mutableListOf<ChartBean>()
    private val bounds = Rect()
    private val path = Path()
    constructor(context: Context)
            : this(context, null)
    constructor(context: Context, attrs: AttributeSet?)
            : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
            super(context, attrs, defStyleAttr)
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (points.isEmpty()) return
        val xAxisOptions = options.xAxisOptions
        val yAxisOptions = options.yAxisOptions
        val dataOptions = options.dataOptions
        //设置原点
        originX = yAxisWidth
        originY = height - xAxisHeight
        //设置X轴Y轴间隔
        xGap = (width - originX) / points.size
        //Y轴默认顶部会留出一半空间
        yGap = originY / (yAxisCount - 1 + 0.5f)
        //绘制X轴
        //轴线
        if (xAxisOptions.isEnableLine) {
            xAxisLinePaint.strokeWidth = xAxisOptions.lineWidth
            xAxisLinePaint.color = xAxisOptions.lineColor
            xAxisLinePaint.pathEffect = xAxisOptions.linePathEffect
            canvas.drawLine(originX, originY, width.toFloat(), originY, xAxisLinePaint)
        }
        xAxisTexts.forEachIndexed { index, text ->
            val pointX = originX + index * xGap
            //刻度线
            if (xAxisOptions.isEnableRuler) {
                xAxisLinePaint.strokeWidth = xAxisOptions.rulerWidth
                xAxisLinePaint.color = xAxisOptions.rulerColor
                canvas.drawLine(
                    pointX, originY,
                    pointX, originY - xAxisOptions.rulerHeight,
                    xAxisLinePaint
                )
            }
            //网格线
            if (xAxisOptions.isEnableGrid) {
                xAxisLinePaint.strokeWidth = xAxisOptions.gridWidth
                xAxisLinePaint.color = xAxisOptions.gridColor
                xAxisLinePaint.pathEffect = xAxisOptions.gridPathEffect
                canvas.drawLine(pointX, originY, pointX, 0f, xAxisLinePaint)
            }
            //文本
            bounds.setEmpty()
            xAxisTextPaint.textSize = xAxisOptions.textSize
            xAxisTextPaint.color = xAxisOptions.textColor
            xAxisTextPaint.getTextBounds(text, 0, text.length, bounds)
            val fm = xAxisTextPaint.fontMetrics
            val fontHeight = fm.bottom - fm.top
            val fontX = originX + index * xGap + (xGap - bounds.width()) / 2f
            val fontBaseline = originY + (xAxisHeight - fontHeight) / 2f - fm.top
            canvas.drawText(text, fontX, fontBaseline, xAxisTextPaint)
        }
        //绘制Y轴
        //轴线
        if (yAxisOptions.isEnableLine) {
            yAxisLinePaint.strokeWidth = yAxisOptions.lineWidth
            yAxisLinePaint.color = yAxisOptions.lineColor
            yAxisLinePaint.pathEffect = yAxisOptions.linePathEffect
            canvas.drawLine(originX, 0f, originX, originY, yAxisLinePaint)
        }
        yAxisTexts.forEachIndexed { index, text ->
            //刻度线
            val pointY = originY - index * yGap
            if (yAxisOptions.isEnableRuler) {
                yAxisLinePaint.strokeWidth = yAxisOptions.rulerWidth
                yAxisLinePaint.color = yAxisOptions.rulerColor
                canvas.drawLine(
                    originX,
                    pointY,
                    originX + yAxisOptions.rulerHeight,
                    pointY,
                    yAxisLinePaint
                )
            }
            //网格线
            if (yAxisOptions.isEnableGrid) {
                yAxisLinePaint.strokeWidth = yAxisOptions.gridWidth
                yAxisLinePaint.color = yAxisOptions.gridColor
                yAxisLinePaint.pathEffect = yAxisOptions.gridPathEffect
                canvas.drawLine(originX, pointY, width.toFloat(), pointY, yAxisLinePaint)
            }
            //文本
            bounds.setEmpty()
            yAxisTextPaint.textSize = yAxisOptions.textSize
            yAxisTextPaint.color = yAxisOptions.textColor
            yAxisTextPaint.getTextBounds(text, 0, text.length, bounds)
            val fm = yAxisTextPaint.fontMetrics
            val x = (yAxisWidth - bounds.width()) / 2f
            val fontHeight = fm.bottom - fm.top
            val y = originY - index * yGap - fontHeight / 2f - fm.top
            canvas.drawText(text, x, y, yAxisTextPaint)
        }
        //绘制数据
        path.reset()
        points.forEachIndexed { index, point ->
            val x = originX + index * xGap + xGap / 2f
            val y = originY - (point.yAxis.toFloat() / yAxisMaxValue) * (yGap * (yAxisCount - 1))
            if (index == 0) {
                path.moveTo(x, y)
            } else {
                path.lineTo(x, y)
            }
            //圆点
            circlePaint.color = dataOptions.circleColor
            canvas.drawCircle(x, y, dataOptions.circleRadius, circlePaint)
        }
        pathPaint.strokeWidth = dataOptions.pathWidth
        pathPaint.color = dataOptions.pathColor
        canvas.drawPath(path, pathPaint)
    }
    /**
     * 设置数据
     */
    fun setData(list: List<ChartBean>) {
        points.clear()
        points.addAll(list)
        //设置X轴、Y轴数据
        setXAxisData(list)
        setYAxisData(list)
        invalidate()
    }
    /**
     * 设置X轴数据
     */
    private fun setXAxisData(list: List<ChartBean>) {
        val xAxisOptions = options.xAxisOptions
        val values = list.map { it.xAxis }
        //X轴文本
        xAxisTexts.clear()
        xAxisTexts.addAll(values)
        //X轴高度
        val fontMetrics = xAxisTextPaint.fontMetrics
        val lineHeight = fontMetrics.bottom - fontMetrics.top
        xAxisHeight = lineHeight + xAxisOptions.textMarginTop + xAxisOptions.textMarginBottom
    }
    /**
     * 设置Y轴数据
     */
    private fun setYAxisData(list: List<ChartBean>) {
        val yAxisOptions = options.yAxisOptions
        yAxisTextPaint.textSize = yAxisOptions.textSize
        yAxisTextPaint.color = yAxisOptions.textColor
        val texts = list.map { it.yAxis.toString() }
        yAxisTexts.clear()
        yAxisTexts.addAll(texts)
        //Y轴高度
        val maxTextWidth = yAxisTexts.maxOf { yAxisTextPaint.measureText(it) }
        yAxisWidth = maxTextWidth + yAxisOptions.textMarginLeft + yAxisOptions.textMarginRight
        //Y轴间隔
        val maxY = list.maxOf { it.yAxis }
        val interval = when {
            maxY <= 10 -> getYInterval(10)
            else -> getYInterval(maxY)
        }
        //Y轴文字
        yAxisTexts.clear()
        for (index in 0..yAxisCount) {
            val value = index * interval
            yAxisTexts.add(formatNum(value))
        }
        yAxisMaxValue = (yAxisCount - 1) * interval
    }
    /**
     * 格式化数值
     */
    private fun formatNum(num: Int): String {
        val absNum = Math.abs(num)
        return if (absNum >= 0 && absNum < 1000) {
            return num.toString()
        } else {
            val format = DecimalFormat("0.0")
            val value = num / 1000f
            "${format.format(value)}k"
        }
    }
    /**
     * 根据Y轴最大值、数量获取Y轴的标准间隔
     */
    private fun getYInterval(maxY: Int): Int {
        val yIntervalCount = yAxisCount - 1
        val rawInterval = maxY / yIntervalCount.toFloat()
        val magicPower = floor(log10(rawInterval.toDouble()))
        var magic = 10.0.pow(magicPower).toFloat()
        if (magic == rawInterval) {
            magic = rawInterval
        } else {
            magic *= 10
        }
        val rawStandardInterval = rawInterval / magic
        val standardInterval = getStandardInterval(rawStandardInterval) * magic
        return standardInterval.roundToInt()
    }
    /**
     * 根据初始的归一化后的间隔,转化为目标的间隔
     */
    private fun getStandardInterval(x: Float): Float {
        return when {
            x <= 0.1f -> 0.1f
            x <= 0.2f -> 0.2f
            x <= 0.25f -> 0.25f
            x <= 0.5f -> 0.5f
            x <= 1f -> 1f
            else -> getStandardInterval(x / 10) * 10
        }
    }
    /**
     * 重置参数
     */
    fun setOptions(newOptions: ChartOptions) {
        this.options = newOptions
        setData(points)
    }
    fun getOptions(): ChartOptions {
        return options
    }
    data class ChartBean(val xAxis: String, val yAxis: Int)
}

ChartOptions配置选项

class ChartOptions {
    //X轴配置
    var xAxisOptions = AxisOptions()
    //Y轴配置
    var yAxisOptions = AxisOptions()
    //数据配置
    var dataOptions = DataOptions()
}
/**
 * 轴线配置参数
 */
class AxisOptions {
    companion object {
        private const val DEFAULT_TEXT_SIZE = 20f
        private const val DEFAULT_TEXT_COLOR = Color.BLACK
        private const val DEFAULT_TEXT_MARGIN = 20
        private const val DEFAULT_LINE_WIDTH = 2f
        private const val DEFAULT_RULER_WIDTH = 10f
    }
    /**
     * 文字大小
     */
    @FloatRange(from = 1.0)
    var textSize: Float = DEFAULT_TEXT_SIZE
    @ColorInt
    var textColor: Int = DEFAULT_TEXT_COLOR
    /**
     * X轴文字内容上下两侧margin
     */
    var textMarginTop: Int = DEFAULT_TEXT_MARGIN
    var textMarginBottom: Int = DEFAULT_TEXT_MARGIN
    /**
     * Y轴文字内容左右两侧margin
     */
    var textMarginLeft: Int = DEFAULT_TEXT_MARGIN
    var textMarginRight: Int = DEFAULT_TEXT_MARGIN
    /**
     * 轴线
     */
    var lineWidth: Float = DEFAULT_LINE_WIDTH
    @ColorInt
    var lineColor: Int = DEFAULT_TEXT_COLOR
    var isEnableLine = true
    var linePathEffect: PathEffect? = null
    /**
     * 刻度
     */
    var rulerWidth = DEFAULT_LINE_WIDTH
    var rulerHeight = DEFAULT_RULER_WIDTH
    @ColorInt
    var rulerColor = DEFAULT_TEXT_COLOR
    var isEnableRuler = true
    /**
     * 网格
     */
    var gridWidth: Float = DEFAULT_LINE_WIDTH
    @ColorInt
    var gridColor: Int = DEFAULT_TEXT_COLOR
    var gridPathEffect: PathEffect? = null
    var isEnableGrid = true
}
/**
 * 数据配置参数
 */
class DataOptions {
    companion object {
        private const val DEFAULT_PATH_WIDTH = 2f
        private const val DEFAULT_PATH_COLOR = Color.BLACK
        private const val DEFAULT_CIRCLE_RADIUS = 10f
        private const val DEFAULT_CIRCLE_COLOR = Color.BLACK
    }
    var pathWidth = DEFAULT_PATH_WIDTH
    var pathColor = DEFAULT_PATH_COLOR
    var circleRadius = DEFAULT_CIRCLE_RADIUS
    var circleColor = DEFAULT_CIRCLE_COLOR
}

Demo样式

private fun initView() {
    val options = binding.chart.getOptions()
    //X轴
    val xAxisOptions = options.xAxisOptions
    xAxisOptions.isEnableLine = false
    xAxisOptions.textColor = Color.parseColor("#999999")
    xAxisOptions.textSize = dpToPx(12)
    xAxisOptions.textMarginTop = dpToPx(12).toInt()
    xAxisOptions.textMarginBottom = dpToPx(12).toInt()
    xAxisOptions.isEnableGrid = false
    xAxisOptions.isEnableRuler = false
    //Y轴
    val yAxisOptions = options.yAxisOptions
    yAxisOptions.isEnableLine = false
    yAxisOptions.textColor = Color.parseColor("#999999")
    yAxisOptions.textSize = dpToPx(12)
    yAxisOptions.textMarginLeft = dpToPx(12).toInt()
    yAxisOptions.textMarginRight = dpToPx(12).toInt()
    yAxisOptions.gridColor = Color.parseColor("#999999")
    yAxisOptions.gridWidth = dpToPx(0.5f)
    val dashLength = dpToPx(8f)
    yAxisOptions.gridPathEffect = DashPathEffect(floatArrayOf(dashLength, dashLength / 2), 0f)
    yAxisOptions.isEnableRuler = false
    //数据
    val dataOptions = options.dataOptions
    dataOptions.pathColor = Color.parseColor("#79EBCF")
    dataOptions.pathWidth = dpToPx(1f)
    dataOptions.circleColor = Color.parseColor("#79EBCF")
    dataOptions.circleRadius = dpToPx(3f)
    binding.chart.setOnClickListener {
        initChartData()
    }
    binding.toolbar.setLeftClick {
        finish()
    }
}

private fun initChartData() {
    val random = 1000
    val list = mutableListOf&lt;LineChart.ChartBean&gt;()
    list.add(LineChart.ChartBean("05-01", Random.nextInt(random)))
    list.add(LineChart.ChartBean("05-02", Random.nextInt(random)))
    list.add(LineChart.ChartBean("05-03", Random.nextInt(random)))
    list.add(LineChart.ChartBean("05-04", Random.nextInt(random)))
    list.add(LineChart.ChartBean("05-05", Random.nextInt(random)))
    list.add(LineChart.ChartBean("05-06", Random.nextInt(random)))
    list.add(LineChart.ChartBean("05-07", Random.nextInt(random)))
    binding.chart.setData(list)
    //文本
    val text = list.joinToString("\n") {
        "x : ${it.xAxis}  y:${it.yAxis}"
    }
    binding.value.text = text
}

总结

到此这篇关于Android自定义折线图控件的文章就介绍到这了,更多相关Android自定义折线图控件内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android自定义View简易折线图控件(二)

    继续练习自定义View,这次带来的是简易折线图,支持坐标点点击监听,效果如下: 画坐标轴.画刻度.画点.连线..x.y轴的数据范围是写死的 1 <= x <= 7 ,1 <= y <= 70 ..写活的话涉及到坐标轴刻度的动态计算.坐标点的坐标修改,想想就头大,这里只练习自定义View. 1.在res/values文件夹下新建attrs.xml文件,编写自定义属性: <?xml version="1.0" encoding="utf-8"

  • Android开发自定义控件之折线图实现方法详解

    本文实例讲述了Android开发自定义控件之折线图实现方法.分享给大家供大家参考,具体如下: 前言 折线图是Android开发中经常会碰到的效果,但由于涉及自定义View的知识,对许多刚入门的小白来说会觉得很高深.其实不然,接下来我就以尽量通俗的语言来说明下图折线图效果的实现过程. 效果图 实现过程 首先,选择自定义控件的方式. 自定义控件的实现有四种方式: 1.继承View,重写onDraw.onMeasure等方法. 2.继承已有的View(比如TextView). 3.继承ViewGrou

  • Android自定义控件实现折线图

    本文实例实现一个如下图所示的Android折线图,供大家参考,具体内容如下 首先是控件绘图区域的划分,控件左边取一小部分(控件总宽度的八分之一)绘制表头,右边剩余的部分绘制表格 确定表格的行列数,首先绘制一个三行八列的网格,设置好行列的坐标后开始绘制 /*绘制三条横线*/ for(int i=0;i<3;i++){ canvas.drawLine(textWide, mLineYs[i], totalWidth, mLineYs[i], mPaintLine); } /*绘制八条竖线*/ for

  • Android自定义折线图控件的完整步骤

    目录 前言 概述 原点 计算Y轴宽度 计算X轴高度 X轴 绘制轴线 X轴刻度间隔 网格线.文本 Y轴 计算Y轴分布 刻度间隔.网格线.文本 折线 代码 总结 前言 日前,有一个“折现图”的需求,如下图所示: 概述 如何自定义折线图?首先将折线图的绘制部分拆分成三部分: 原点 X轴 Y轴 折线 原点 第一步,需要定义出“折线图”原点的位置,由图得: 可以发现,原点的位置由X轴.Y轴所占空间决定: OriginX:Y轴宽度OriginY:View高度 - X轴高度 计算Y轴宽度 思路:遍历Y轴的绘制

  • Android 实现自定义折线图控件

    目录 前言 概述 原点 计算Y轴宽度 计算X轴高度 X轴 绘制轴线 X轴刻度间隔 网格线.文本 Y轴 计算Y轴分布 刻度间隔.网格线.文本 折线 代码 前言 日前,有一个“折现图”的需求,如下图所示: 概述 如何自定义折线图?首先将折线图的绘制部分拆分成三部分: 原点 X轴 Y轴 折线 原点 第一步,需要定义出“折线图”原点的位置,由图得: 可以发现,原点的位置由X轴.Y轴所占空间决定: OriginX:Y轴宽度 OriginY:View高度 - X轴高度 计算Y轴宽度 思路:遍历Y轴的绘制文字

  • Android自定义双向滑动控件

    本文实例为大家分享了Android自定义双向滑动控件的具体代码,供大家参考,具体内容如下 先看一下效果图 1.SeekBarPressure工具类 public class SeekBarPressure extends View {     private static final String TAG = "SeekBarPressure";     private static final int CLICK_ON_LOW = 1;      //点击在前滑块上     priv

  • Android自定义播放器控件VideoView

    介绍 最近要使用播放器做一个简单的视频播放功能,开始学习VideoView,在横竖屏切换的时候碰到了点麻烦,不过在查阅资料后总算是解决了.在写VideoView播放视频时候定义控制的代码全写在Actvity里了,写完一看我靠代码好乱,于是就写了个自定义的播放器控件,支持指定大小,可以横竖屏切换,手动左右滑动快进快退.好了,下面开始. 效果图有点卡,我也不知道为啥..... VideoView介绍 这个是我们实现视频播放最主要的控件,详细的介绍大家百度就去看,这里介绍几个常用的方法. 用于播放视频

  • Android自定义加载控件实现数据加载动画

    本文实例为大家分享了Android自定义加载控件,第一次小人跑动的加载效果眼前一亮,相比传统的PrograssBar高大上不止一点,于是走起,自定义了控件LoadingView去实现动态效果,可直接在xml中使用,具体实现如下 package com.*****.*****.widget; import android.content.Context; import android.graphics.drawable.AnimationDrawable; import android.util.

  • Android自定义圆环倒计时控件

    本文实例为大家分享了Android自定义圆环倒计时控件的具体代码,供大家参考,具体内容如下 先来一张最终效果图: 主要思路: 在画渐变色圆环的时候,设置一个属性动画,根据属性动画的执行时长,来作为倒计时的时长.监听属性动画的进度,来达到 倒计时的目的. 二话不说,直接贴代码.具体实现思路都在注释上. 自定义属性: <declare-styleable name="CountDownProgressBar"> <attr name="countDown_cir

  • Android自定义日历滑动控件

    本文实例为大家分享了Android自定义日历滑动控件的使用方法,供大家参考,具体内容如下 最近公司项目需要做这个需求,自己才疏学浅,总算能写出个大概来,遂在这里记录下来. 分析 先来分析一下: 首先,我们的需求是可以左右点击查看跳转到下一个月,中间的日历控件可以水平滚动选择日期,所以我们中间的日历控件用一个RecycleView来做,左右两位的为ImageVeiw. LRCalendarView 总体流程: 编写LRCalendarView的布局R.layout.calendar_view 新建

  • Android自定义实现日历控件

    本文实例为大家分享了Android自定义实现日历控件的具体代码,供大家参考,具体内容如下 1. Calendar类 2. 布局 创建calendar_layout.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:padding="20sp" android:orientation="vertical" android:l

  • C#折线图控件使用方法详解

    本文实例为大家分享了C#编写折线图控件的具体代码,供大家参考,具体内容如下 简单解说 这是第一次写博客,也是第一次发布自己写代码,有不足之处请多见谅.源代码参考了网络搜索到的一些资源.因为我需要的折线图数据没有小于0的,所以在计算时偷懒了.只支持大于0的数据. 上图 如何插入一段漂亮的代码片因为自学编程,代码注释与命名比较乱,请见谅.这是新建控件的代码.需要给控件添加FoldLineDiagram_Resize 事件. using System; using System.Collections

随机推荐