美文网首页
自定义View-webview进度条(指示器)

自定义View-webview进度条(指示器)

作者: 爱学习的猫叔 | 来源:发表于2024-06-13 08:36 被阅读0次

    效果展示

    Screen_recording_20240614_082748.gif

    代码细节

    主要逻辑在startAnim里,执行了几次动画效果,感兴趣的话需要研究下动画细节

    class WebIndicator @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr) {
    
        /**
         * 进度条颜色
         */
        @ColorInt
        private var mColor = ResourcesCompat.getColor(resources, R.color.color_1aad19, null)
    
        /**
         * 进度条的画笔
         */
        private val mPaint = Paint().apply {
            isAntiAlias = true
            color = mColor
            isDither = true
            strokeCap = Paint.Cap.SQUARE
        }
    
        /**
         * 进度条动画
         */
        private var mAnimator: Animator? = null
    
        /**
         * 控件的宽度
         */
        private var mTargetWidth = 0
    
        /**
         * 当前匀速动画最大的时长
         */
        private var mCurrentMaxUniformSpeedDuration = MAX_UNIFORM_SPEED_DURATION
    
        /**
         * 当前加速后减速动画最大时长
         */
        private var mCurrentMaxDecelerateSpeedDuration = MAX_DECELERATE_SPEED_DURATION
    
        /**
         * 结束动画时长
         */
        private var mCurrentDoEndAnimationDuration = DO_END_ANIMATION_DURATION
    
        /**
         * 当前进度条的状态
         */
        private var indicatorStatus = 0
        private var mCurrentProgress = 0f
    
        /**
         * 默认的高度
         */
        var mWebIndicatorDefaultHeight: Int = 3
    
        init {
            val ta = context.obtainStyledAttributes(attrs, R.styleable.WebIndicator)
            mColor = ta.getColor(R.styleable.WebIndicator_web_indicator_color, mColor)
            mPaint.color = mColor
            ta.recycle()
            mTargetWidth = context.resources.displayMetrics.widthPixels
            mWebIndicatorDefaultHeight = dp2px(3)
        }
    
        fun setColor(color: Int) {
            this.mColor = color
            mPaint.color = color
        }
    
        fun setColor(color: String?) {
            this.setColor(Color.parseColor(color))
        }
    
        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            val wMode = MeasureSpec.getMode(widthMeasureSpec)
            var w = MeasureSpec.getSize(widthMeasureSpec)
            val hMode = MeasureSpec.getMode(heightMeasureSpec)
            var h = MeasureSpec.getSize(heightMeasureSpec)
    
            if (wMode == MeasureSpec.AT_MOST) {
                w = min(w.toDouble(), context.resources.displayMetrics.widthPixels.toDouble()).toInt()
            }
            if (hMode == MeasureSpec.AT_MOST) {
                h = mWebIndicatorDefaultHeight
            }
            this.setMeasuredDimension(w, h)
        }
    
        override fun dispatchDraw(canvas: Canvas) {
            canvas.drawRect(0f, 0f, mCurrentProgress / 100 * this.width, this.height.toFloat(), mPaint)
        }
    
        fun show() {
            if (visibility == GONE) {
                this.visibility = VISIBLE
                mCurrentProgress = 0f
                this.alpha = 1.0f
                startAnim(false)
            }
        }
    
        fun showForTest() {
            this.visibility = VISIBLE
            mCurrentProgress = 0f
            this.alpha = 1.0f
            startAnim(false)
        }
    
        override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
            super.onSizeChanged(w, h, oldw, oldh)
            this.mTargetWidth = measuredWidth
            val screenWidth = context.resources.displayMetrics.widthPixels
            if (mTargetWidth >= screenWidth) {
                mCurrentMaxDecelerateSpeedDuration = MAX_DECELERATE_SPEED_DURATION
                mCurrentMaxUniformSpeedDuration = MAX_UNIFORM_SPEED_DURATION
                mCurrentDoEndAnimationDuration = MAX_DECELERATE_SPEED_DURATION
            } else {
                //取比值
                val rate = (mTargetWidth / screenWidth).toFloat()
                mCurrentMaxUniformSpeedDuration = (MAX_UNIFORM_SPEED_DURATION * rate).toInt()
                mCurrentMaxDecelerateSpeedDuration = (MAX_DECELERATE_SPEED_DURATION * rate).toInt()
                mCurrentDoEndAnimationDuration = (DO_END_ANIMATION_DURATION * rate).toInt()
            }
        }
    
        fun setProgress(progress: Float) {
            if (visibility == GONE) {
                visibility = VISIBLE
            }
            if (progress < 95f) {
                return
            }
            if (indicatorStatus != FINISH) {
                startAnim(true)
            }
        }
    
        fun hide() {
            indicatorStatus = FINISH
        }
    
        private fun startAnim(isFinished: Boolean) {
            val v = (if (isFinished) 100 else 95).toFloat()
            if (mAnimator?.isStarted == true) {
                mAnimator?.cancel()
            }
            mCurrentProgress = if (mCurrentProgress == 0f) 0.00000001f else mCurrentProgress
            if (!isFinished) {
                AnimatorSet().apply {
                    val p1 = v * 0.60f
                    val p2 = v
                    val animator0 = ValueAnimator.ofFloat(mCurrentProgress, p1)
                    val animator1 = ValueAnimator.ofFloat(p1, p2)
                    val residue = 1f - mCurrentProgress / 100 - 0.05f
                    val duration = (residue * mCurrentMaxUniformSpeedDuration).toLong()
                    //前半段40%+后半段60%
                    val duration4 = (duration * 0.4f).toLong()
                    val duration6 = (duration * 0.6f).toLong()
                    animator0.interpolator = LinearInterpolator()
                    animator0.setDuration(duration4)
                    animator0.addUpdateListener(mAnimatorUpdateListener)
    
                    animator1.interpolator = LinearInterpolator()
                    animator1.setDuration(duration6)
                    animator1.addUpdateListener(mAnimatorUpdateListener)
                    play(animator1).after(animator0)
                    start()
                    this@WebIndicator.mAnimator = this
                }
            } else {
                var segment95Animator: ValueAnimator? = null
                if (mCurrentProgress < 95f) {
                    segment95Animator = ValueAnimator.ofFloat(mCurrentProgress, 95f)
                    val residue = 1f - mCurrentProgress / 100f - 0.05f
                    segment95Animator.setDuration((residue * mCurrentMaxDecelerateSpeedDuration).toLong())
                    segment95Animator.interpolator = DecelerateInterpolator()
                    segment95Animator.addUpdateListener(mAnimatorUpdateListener)
                }
                val alphaAnimator = ObjectAnimator.ofFloat(this, "alpha", 1f, 0f)
                alphaAnimator.setDuration(mCurrentDoEndAnimationDuration.toLong())
                val valueAnimatorEnd = ValueAnimator.ofFloat(95f, 100f)
                valueAnimatorEnd.setDuration(mCurrentDoEndAnimationDuration.toLong())
                valueAnimatorEnd.addUpdateListener(mAnimatorUpdateListener)
                var animatorSet = AnimatorSet()
                animatorSet.playTogether(alphaAnimator, valueAnimatorEnd)
                if (segment95Animator != null) {
                    val animatorSet0 = AnimatorSet()
                    animatorSet0.play(animatorSet).after(segment95Animator)
                    animatorSet = animatorSet0
                }
                animatorSet.addListener(mAnimatorListenerAdapter)
                animatorSet.start()
                mAnimator = animatorSet
            }
            indicatorStatus = STARTED
        }
    
        private val mAnimatorUpdateListener = AnimatorUpdateListener { animation ->
            val t = animation.animatedValue as Float
            this@WebIndicator.mCurrentProgress = t
            this@WebIndicator.invalidate()
        }
    
        private val mAnimatorListenerAdapter: AnimatorListenerAdapter = object : AnimatorListenerAdapter() {
            override fun onAnimationEnd(animation: Animator) {
                doEnd()
            }
        }
    
        override fun onDetachedFromWindow() {
            super.onDetachedFromWindow()
            /**
             * animator cause leak , if not cancel;
             */
            if (mAnimator?.isStarted == true) {
                mAnimator?.cancel()
                mAnimator = null
            }
        }
    
        private fun doEnd() {
            if (indicatorStatus == FINISH && mCurrentProgress == 100f) {
                visibility = GONE
                mCurrentProgress = 0f
                //动画执行结束一定要将透明度还原,否则后面的draw操作看不到东西
                this.alpha = 1f
            }
            indicatorStatus = UN_START
        }
    
        fun reset() {
            mCurrentProgress = 0f
            if (mAnimator?.isStarted == true) {
                mAnimator?.cancel()
            }
        }
    
        fun setProgress(newProgress: Int) {
            setProgress(newProgress.toFloat())
        }
    
        fun dp2px(dip: Int): Int {
            return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip.toFloat(), context.resources.displayMetrics).toInt()
        }
    
        companion object {
            /**
             * 默认匀速动画最大的时长
             */
            const val MAX_UNIFORM_SPEED_DURATION: Int = 8 * 1000
    
            /**
             * 默认加速后减速动画最大时长
             */
            const val MAX_DECELERATE_SPEED_DURATION: Int = 450
    
            /**
             * 结束动画时长 , Fade out 。
             */
            const val DO_END_ANIMATION_DURATION: Int = 600
            const val UN_START: Int = 0
            const val STARTED: Int = 1
            const val FINISH: Int = 2
        }
    }
    

    源码地址

    https://github.com/treech/MyView

    相关文章

      网友评论

          本文标题:自定义View-webview进度条(指示器)

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