Android日志:自定义属性和文本绘制
概览
- 改变自定义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.pngdeclare-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)
}
}