Android日志:自定义炫酷进度

作者: 搬码人 | 来源:发表于2021-08-28 15:04 被阅读0次

    前几期涉及到了自定义View以及属性动画的使用,这次利用一个模拟网络下载的自定义炫酷进度对自定义View和动画进行回顾与深度学习。

    Demo效果

    Demo效果

    Demo实现目录

    1、绘制一个圆角矩形区域
    2、进度变化
    3、两端缩成一个半圆形
    4、两端向中间靠拢 形成一个完整的圆
    5、绘制勾勾 或者 叉叉
    6、实现展开效果

    绘制圆角矩形区域

    /**
     *@Description
     *@Author PC
     *@QQ 1578684787
     */
    class MyProgress:View {
    
        private var mWidth = 0f
        private var mHeight = 0f
        //矩形的圆角
        private var cornerRadius = 0f
        //矩形的画笔
        private var mRectPaint = Paint().apply {
            style = Paint.Style.FILL
            color = Color.MAGENTA
        }
        //代码创建这个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)
            mWidth = width.toFloat()
            mHeight = height.toFloat()
    
    }
        override fun onDraw(canvas: Canvas?) {
            //绘制矩形区域
            mRectPaint.color = Color.MAGENTA
            canvas?.drawRoundRect(transx,0f,mWidth-transx,mHeight,cornerRadius,cornerRadius,mRectPaint)
    
        }
        private fun startFinishAnim(){
            //两端变成半圆形
            val changeToHalfCircle = ValueAnimator.ofFloat(0f,mHeight/2).apply {
                duration = 1000
                addUpdateListener {
                    cornerRadius = it.animatedValue as Float
                    invalidate()
                }
            }
          
        }
    }
    

    startFinishAnim()方法存放一堆属性动画,因为本次Demo将用到很多属性动画且这个类中需要定义大部分属性动画。

    进度变化

    这里模仿网络下载进度变化,所以由外部(这里是MainActivity)触发动画进度
    使用高阶函数CallBack进行下载状态的回调
    以静态变量的方式(放在companion object中)模拟外部传递是否下载成功的信息以及正在下载还是暂停下载的信息

      //回调事件
        var mCallBack:((Int)->Unit) ?= null
    //进度动画因子
     var progress = 40f
        set(value) {
            //记录外部传递过来的值
            field = value
            //判断下载是否完毕
            if (value == 1.0f){
                //下载完毕
                startFinishAnim()
            }
            //重新绘制
            invalidate()
        }
    //定义状态
        companion object{
            const val ON = 0
            const val OFF = 1
            const val SUCCESS = 2
            const val FAILURE = 3
        }
        //记录当前的状态
        private var mState = ON
        //记录下载的结果
        var resultStatus = SUCCESS
    

    设置触摸事件触发进度动画因子的回调

     override fun onTouchEvent(event: MotionEvent?): Boolean {
            if (event?.action == MotionEvent.ACTION_DOWN){
                //将当前的点击事件传递给外部
                mCallBack?.let{
                    //将当前状态传递过去
                    it(mState)
                    //更改状态值
                    mState = if (mState == ON) OFF else ON
                }
            }
            return true
        }
    
    image.png

    MainActivity中
    接受下载状态的额回调
    并将progress动画因子的属性动画设置到MainActivity中模拟下载数据

      downLoadData()
            //监听控键的点击事件
            mbinding.mProgress.mCallBack = { state->
                if (state == MyProgress.ON){
                    //下载
                   if (mDownloadAnim!!.isPaused){
                       mDownloadAnim?.resume()
                   }else{
                       mDownloadAnim?.start()
                   }
                }else{
                    //暂停
                    mDownloadAnim?.pause()
                }
            }
        }
        //下载数据
        private fun downLoadData(){
            //属性动画模拟下载数据
            mDownloadAnim =  ValueAnimator.ofFloat(0f,1.0f).apply {
                duration = 2000
                addUpdateListener {
                    //将当前的进度传递给自定义View
                    mbinding.mProgress.progress = it.animatedValue as Float
                }
            }
        }
    

    可以看到progress属性动画的刷新在MyProgress类中也就是继承于View的类,目的是每接收外部传来的数据都会进行重新绘制,实现进度变化。

    progress

    两端缩成一个半圆

    //矩形的圆角
        private var cornerRadius = 0f
    
     private fun startFinishAnim(){
            //两端变成半圆形
            val changeToHalfCircle = ValueAnimator.ofFloat(0f,mHeight/2).apply {
                duration = 1000
                addUpdateListener {
                    cornerRadius = it.animatedValue as Float
                    invalidate()
                }
            }
    

    进度条和背景举行都需要进行动画变形。

    image.png

    两端向中间靠拢 形成一个完整的圆

    这就要设计到前面所提到的动画因子transX

    //向中间靠拢的变化因子
        private var transx = 0f
    

    startFinishAnim()方法中

    val sideToCenterAnim = ValueAnimator.ofFloat(0f,mWidth/2 - mHeight/2).apply {
                duration = 1000
                addUpdateListener {
                        transx = it.animatedValue as Float
                        invalidate()
                }
            }
    

    绘制勾勾 或者 叉叉

    显示下载成功或失败的图案

     //勾勾或叉叉的Paint
        private val resultPaint = Paint().apply {
            color = Color.WHITE
            style = Paint.Style.STROKE
            strokeWidth =5f
        }
        private var cx = 0f
        private var cy = 0f
        private var markWidth = 0f
        //path
        private val resultPath = Path()
    

    这里涉及使用kotlin中的with()方法:当某个方法中需要反复对某个对象进行操作,最好使用with方法。比如这里进行勾勾和叉叉的绘制就要反复调用resultPath对象就可以使用with()方法进行简化操作。
    这里只是在SizeChanged中进行测量,真正的绘制在onDraw中

      //绘制勾勾或者叉叉
            markWidth = mHeight/3
             cx = mWidth/2
             cy = mHeight/2
            with(resultPath) {
                if (resultStatus == SUCCESS) {
                    //勾勾
                    //起点
                    moveTo(cx - markWidth / 2, cy)
                    //拐点
                    lineTo(cx - markWidth / 8, cy + markWidth / 2)
                    //终点
                    lineTo(cx + markWidth / 2, cy - markWidth / 4)
                } else {
                    //叉叉
                    //第一条
                    moveTo(cx-markWidth/2,cy-markWidth/2)
                    lineTo(cx+markWidth/2,cy+markWidth/2)
                    //第二条
                    moveTo(cx-markWidth/2,cy+markWidth/2)
                    lineTo(cx+markWidth/2,cy-markWidth/2)
                }
            }
    
    override fun onDraw(canvas: Canvas?) {
            //绘制矩形区域
            mRectPaint.color = Color.MAGENTA
            canvas?.drawRoundRect(transx,0f,mWidth-transx,mHeight,cornerRadius,cornerRadius,mRectPaint)
            //绘制进度矩形区域
            mRectPaint.color = Color.CYAN
            canvas?.drawRoundRect(transx,0f,progress*mWidth-transx,mHeight,cornerRadius,cornerRadius,mRectPaint)
    
            //判断是否靠拢了 在中心形成圆
            if (transx == mWidth/2-mHeight/2){
                //绘制勾勾或叉叉
                canvas?.drawPath(resultPath,resultPaint)
            }
    
        }
    

    实现展开效果

    使勾勾或者叉叉 有较缓慢的从左网络出现的效果
    思路:在图案编程加上遮罩层,设置遮罩层的属性动画,当进度条在中心形成圆的时候遮罩层向右移动露出图案。

     //遮罩层的动画因子
        private var clipWidth = 0f
    
     override fun onDraw(canvas: Canvas?) {
            //绘制矩形区域
            mRectPaint.color = Color.MAGENTA
            canvas?.drawRoundRect(transx,0f,mWidth-transx,mHeight,cornerRadius,cornerRadius,mRectPaint)
            //绘制进度矩形区域
            mRectPaint.color = Color.CYAN
            canvas?.drawRoundRect(transx,0f,progress*mWidth-transx,mHeight,cornerRadius,cornerRadius,mRectPaint)
    
            //判断是否靠拢了 在中心形成圆
            if (transx == mWidth/2-mHeight/2){
                //绘制勾勾或叉叉
                canvas?.drawPath(resultPath,resultPaint)
    
                //绘制遮罩层
                //确定遮罩层的矩形区域
                coverRect.set(
                        ((cx-markWidth/2).toInt()+clipWidth).toInt(),
                        (cy-markWidth/2).toInt(),
                        (cx+markWidth/2).toInt()+8,
                        (cy+markWidth/2).toInt()+8
                )
                canvas?.drawRect(coverRect,mRectPaint)
            }
    
        }
    
    private fun startFinishAnim(){
            //两端变成半圆形
            val changeToHalfCircle = ValueAnimator.ofFloat(0f,mHeight/2).apply {
                duration = 1000
                addUpdateListener {
                    cornerRadius = it.animatedValue as Float
                    invalidate()
                }
            }
            val sideToCenterAnim = ValueAnimator.ofFloat(0f,mWidth/2 - mHeight/2).apply {
                duration = 1000
                addUpdateListener {
                        transx = it.animatedValue as Float
                        invalidate()
                }
            }
            val clipAnim = ValueAnimator.ofFloat(0f,markWidth).apply {
                duration = 1000
                addUpdateListener {
                        clipWidth = it.animatedValue as Float
                        invalidate()
                }
            }
            //设置动画先后顺序
            AnimatorSet().apply {
                playSequentially(changeToHalfCircle,sideToCenterAnim,clipAnim)
                start()
            }
        }
    

    Animator()设置动画启动的先后顺序,动画统一在这启动start()。

    代码总览

    package com.example.falltype
    
    import android.animation.AnimatorSet
    import android.animation.ValueAnimator
    import android.content.Context
    import android.graphics.*
    import android.util.AttributeSet
    import android.view.MotionEvent
    import android.view.View
    
    /**
     *@Description
     *@Author PC
     *@QQ 1578684787
     */
    class MyProgress:View {
    
        private var mWidth = 0f
        private var mHeight = 0f
        //矩形的圆角
        private var cornerRadius = 0f
        //矩形的画笔
        private var mRectPaint = Paint().apply {
            style = Paint.Style.FILL
            color = Color.MAGENTA
        }
        //勾勾或叉叉的Paint
        private val resultPaint = Paint().apply {
            color = Color.WHITE
            style = Paint.Style.STROKE
            strokeWidth =5f
        }
        private var cx = 0f
        private var cy = 0f
        private var markWidth = 0f
        //path
        private val resultPath = Path()
        //遮罩的矩形区域
        private var coverRect = Rect()
        //进程变化因子
        var progress = 40f
        set(value) {
            //记录外部传递过来的值
            field = value
            //判断下载是否完毕
            if (value == 1.0f){
                //下载完毕
                startFinishAnim()
            }
            //重新绘制
            invalidate()
        }
        //遮罩层的动画因子
        private var clipWidth = 0f
        //定义状态
        companion object{
            const val ON = 0
            const val OFF = 1
            const val SUCCESS = 2
            const val FAILURE = 3
        }
        //记录当前的状态
        private var mState = ON
        //记录下载的结果
        var resultStatus = SUCCESS
    
        //回调事件
        var mCallBack:((Int)->Unit) ?= null
    
        //向中间靠拢的变化因子
        private var transx = 0f
        //代码创建这个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)
            mWidth = width.toFloat()
            mHeight = height.toFloat()
    
            //绘制勾勾或者叉叉
            markWidth = mHeight/3
             cx = mWidth/2
             cy = mHeight/2
            with(resultPath) {
                if (resultStatus == SUCCESS) {
                    //勾勾
                    //起点
                    moveTo(cx - markWidth / 2, cy)
                    //拐点
                    lineTo(cx - markWidth / 8, cy + markWidth / 2)
                    //终点
                    lineTo(cx + markWidth / 2, cy - markWidth / 4)
                } else {
                    //叉叉
                    //第一条
                    moveTo(cx-markWidth/2,cy-markWidth/2)
                    lineTo(cx+markWidth/2,cy+markWidth/2)
                    //第二条
                    moveTo(cx-markWidth/2,cy+markWidth/2)
                    lineTo(cx+markWidth/2,cy-markWidth/2)
                }
            }
        }
    
        override fun onDraw(canvas: Canvas?) {
            //绘制矩形区域
            mRectPaint.color = Color.MAGENTA
            canvas?.drawRoundRect(transx,0f,mWidth-transx,mHeight,cornerRadius,cornerRadius,mRectPaint)
            //绘制进度矩形区域
            mRectPaint.color = Color.CYAN
            canvas?.drawRoundRect(transx,0f,progress*mWidth-transx,mHeight,cornerRadius,cornerRadius,mRectPaint)
    
            //判断是否靠拢了 在中心形成圆
            if (transx == mWidth/2-mHeight/2){
                //绘制勾勾或叉叉
                canvas?.drawPath(resultPath,resultPaint)
    
                //绘制遮罩层
                //确定遮罩层的矩形区域
                coverRect.set(
                        ((cx-markWidth/2).toInt()+clipWidth).toInt(),
                        (cy-markWidth/2).toInt(),
                        (cx+markWidth/2).toInt()+8,
                        (cy+markWidth/2).toInt()+8
                )
                canvas?.drawRect(coverRect,mRectPaint)
            }
    
        }
    
        override fun onTouchEvent(event: MotionEvent?): Boolean {
            if (event?.action == MotionEvent.ACTION_DOWN){
                //将当前的点击事件传递给外部
                mCallBack?.let{
                    //将当前状态传递过去
                    it(mState)
                    //更改状态值
                    mState = if (mState == ON) OFF else ON
                }
            }
            return true
        }
        private fun startFinishAnim(){
            //两端变成半圆形
            val changeToHalfCircle = ValueAnimator.ofFloat(0f,mHeight/2).apply {
                duration = 1000
                addUpdateListener {
                    cornerRadius = it.animatedValue as Float
                    invalidate()
                }
            }
            val sideToCenterAnim = ValueAnimator.ofFloat(0f,mWidth/2 - mHeight/2).apply {
                duration = 1000
                addUpdateListener {
                        transx = it.animatedValue as Float
                        invalidate()
                }
            }
            val clipAnim = ValueAnimator.ofFloat(0f,markWidth).apply {
                duration = 1000
                addUpdateListener {
                        clipWidth = it.animatedValue as Float
                        invalidate()
                }
            }
            //设置动画先后顺序
            AnimatorSet().apply {
                playSequentially(changeToHalfCircle,sideToCenterAnim,clipAnim)
                start()
            }
        }
    }
    

    MainActivity

    package com.example.falltype
    
    import android.animation.ValueAnimator
    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import android.renderscript.Sampler
    import com.example.falltype.databinding.ActivityMainBinding
    
    class MainActivity : AppCompatActivity() {
        lateinit var mbinding:ActivityMainBinding
    
        private var mDownloadAnim:ValueAnimator?=null
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            mbinding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(mbinding.root)
    
            mbinding.mProgress.resultStatus = MyProgress.SUCCESS
            downLoadData()
            //监听控键的点击事件
            mbinding.mProgress.mCallBack = { state->
                if (state == MyProgress.ON){
                    //下载
                   if (mDownloadAnim!!.isPaused){
                       mDownloadAnim?.resume()
                   }else{
                       mDownloadAnim?.start()
                   }
                }else{
                    //暂停
                    mDownloadAnim?.pause()
                }
            }
        }
        //下载数据
        private fun downLoadData(){
            //属性动画模拟下载数据
            mDownloadAnim =  ValueAnimator.ofFloat(0f,1.0f).apply {
                duration = 2000
                addUpdateListener {
                    //将当前的进度传递给自定义View
                    mbinding.mProgress.progress = it.animatedValue as Float
                }
            }
        }
    }
    

    相关文章

      网友评论

        本文标题:Android日志:自定义炫酷进度

        本文链接:https://www.haomeiwen.com/subject/sikmiltx.html