自我绘制二

296 阅读6分钟

一 整体缩放绘制(不用自己每个都去计算)

上一篇文章说过解决问题:用户设置的宽度和高度比自定义默认需要的宽度和高度还要小,我们是通过在onChangedSize方法中去更新默认的宽度和高度,然后再通过比例去算出字体的缩小率,去更新字体的大小。

这样做没有问题?

假如我们自定义控件中有很多元素,我们难道都要一一缩放嘛?

当然不需要!

Google工程师已经给我们提供好了缩放的方法,只需要我们去设置一个比例:

canvas.scale(scal, scal)

他就会自己缩放绘制。

都需要哪些地方去缩放?

1.测量方法中测量的控件大小需要缩放。(当然这个不是必须的,让系统去自己算,但我喜欢给系统准确的值,所以一般我都算出来一个正确的值给系统)

2.计算绘制的比例,然后设置给画布,(这里是必须的,否则就会显示不完整)

解决1:计算控件的大小不要超过最大能给的值,代码如下:

//测量控件的大小
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    //获取父布局的宽高和模式要求
    val widthMode = View.MeasureSpec.getMode(widthMeasureSpec)
    val widthSize = View.MeasureSpec.getSize(widthMeasureSpec)
    val heightMode = View.MeasureSpec.getMode(heightMeasureSpec)
    val heightSize = View.MeasureSpec.getSize(heightMeasureSpec)
    //宽默认缩放系数,高默认缩放系数
    var hScale = 1.0f
    var vScale = 1.0f
    //计算两个缩放比例
    //父布局不能滚动,并且给的值不能满足绘制完整,就需要缩放
    if (widthMode != View.MeasureSpec.UNSPECIFIED && widthSize < defult_Widht) {
        hScale = widthSize.toFloat() / defult_Widht
    }

    if (heightMode != View.MeasureSpec.UNSPECIFIED && heightSize < defult_Height) {
        vScale = heightSize.toFloat() / defult_Height
    }
    //取较小的缩放比例
    var scale = Math.min(hScale, vScale)
    //设置进去正确的数字
    setMeasuredDimension(resolveSize((defult_Widht * scale).toInt(), widthMeasureSpec), resolveSize((defult_Height * scale).toInt(), heightMeasureSpec))
}

一看都明白,可写可不写,没有什么好说的!

解决2,怎么在绘制的时候算出这个缩放绘制的比例呢?

我们在绘制的时候获取控件能绘制的真实宽高,然后和默认宽高去比较,如果比默认宽高还要小就需要去计算缩放,计算出来,取一个较小值,然后去设置即可。

代码:

override fun onDraw(canvas: Canvas) {
    //计算控件真实显示的宽高
    val realWidht = right - left
    val realHeight = bottom - top
    //默认缩放比例
    var scal = 1.0F
    //说明需要缩放,计算缩放绘制
    if (realWidht < defult_Widht || realHeight < defult_Height) {
        scal = Math.min(realWidht / defult_Widht, realHeight / defult_Height)
        canvas.save()
        //缩放绘制
        canvas.scale(scal, scal)
    }
    ......
  绘制代码,按照默认去绘制即可,因为布局已经缩放

}

二绘制圆半径细节

假如我们绘制的圆的半径是100,圆线的宽度是40,那么我们在指定圆的半径的时候应该是

设置的半径 = 100-40/2

如图:红线的是需要设置的半径长度


三.绘制一个圆形进度条

绘制效果图:


绘制计算图:


分析:

1.需要绘制一个圆形的背景,计算圆的圆心和半径(上边说过)

使用 canvas.drawCircle(centerX, centerY, radius, bg_Paint)

2.绘制中心文字,计算文字的绘制起点坐标(第一篇文章讲过)

3.绘制底部的文字,计算文字绘制的起点坐标(第一篇文章讲过)

4.绘制可以结合属性动画绘制的圆弧,然后动态去改变圆弧角度即可

使用 canvas.drawArc,这里边界的设置也是线宽度的一半

然后就是贴出今天效果图的源码:

package MyViews

import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.os.Build
import android.support.annotation.RequiresApi
import android.util.AttributeSet
import android.view.View

/**
 * Created by Administrator on 2018/7/10.
 */
class CircleProgress : View {
    //控件默认的宽
    private var defult_Widht = DisplayUtils.dip2px(context, 150F)
    //控件默认的高
    private var defult_Height = DisplayUtils.dip2px(context, 180F)
    //默认圆绘制线宽度
    private var defult_bg_StrokeWidth = DisplayUtils.dip2px(context, 10F)
    //画背景圆的画笔
    private lateinit var bg_Paint: Paint
    //绘制中心文字的画笔
    private lateinit var centerText_Paint: Paint
    //默认文字的大小
    private val textSize = DisplayUtils.sp2px(context, 38F)
    //进度值
    private var progress = 0
        set(value) {
            field = value
            invalidate()
        }
    //绘制底部文字的画笔
    private lateinit var bottomText_Paint: Paint
    //绘制弧线的画笔
    private lateinit var arc_Paint: Paint
    //最后结束的值
    var endProgress = 0


    //构造方法
    constructor(context: Context?) : this(context, null)

    //构造方法
    constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)

    //构造方法
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : this(context, attrs, defStyleAttr, 0)

    //构造方法
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
        initPaint()
    }

    //初始化画笔
    fun initPaint() {
        //创建画背景圆的画笔
        bg_Paint = Paint(Paint.ANTI_ALIAS_FLAG)
        //设置画线模式
        bg_Paint.style = Paint.Style.STROKE
        //设置画线的宽度
        bg_Paint.strokeWidth = defult_bg_StrokeWidth
        //设置线的颜色
        bg_Paint.color = Color.parseColor("#cccccc")

        //创建绘制中心文字的画笔
        centerText_Paint = Paint(Paint.ANTI_ALIAS_FLAG)
        //设置绘制文字画笔颜色
        centerText_Paint.color = Color.parseColor("#ff9de2")
        //计算文字开始大小
        centerText_Paint.textSize = textSize.toFloat()
        //设置中心位置
        centerText_Paint.textAlign = Paint.Align.CENTER
        //设置文字加粗
        centerText_Paint.isFakeBoldText = true

        //创建绘制底部文字的画笔
        bottomText_Paint = Paint(Paint.ANTI_ALIAS_FLAG)
        //设置绘制文字画笔颜色
        bottomText_Paint.color = Color.parseColor("#999999")
        //计算文字开始大小
        bottomText_Paint.textSize = textSize.toFloat() / 2
        //设置中心位置
        bottomText_Paint.textAlign = Paint.Align.CENTER
        //设置文字加粗
        bottomText_Paint.isFakeBoldText = true

        //创建绘制弧形的画笔
        arc_Paint = Paint(Paint.ANTI_ALIAS_FLAG)
        //画笔的颜色
        arc_Paint.color = Color.parseColor("#FFAA00")
        //设置画线模式
        arc_Paint.style = Paint.Style.STROKE
        //设置画线的宽度
        arc_Paint.strokeWidth = defult_bg_StrokeWidth
        //设置绘制源头
        arc_Paint.strokeCap = Paint.Cap.ROUND
    }

    //测量控件的大小
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        //获取父布局的宽高和模式要求
        val widthMode = View.MeasureSpec.getMode(widthMeasureSpec)
        val widthSize = View.MeasureSpec.getSize(widthMeasureSpec)
        val heightMode = View.MeasureSpec.getMode(heightMeasureSpec)
        val heightSize = View.MeasureSpec.getSize(heightMeasureSpec)
        //宽默认缩放系数,高默认缩放系数
        var hScale = 1.0f
        var vScale = 1.0f
        //计算两个缩放比例
        //父布局不能滚动,并且给的值不能满足绘制完整,就需要缩放
        if (widthMode != View.MeasureSpec.UNSPECIFIED && widthSize < defult_Widht) {
            hScale = widthSize.toFloat() / defult_Widht
        }

        if (heightMode != View.MeasureSpec.UNSPECIFIED && heightSize < defult_Height) {
            vScale = heightSize.toFloat() / defult_Height
        }
        //取较小的缩放比例
        var scale = Math.min(hScale, vScale)
        //设置进去正确的数字
        setMeasuredDimension(resolveSize((defult_Widht * scale).toInt(), widthMeasureSpec), resolveSize((defult_Height * scale).toInt(), heightMeasureSpec))
    }


    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    override fun onDraw(canvas: Canvas) {
        //计算控件真实显示的宽高
        val realWidht = right - left
        val realHeight = bottom - top
        //默认缩放比例
        var scal = 1.0F
        //说明需要缩放,计算缩放绘制
        if (realWidht < defult_Widht || realHeight < defult_Height) {
            scal = Math.min(realWidht / defult_Widht, realHeight / defult_Height)
            canvas.save()
            //缩放绘制
            canvas.scale(scal, scal)
        }
        //绘制背景圆形
        drawCircleBG(canvas)
        //绘制中心文字
        drawCenterText(canvas)
        //绘制底部文字
        drawBottomText(canvas)
        //绘制进度
        drawProgressCircle(canvas)

    }


    //画背景圆
    private fun drawCircleBG(canvas: Canvas) {
        //计算圆心和半径
        val centerX = defult_Widht / 2
        val centerY = defult_Widht / 2
        //这里的半径指定的是线宽度的中心位置
        val radius = (defult_Widht - defult_bg_StrokeWidth) / 2
        canvas.drawCircle(centerX, centerY, radius, bg_Paint)
    }

    //绘制中心文字
    private fun drawCenterText(canvas: Canvas) {
        //计算绘制文字开始xy坐标,这里计算基线是把圆形看成一个矩形去计算
        val x = defult_Widht / 2
        val fontMetrics = centerText_Paint.getFontMetrics()
        val baseLine = defult_Widht / 2 + (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom
        canvas.drawText("$progress%", x, baseLine, centerText_Paint)
    }

    //绘制底部文字
    private fun drawBottomText(canvas: Canvas) {
        //计算底部文字的开始绘制坐标,这里是以底部巨型做为文字的起始点去计算
        val x = defult_Widht / 2
        val fontMetrics = bottomText_Paint.getFontMetrics()
        val baseLine = defult_Widht + (defult_Height - defult_Widht) / 2 + (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom
        canvas.drawText("当前进度$progress%", x, baseLine, bottomText_Paint)
    }

    //绘制进度
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    private fun drawProgressCircle(canvas: Canvas) {
        val d = progress * 360 / 100
        //边界要考虑线的宽度的一半计算进去
        canvas.drawArc(0F + defult_bg_StrokeWidth / 2,
                0F + defult_bg_StrokeWidth / 2,
                defult_Widht - defult_bg_StrokeWidth / 2,
                defult_Widht - defult_bg_StrokeWidth / 2,
                90F, d.toFloat(), false, arc_Paint)

    }


    //===================================以下是设置属性的方法================================
    //开启动画
    fun start() {
        //创建动画
        val animator = ObjectAnimator.ofInt(this, "progress", 0, endProgress)
        animator.duration = 1000
        animator.start()
    }


}

接下来我打算写一批自我绘制的文章,当然并不是每篇都有营养,而是记录一下自己进阶自定义View的过程,通过练习这些小的自定义view,来把自定义View的AP融会贯通。

源码地址:github.com/XiFanYin/Cu…