自定义控件无名之辈的Android之路

Android日志:自定义属性和文本绘制

2021-08-26  本文已影响0人  搬码人

概览

  • 改变自定义View属性值得两种方式
    1、代码直接设置
    2、自定义属性,xml中配置
  • 自定义属性的步骤
    1、 创建attr.xml
    2、添加declared-style节点
    3、添加属性 <attr name=  format=  />
    4、xml中使用
    5、代码中解析xml中配置的值
  • 文本绘制
    drawText()

代码直接设置

package com.example.falltype

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import java.time.format.DecimalStyle

/**
 *@Description
 *@Author PC
 *@QQ 1578684787
 */
class ProgressVIew:View {
    private var cx = 0f
    private var cy = 0f
    private var radius = 0f
    var lineWidth = 5f
    set(value) {
        field = value
        bgPaint.strokeWidth = value
    }
    var bgCircleColor = Color.GREEN
    set(value) {
        field = value
        bgPaint.color = value
    }
    private val bgPaint = Paint().apply {
        style = Paint.Style.STROKE
        strokeWidth = lineWidth
        color = bgCircleColor
    }
    //代码创建这个View时被调用
    constructor(context: Context):super(context){}
    //xml中添加
    constructor(context: Context,attrs:AttributeSet?):super(context,attrs){}
    constructor(context: Context,attrs: AttributeSet?,style:Int):super(context,attrs,style){}

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        cx = measuredWidth/2f
        cy = measuredHeight/2f
        radius = if (measuredWidth<=measuredHeight){
            measuredWidth/2f - lineWidth
        }else{
            measuredHeight/2f -lineWidth
        }
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.drawCircle(cx,cy,radius,bgPaint)
    }
}

在mainActivity中
对对应的属性值进行设置更改

 //用代码来改变对象属性的值
        mbinding.progress.lineWidth = 20f
        mbinding.progress.bgCircleColor = resources.getColor(R.color.main_purple,null)

代码中对lineWidth和bgCircleColor必须重写set方法,否则没有更改效果->原因:
外部的设置只是更改了这两个属性的值,画笔(Paint)并没有改变,所以必须重写这两个属性的set方法。

但是从结果来看:用代码的方式设置时xml文件显示结果却没有变化,不过手机中的运行结果是更改后的结果。这就引出了下面的配置xml方式。

xml中的显示结果 手机显示结果

配置xml方式自定义属性

可以使用下方的方式添加,也可以直接在string.xml文件中添加

image.png

declare-styleable name对应自定义View的名字
attr name->自定义属性名  format->属性类型
reference:表示引用类型  例如:


例子
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ProgressVIew">
        <attr name="lineWidth" format="float"/>
        <attr name="backgroundCircleColor" format="color|reference"/>
    </declare-styleable>
</resources>

创建parseAttrs方法对属性进行解析
obtainStyleAttributes获取对应的declare-styleable对象,然后再对之前配置的自定义属性进行解析
返回值为TypedArray
必须使用recycle()回收

 //代码创建这个View时被调用
    constructor(context: Context):super(context){}
    //xml中添加
    constructor(context: Context,attrs:AttributeSet?):super(context,attrs){
        parseAttrs(context,attrs)
    }
    constructor(context: Context,attrs: AttributeSet?,style:Int):super(context,attrs,style){
        parseAttrs(context,attrs)
    }
//解析自定义属性的值
    private fun parseAttrs(context: Context,attrs:AttributeSet?){
        /**使用obtainStyledAttributes从attrs中解析所有的属性
         * resId:R.styleable.ProgressVIew,attrs 指定从哪个样式中解析
         * attrs:解析的来源
         * 返回值:TypedArray
         * 必须使用recycle()回收
         */
        context.obtainStyledAttributes(attrs,R.styleable.ProgressVIew).apply {
            //获取线的宽度 defValue为默认值
            lineWidth = this.getFloat(R.styleable.ProgressVIew_lineWidth,5f)
            //获取背景颜色
            bgCircleColor = this.getColor(R.styleable.ProgressVIew_backgroundCircleColor,Color.BLUE)
            this.recycle()
        }
    }
xml文件中

可以看出,用xml的方式对自定义属性进行更改的结果:xml和手机上的显示结果一致。

自定义文本绘制

文本的绘制同画圆和画弧基本一样,细微差别就在属性的设置。
下面以一个模仿下载动画的Demo介绍文本绘制

点击开始下载弧形进度条滚动加载,同时中心的数字显示下载进度。点击暂停进度将暂停。

下载动画

在之前的基础上添加如下代码

变量progress:进度动画因子,默认值0f,属性动画中值得变化0f->1f。
在画弧drawArc()中使progress * 360 使progress值变化过程中,弧形进度条跟着移动形成下载动画。
同时中心text * 100使中心文本数字一起变化。

 var fgCircleColor  = Color.LTGRAY
    set(value) {
        field = value
        fgPaint.color = value
    }
 private val fgPaint = Paint().apply {
        style = Paint.Style.STROKE
        strokeWidth = lineWidth
        color = fgCircleColor
    }
private val textPaint = Paint().apply {
        color = Color.BLACK
        textSize = 100f
        textAlign = Paint.Align.CENTER
    }
    //进度变化因子
    var progress = 0f
        set(value) {
       field = value
        invalidate()
        }
 @SuppressLint("Recycle")
    private fun parseAttrs(context: Context, attrs:AttributeSet?){
 context.obtainStyledAttributes(attrs,R.styleable.ProgressVIew).apply {
            //获取线的宽度 defValue为默认值
            lineWidth = this.getFloat(R.styleable.ProgressVIew_lineWidth,5f)
            //获取背景颜色
            bgCircleColor = this.getColor(R.styleable.ProgressVIew_backgroundCircleColor,Color.BLUE)
            fgCircleColor = this.getColor(R.styleable.ProgressVIew_foregroundCircleColor,Color.BLUE)
            this.recycle()
        }
    }
override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        //绘制背景圆
        canvas?.drawCircle(cx,cy,radius,bgPaint)
        //绘制进度条
        canvas?.drawArc(cx-radius,cy-radius,cx+radius,cy+radius,270f,progress*360,false,fgPaint)

        val text = "${(progress*100).toInt()}%"
        val metrics = textPaint.fontMetrics
        val space = (metrics.descent-metrics.ascent)/2 - metrics.descent
        canvas?.drawText(text,cx,cy+space,textPaint)

    }

注:如果仅仅以的方式将文本设置到中心,其运行结果并不在中心。原因如下图:
默认的中心线为下方的虚线,但从运行结果视觉上看文本并不在中心,所以必须经过计算将下方的绿线位置设到中心,这就需要访问Paint的fontMetrics属性。具体计算过程见上方代码。


image.png

MainActivity中

 //按钮点击事件
        mbinding.mStart.setOnClickListener {
            //如果暂停则继续下载 否则开始下载
            if (animator.isPaused){
                animator.resume()
            }else{
                animator.start()
            }
        }
        mbinding.mPause.setOnClickListener {
            if (animator.isRunning){
                animator.pause()
            }
        }

注:这里invalidate()放在进度动画因子progress的set方法中,set方法在接受外部数据更改时调佣invalidate()刷新动画。

自定义View中的全部代码

package com.example.falltype

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import java.time.format.DecimalStyle

/**
 *@Description
 *@Author PC
 *@QQ 1578684787
 */
class ProgressVIew:View {
    private var cx = 0f
    private var cy = 0f
    private var radius = 0f
    var lineWidth = 5f
    set(value) {
        field = value
        bgPaint.strokeWidth = value
        fgPaint.strokeWidth = value
    }
    var bgCircleColor = Color.GREEN
    set(value) {
        field = value
        bgPaint.color = value
    }
    var fgCircleColor  = Color.LTGRAY
    set(value) {
        field = value
        fgPaint.color = value
    }
    private val bgPaint = Paint().apply {
        style = Paint.Style.STROKE
        strokeWidth = lineWidth
        color = bgCircleColor
    }
    private val fgPaint = Paint().apply {
        style = Paint.Style.STROKE
        strokeWidth = lineWidth
        color = fgCircleColor
    }
    private val textPaint = Paint().apply {
        color = Color.BLACK
        textSize = 100f
        textAlign = Paint.Align.CENTER
    }
    //进度变化因子
    var progress = 0f
        set(value) {
       field = value
        invalidate()
        }
    //代码创建这个View时被调用
    constructor(context: Context):super(context){}
    //xml中添加
    constructor(context: Context,attrs:AttributeSet?):super(context,attrs){
        parseAttrs(context,attrs)
    }
    constructor(context: Context,attrs: AttributeSet?,style:Int):super(context,attrs,style){
        parseAttrs(context,attrs)
    }
//解析自定义属性的值
    @SuppressLint("Recycle")
    private fun parseAttrs(context: Context, attrs:AttributeSet?){
        /**使用obtainStyledAttributes从attrs中解析所有的属性
         * resId:R.styleable.ProgressVIew,attrs 指定从哪个样式中解析
         * attrs:解析的来源
         * 返回值:TypedArray
         * 必须使用recycle()回收
         */
        context.obtainStyledAttributes(attrs,R.styleable.ProgressVIew).apply {
            //获取线的宽度 defValue为默认值
            lineWidth = this.getFloat(R.styleable.ProgressVIew_lineWidth,5f)
            //获取背景颜色
            bgCircleColor = this.getColor(R.styleable.ProgressVIew_backgroundCircleColor,Color.BLUE)
            fgCircleColor = this.getColor(R.styleable.ProgressVIew_foregroundCircleColor,Color.BLUE)
            this.recycle()
        }
    }
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        cx = measuredWidth/2f
        cy = measuredHeight/2f
        radius = if (measuredWidth<=measuredHeight){
            measuredWidth/2f - lineWidth
        }else{
            measuredHeight/2f -lineWidth
        }
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        //绘制背景圆
        canvas?.drawCircle(cx,cy,radius,bgPaint)
        //绘制进度条
        canvas?.drawArc(cx-radius,cy-radius,cx+radius,cy+radius,270f,progress*360,false,fgPaint)

        val text = "${(progress*100).toInt()}%"
        val metrics = textPaint.fontMetrics
        val space = (metrics.descent-metrics.ascent)/2 - metrics.descent
        canvas?.drawText(text,cx,cy+space,textPaint)

    }
}
上一篇 下一篇

猜你喜欢

热点阅读