本篇文章主要记录本周学习的进度动画,一个特别有趣的例子,当我们点击的时候,会开始进度值变化,还会有圆环进度变化
演示:

image.png
下面让我们一起实现一下
activity_mian界面布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<me.jrl.demo4.DownloadView
android:id="@+id/mDownLoadView"
android:layout_width="200dp"
android:layout_height="200dp"
app:bgColor="@color/colorAccent"
app:pgColor="@color/colorPrimary"
app:txColor="@android:color/black"
app:lineWidth="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
布局可以根据自己需要调整,这里为了好看,我就设置了屏幕居中了
DownloadView自定义View
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.text.TextPaint
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import kotlin.math.log
import kotlin.math.min
class DownloadView : View {
/**
* 圆环画笔,绘制背景
* */
private val mBgPaint by lazy {
Paint().apply {
strokeWidth = mLineWidth
color = mBgColor
style = Paint.Style.STROKE
isAntiAlias = true
isDither = true
}
}
/**
* 背景颜色
* */
private var mBgColor = 0
/**
* 圆环画笔,绘制进度
* */
private val mPgPaint by lazy {
Paint().apply {
strokeWidth = mLineWidth
color = mPgColor
style = Paint.Style.STROKE
isAntiAlias = true
isDither = true
}
}
/**
* 进度颜色
* */
private var mPgColor = 0
/**
* 线宽
* */
private var mLineWidth = dp2px(0)
/**
* 画笔,绘制进度值
* */
private val mTxPaint by lazy {
TextPaint().apply {
strokeWidth = dp2px(2)
textSize = dp2px(20)
color = mTxColor
style = Paint.Style.STROKE
isAntiAlias = true
isDither = true
}
}
/**
* 文本颜色
* */
private var mTxColor = 0
/**
* 控件中心点
* */
private var cx = 0f
private var cy = 0f
/**
* 圆环半径
* */
private var radius = 0f
/**
* 下载动画因子,也就是进度值
* */
private var progress = 0f
/**
* 下载动画
* */
private var mAnimator: ValueAnimator? = null
/**
* 矩阵
* */
private val mRectF by lazy {
RectF(cx - radius,cy - radius,cx + radius,cy + radius)
}
/**
* 进度值
* */
private var mText = "0%"
/**
* 文字矩阵
* */
private val mFontMetricsInt by lazy {
mTxPaint.fontMetricsInt
}
constructor(context: Context) : super(context) {}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(context, attrs)
}
/**
* 提取自定义属性
* */
private fun init(context: Context, attrs: AttributeSet?) {
val array = context.obtainStyledAttributes(attrs, R.styleable.DownloadView)
mBgColor = array.getColor(R.styleable.DownloadView_bgColor, Color.GREEN)
mPgColor = array.getColor(R.styleable.DownloadView_pgColor, Color.BLUE)
mTxColor = array.getColor(R.styleable.DownloadView_txColor, Color.BLACK)
mLineWidth = array.getDimension(R.styleable.DownloadView_lineWidth, dp2px(20))
array.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 = min(measuredWidth, measuredHeight) / 2f - mLineWidth
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
//绘制背景圆环
canvas?.drawCircle(cx,cy,radius,mBgPaint)
//绘制进度圆环
canvas?.drawArc(mRectF,0f,progress * 360,false,mPgPaint)
//文字宽度的一半
val mHalfWidth = mTxPaint.measureText(mText) / 2
Log.v("pxd","mHalfWidth:$mHalfWidth")
//绘制显示的进度值
canvas?.drawText(mText,cx - mHalfWidth,cy - mFontMetricsInt.ascent / 2,mTxPaint)
}
/**
* 下载,实现下载动画
* */
private fun initAnimator() {
mAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
duration = 2000
addUpdateListener {
progress = it.animatedValue as Float
mText = "${(progress * 100).toInt()}%"
//重绘
invalidate()
}
start()
}
}
/**
* 开始动画或者暂停动画
* */
fun start() {
if (mAnimator != null) {
if (mAnimator!!.isPaused) {
mAnimator!!.resume()
} else {
mAnimator!!.start()
}
} else {
initAnimator()
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event?.action == MotionEvent.ACTION_DOWN){
start()
}
return true
}
/**
* dp 转 px
* */
private fun dp2px(dpValue: Int): Float {
val scale = context.resources.displayMetrics.density
return (dpValue * scale + 0.5f)
}
}
这里有两个需要提到的点
- 1.如何提取自定义属性
- 2.如何绘制文本,属性动画这里就不再提了
提取自定义属性
第一步在
values所对应的包名下新建attrs的xml文件
第二步声明自己想要的属性
第三步在构造方法中提取属性,也可以单独写一个方法来提取属性,我这里就是
最后在xml文件里引用自定义的属性
atrrsxml文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ProgressView">
<attr name="bg_color" format="color|integer"/>
<attr name="pg_color" format="color|integer"/>
<attr name="tv_color" format="color|integer"/>
<attr name="lineWidth" format="float|dimension"/>
<attr name="textSize" format="float|dimension"/>
</declare-styleable>
</resources>
init方法,用来提取属性
/**
* 提取自定义属性
* */
private fun init(context: Context, attrs: AttributeSet?) {
val array = context.obtainStyledAttributes(attrs, R.styleable.DownloadView)
mBgColor = array.getColor(R.styleable.DownloadView_bgColor, Color.GREEN)
mPgColor = array.getColor(R.styleable.DownloadView_pgColor, Color.BLUE)
mTxColor = array.getColor(R.styleable.DownloadView_txColor, Color.BLACK)
mLineWidth = array.getDimension(R.styleable.DownloadView_lineWidth, dp2px(20))
array.recycle()
}
这里需要注意的是
/**
* 1.使用obtainStyledAttributes
* resId: R.styleable.ProgressView 指定从哪个样式中解析
* attrs: 从哪里解析 解析的数据在哪里
* 返回值 TypedArray
* 必须使用 recycle() 回收
* */
最后在xml文件里使用属性,自定义属性一般以app:开头
app:bgColor="@color/colorAccent"
app:pgColor="@color/colorPrimary"
app:txColor="@android:color/black"
app:lineWidth="20dp"
那么我们如何绘制文本呢,具体来说就是调用canvas?.drawText方法
里面有4个参数,第一个参数是绘制的内容,中间两个参数表示绘制的起始坐标,第4个参数画笔,这里我们的x坐标取绘制文字的起始点的横坐标,也就是圆中心坐标cx减去一半文字宽度的距离mHalfWidth,y坐标取值文字高度的一半,这里求文字高度的方法不是唯一的,可以通过文字矩阵的高度的一半 - bottom的距离,这里通过将y坐标向下平移一半的ascent的距离得到,ascent的值为负,所有减去一半就是向下平移一半的距离cy - mFontMetricsInt.ascent / 2
//绘制显示的进度值
canvas?.drawText(mText,cx - mHalfWidth,cy - mFontMetricsInt.ascent / 2,mTxPaint)