Android开发(19)——自定义炫酷进度
2021-04-08 本文已影响0人
让时间走12138
本节内容
1.准备工作
2.绘制矩形区域
3.进度动画分析和高阶函数做回调
4.改变动画因子驱动动画
5.两端形变为半圆形
6.两端向中间靠拢形成圆
7.绘制勾勾或者叉叉
8.实现裁剪效果
1.png效果展示
由于不能放视频,所以我会截一些中间片段。
首先就是一个长矩形
2.png
点击该矩形之后,会有红色的矩形覆盖上来
3.png
等红色矩形完全将绿色矩形覆盖之后,红色矩形先变为圆角,然后不断缩小
4.png
最后缩成一个圆,然后中间出现一个勾
一、准备工作
1.绘制一个圆角矩形区域
2.进度变化
3.两端缩成一个半圆形
4.两端向中间靠拢,形成一个完整的圆
5.绘制勾勾 或者 叉叉
6.实现展开效果。勾勾并不是突然出现,而是一点点逐渐出现,有一个循序渐进的效果
1.创建一个类继承自View,并实现对应的构造方法
class ProgressView : View{
constructor(context: Context):super(context){}
constructor(context: Context,attrs: AttributeSet?):super(context,attrs){}
constructor(context: Context,attrs: AttributeSet?,style:Int):super(context,attrs,style){}
}
二、绘制矩形区域
1.首先,定义两个变量来记录矩形的宽和高,并在onSizeChanged方法里面给它赋值。再定义两个两边来记录中心点的坐标。
private var mWidth = 0f
private var mHeight = 0f
private var cx =0f
private var cy =0f
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
mWidth = width.toFloat()
mHeight = height.toFloat()
}
2.在onDraw里面调用drawRoundRect方法来画一个带有圆角的矩形框。里面有七个参数。
前四个代表矩形框左上右下的位置。左上为0,那么右下就为它们的宽度和高度,因为矩形框的画布也是一个矩形。
rx,ry 表示圆角的半径,如果rx,ry相同,那么就是一个扇形。如果不同,那么就有点像三角形的斜边,只不过是一条斜的弧线而已。(一般都让它们相等)
最后一个参数是画笔。
mRectPaint.color =Color.MAGENTA
canvas?.drawRoundRect(
0f, 0f, mWidth,mHeight,
cornerRadius, cornerRadius, RoundRectPaint
)
定义一下圆角半径
private var cornerRadius = 0f
提供一支画笔
private val mRectPaint = Paint().apply {
color = Color.MAGENTA
style = Paint.Style.FILL
}
三、进度动画分析和高阶函数做回调
1.进度变化效果相当于,新的矩形的宽度不断增加,我们的视觉感受就是有进度变化。
定义一个进度的变化因子
var progress = 0f
2.在绘制新的圆角矩形之前,我们要修改一下画笔的颜色。同理,在绘制之前的矩形时,我们也要修改一下画笔的颜色。不然下次启动动画时,画笔的颜色就都一样了。(见前面的代码)
mRectPaint.color = Color.BLUE
canvas?.drawRoundRect(0f,0f,progress*mWidth,mHeight,
cornerRadius,cornerRadius,mRectPaint)
3.绘制进度条分析:首先外部要先知道目前的状态,是在下载还是暂停。如果是下载的话,那么就启动下载,并获取下载数据,然后根据当前进度来绘制矩形框。
4.要实现这些效果,必须做到以下事情:
自定义的View需要监听点击事件
自定义的View需要将点击事件传递给外部
自定义的View可以接收外部的数据,调节进度
5.先监听一下点击事件,callBack一个函数回调
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event?.action == MotionEvent.ACTION_DOWN){
//将当前点击事件传递给外部
callBack?.let {back->
back()
}
}
return true
}
定义一个名为callBack的变量
var callBack:((Int)-> Unit)? = null
6.在外部设置回调函数的具体实现,mProgress是矩形的id。外部就是MainActivity里面
mProgress.callBack ={state->
}
7.设置几个表示状态的半生对象
companion object{
const val ON = 0
const val OFF = 1
}
8.定义一个变量来记录当前的状态,当我们点击的时候,在callBack里面就把state作为参数传递过去
private var state = ON
9.当我们点击之后,就要改变一下状态,也就是让state取反,为下一次点击做准备
callBack?.let {back->
back(state)
state = if(state== ON) OFF else ON
}
10.接上面第6点,如果在外部接收到的回调是ON,那么就开始下载,否则就暂停
mProgress.callBack ={state->
if (state == ProgressView.ON){
//下载
}else{
//暂停
}
}
四、改变动画因子驱动动画
1.在MainActivity里面写一个函数,模拟下载数据。ValueAnimator就是记录一个数据变到另一个数据的过程,主要是监听动画因子。
private fun downLoadAnim(){
ValueAnimator.ofFloat(0f,1.0f).apply {
duration = 2000
addUpdateListener {
( it.animatedValue as Float ).also {value->
mProgress.progress = value
}
}
}
}
2.在ProgressView里面调用set方法,接收从MainActivity里面传递过来的数据。
set(value) {
//记录外部传递过来的值
field = value
//刷新
invalidate()
}
3.在MainActivity里面添加一个动画变量,并让downLoadAnim动画赋值给它,以便控制它的暂停和开始。如果是暂停的状态,就让它resume,重新启动可以保存前面已有的状态,不会再从头来过。
private var mDownloadAnim:ValueAnimator? = null
downLoadAnim()
//设置回调函数的具体实现
mProgress.callBack ={state->
if (state == ProgressView.ON){
//下载
if (mDownloadAnim?.isPaused!!){
mDownloadAnim?.resume()
}else {
mDownloadAnim?.start()
}
}else{
//暂停
mDownloadAnim?.pause()
}
}
五、两端形变为半圆
1.当进度加载完成之后,就要开启两端形变为半圆的动画。所以在set方法里面,我们要判断一下进度是不是为1.0,如果是就调用实现动画的函数。
if(value==1.0f){
startFinishAnim()
}
2.实现这个函数
private fun startFinishAnim(){
//变成半圆
val changeIntoCircle= ValueAnimator.ofFloat(0f,mHeight/2f).apply {
duration = 1000
addUpdateListener {anim->
cornerRadius= anim.animatedValue as Float
invalidate()
}
}
}
image.png3.启动程序,点击之后,最终可以得到如下效果。
六、两端向中间靠拢形成圆
1.要实现这个效果,就是在drawRoundRec的时候,修改一下left和right这两个参数,左边往右靠(让transX从0→width/2-radius),右边往左靠(让right从mWidth→mWidth-2*transX)
2.定义一个变化因子,作为左边向中间靠的因子
//定义中间靠拢的动画因子
private var transX = 0f
3.修改一下前面绘制圆角矩形的参数。因为往中间靠的时候,可绘制视图也在不断变化,所有右侧的距离就为视图的宽度减去间距,右边的间距和左边的间距一样,所以就为mWidth-transX
canvas?.drawRoundRect(
transX, 0f, mWidth-transX,mHeight,
cornerRadius, cornerRadius, mRectPaint
)
canvas?.drawRoundRect(
transX, 0f, progress * mWidth-transX, mHeight,
cornerRadius, cornerRadius, mRectPaint
)
4.实现向中间靠拢的动画,由于半径 = mHeight/2。所以transX由0→(mWidth-mHeight)/2)
val moveToCenterAnim= ValueAnimator.ofFloat(0f,(mWidth-mHeight)/2).apply {
duration = 1000
addUpdateListener {anim->
transX= anim.animatedValue as Float
invalidate()
}
}
5.然后让这两个动画先后运行起来
AnimatorSet().apply {
playSequentially(changeIntoCircle,moveToCenterAnim)
start()
}
image.png6.最后得到如下效果
勾勾的模型七、绘制勾勾或者叉叉
1.在绘制勾勾之前,我们要先建模,确定绘制区域的宽高以及勾勾的坐标。在下图中,我们以中心点的坐标为cx,cy。
勾勾细化然后我们再把勾勾所在的矩形区域进行划分。我们令这个勾勾所在的正方形的边长为gWidth,那么我们就可以写除各个点的坐标
x1(cx - gWidth/2,cy)
x2(cx - gWidth/8,cy+gWidth/2)
x3(cx + gWidth/2,cy - gWidth/4)
叉叉2.绘制勾勾相当于画两条线,只要能确定起点与终点的坐标即可,也就是三个顶点坐标。
3.下面来确定叉叉的四个坐标
x1(cx -gWidth/4,cy -gWidth/4)
x2(cx -gWidth/4,cy +gWidth/4)
x3(cx +gWidth/4,cy -gWidth/4)
x4(cx +gWidth/4,cy +gWidth/4)
4.当矩形完全向中间靠拢聚成一个圆之后,我们才开始绘制勾勾或者叉叉。那么就要来记录一下加载成功还是失败。先定义两个静态变量来记录成功和失败。
companion object{
const val ON = 0
const val OFF = 1
const val SUCCESS = 2
const val FAILURE = 3
}
5.然后用一个变量来记录下载的结果
var resultStatus = SUCCESS
6.下载的结果一般由外部返回给我们,由于我们前面是用动画模拟了一下下载过程,所以不是真的下载,那我们在外部也就自己设置一下返回结果。
mProgress.resultStatus = ProgressView.SUCCESS
7.在绘制之前,我们要提供一下绘制的画笔,并用一个变量来记录绘制的路径,还要一个变量来记录绘制勾勾的矩形边长
//勾勾叉叉的画笔
private val markPaint = Paint().apply {
color=Color.WHITE
strokeWidth = 10f
style = Paint.Style.STROKE
}
//绘制勾勾或者叉叉的路径Path
private var markPath = Path()
private var markSize = 0f
8.在onSizeChanged方法里面,可以确定中心点的坐标以及勾勾或者叉叉的矩形边长。绘制勾勾叉叉可以在onSizeChanged方法里面进行。
markSize = height/3f
//中心点坐标
cx = width/2f
cy = height/2f
if(resultStatus== SUCCESS ){
//绘制勾勾
markPath.apply {
//markPath有相应的函数可以移动点的位置,然后再画线
moveTo(cx-markSize/2,cy)
lineTo(cx-markSize/8,cy+markSize/2)
lineTo(cx+markSize/2,cy-markSize/4)
}
}else{
//绘制叉叉
markPath.apply {
moveTo(cx-markSize/2,cy-markSize/2)
lineTo(cx+markSize/2,cy+markSize/2)
moveTo(cx-markSize/2,cy+markSize/2)
lineTo(cx+markSize/2,cy-markSize/2)
}
}
9.在onDraw方法里面,先判断圆角矩形是否已经聚拢成了一个圆,如果是的话再绘制叉叉或勾勾
if( transX ==( width - height)/2f){
canvas?.drawPath(markPath,markPaint)
}
结果10.运行程序之后,可以得到如下效果
八、实现裁剪效果
1.如果仅仅只是绘制还不行,因为我们还需要一个展开的效果,让这个勾勾或者叉叉缓慢出现,而不是突然出现。
2.想要实现这样的效果,我们可以绘制一个遮罩层,也就是一个矩形区域,所以我们用一个变量来记录该矩形区域
private var coverRect = Rect()
3.在onDraw方法里面绘制遮罩层,遮罩层的画笔和背景一样
coverRect.set((cx-markSize/2).toInt(),
(cy-markSize/2).toInt(),
(cx+markSize/2).toInt(),
(cy+markSize/2).toInt()
)
canvas?.drawRect(coverRect,mRectPaint)
4.把遮罩层添加好了之后,我们只需要让遮罩层不断向右边移动,就有逐渐出现的动画效果了。所以我们要定义一个动画因子,然后把它加入到遮罩层的横坐标里面
private var clipWidth = 0
coverRect.set((cx-markSize/2).toInt()+clipWidth.toInt(),
(cy-markSize/2).toInt(),
(cx+markSize/2).toInt(),
(cy+markSize/2).toInt()
)
5.我们再添加一个裁剪动画
val clipAnim = ValueAnimator.ofFloat(0f,markSize+20).apply {
duration = 1000
addUpdateListener {anim->
clipWidth= anim.animatedValue as Float
invalidate()
}
}
6.最后,把它添加到AnimatorSet里面,让它们三个先后运行
AnimatorSet().apply {
playSequentially(changeIntoCircle,moveToCenterAnim,clipAnim)
start()
}
7.运行之后,我们发现绘制的时候多出来了一点,主要是因为我们忽略了画笔的宽度,所以会露一点出来。那我们绘制遮罩层时就得加上画笔的宽度10
coverRect.set(
((cx-markSize/2).toInt()+clipWidth).toInt(),
(cy-markSize/2).toInt(),
(cx+markSize/2).toInt()+10,
(cy+markSize/2).toInt()+10
)
canvas?.drawRect(coverRect,mRectPaint)