美文网首页
模仿途虎的登录进度条——带节点进度条

模仿途虎的登录进度条——带节点进度条

作者: ifadai | 来源:发表于2020-03-04 16:57 被阅读0次

    去年写的,一直忘了发,这几天发一下。

    前段时间,项目中使用了阿里的号码认证服务(一键登录),登录样式模仿了途虎养车app的登录样式,于是照猫画虎写了个带节点的进度条。

    途虎登录进度条 模仿效果

    使用

     <android.support.constraint.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
    
                <com.fadai.nodeprogress.NodeProgressBar
                    android:id="@+id/npb"
                    android:layout_width="match_parent"
                    android:layout_height="148dp"
                    app:np_backgroundBarColor="#FFCCCCCC"
                    app:np_circleWidth="20dp"
                    app:np_progressColor="#FFFF0000"
                    app:np_circleAnimDuration="600"
                    app:np_lineAnimDuration="200"
                    app:np_circleContentAnimDuration="400"
                    app:np_progressHeight="2dp"/>
            </android.support.constraint.ConstraintLayout>
    
    // 设置节点数
     npb.setCount(3)
     // 回调事件
     npb.progressListener = object : NodeProgressBar.OnProgressListener {
                override fun onRequestScuccess(index: Int) {
                    showToast("请求成功 $index")
                }
    
                override fun onRequestFailure(index: Int) {
                    showToast("请求失败 $index")
                }
    
                override fun onComplete() {
                    showToast("完成")
                }
            }
    // 开始动画
     npb.start()
     // 第一个耗时请求成功
     npb.setRequestStatus(true, 0)
     // 第二个耗时请求成功
     npb.setRequestStatus(true, 1)
     // 第三个耗时请求失败
     npb.setRequestStatus(true, 3)
    

    自定义属性:

            <!--圆圈宽度-->
            <attr name="np_circleWidth" format="dimension" />
            <!--背景条颜色-->
            <attr name="np_backgroundBarColor" format="color" />
            <!--进度条高度-->
            <attr name="np_progressHeight" format="dimension" />
            <!--进度条颜色-->
            <attr name="np_progressColor" format="color" />
            <!--每条横线的动画时间-->
            <attr name="np_lineAnimDuration" format="integer" />
            <!--每个圆圈的动画时间-->
            <attr name="np_circleAnimDuration" format="integer" />
            <!--圆圈内容的动画时间-->
            <attr name="np_circleContentAnimDuration" format="integer" />
    
    

    开发前

    途虎登录进度条

    第一眼看到途虎的这个效果图,想法就是两个节点代表两个耗时请求:请求A,请求B;

    1. 请求A开始执行,同时动画开始执行;
    2. 动画一直走,直到走到第一个圆圈;
    3. 当第一个圆圈走完一圈之后,判断请求A是否仍是请求中,如果是,继续转圈;
    4. 如果不是,判断时请求成功的话,动画绘制第一个圆圈内的对号,然后开始执行请求B,同时动画继续走后面的流程;
    5. 如果判断请求A失败的话,则动画绘制第一个圆圈内的叉号,叉号绘制完毕后,回调请求失败事件,结束。
    6. 以此类推,请求B也是一样,直到动画走完所有流程,执行完成事件的回调。结束。

    emmm,整个流程并不麻烦,这个主要是动画的绘制,我这里把动画两个节点+最后一条线。

    两个节点+最后一段线

    红色代表第一节点,紫色代表第二节点,绿色是所有请求成功后走的最后一段线。

    而每一个节点可以分为横线阶段、圆圈阶段、圈内内容阶段(对号或者叉号)。

    以此类推,还可以有第三节点、第四节点...

    开发

    初始化Path

    我们可以将所有节点的横线、圆圈、对号、叉号存进list中,然后绘制到哪个节点的时候list.get(index)取出来即可

    var startY = height / 2F
            var startX = 0F
            // 每一节线的宽度=(总宽度-节点宽度*数量)/(节点数量+1)
            var progressWidth = (width - circleWidth * mCount) / (mCount + 1)
    
            // 移动到开始位置
            mBgPath?.moveTo(startX, startY)
    
            // 遍历所有节点
            for (i in 0 until mCount) {
    
                // 线
                var linePath = Path()
                linePath.moveTo(startX, startY)
                startX += progressWidth
                linePath?.lineTo(startX, startY)
                mBgPath?.lineTo(startX, startY)
    
                // 圈
                var ciclePath = Path()
                var radius = circleWidth / 2F
                var centerX1 = startX + (radius)
                var centerY1 = height / 2F
                var rectfCircle1 = RectF(startX, centerY1 - radius, startX + circleWidth, centerY1 + radius)
                ciclePath?.addArc(rectfCircle1, 180F, 359.9F)
                mBgPath?.addCircle(centerX1, centerY1, radius, Path.Direction.CW)
    
                // 圆圈内容 对号
                var contentTruePath = Path()
                var startContentX1 = centerX1 - circleContentWidth / 2
                var startContentY1 = centerY1 - circleContentWidth / 2
                var contentPoint11 = PointF(startContentX1, startContentY1 + circleContentWidth / 2)
                var contentPoint12 = PointF(startContentX1 + circleContentWidth / 2, startContentY1 + circleContentWidth)
                var contentPoint13 = PointF(startContentX1 + circleContentWidth, startContentY1)
                contentTruePath?.moveTo(contentPoint11.x, contentPoint11.y)
                contentTruePath?.lineTo(contentPoint12.x, contentPoint12.y)
                contentTruePath?.lineTo(contentPoint13.x, contentPoint13.y)
    
                // 圆圈内容 叉号
                var contentFalsePath = Path()
                var contentPoint14 = PointF(startContentX1, startContentY1)
                var contentPoint15 = PointF(startContentX1 + circleContentWidth, startContentY1 + circleContentWidth)
                var contentPoint16 = PointF(startContentX1 + circleContentWidth, startContentY1)
                var contentPoint17 = PointF(startContentX1, startContentY1 + circleContentWidth)
                contentFalsePath?.moveTo(contentPoint14.x, contentPoint14.y)
                contentFalsePath?.lineTo(contentPoint15.x, contentPoint15.y)
                contentFalsePath?.moveTo(contentPoint16.x, contentPoint16.y)
                contentFalsePath?.lineTo(contentPoint17.x, contentPoint17.y)
    
                mLinePathList.add(linePath)
                mCirclePathList.add(ciclePath)
                mCircleContentTruePathList.add(contentTruePath)
                mCircleContentFalsePathList.add(contentFalsePath)
                mCircleContentPathList.add(contentFalsePath)
    
                startX += circleWidth
            }
    
            // 最后一段横线
            mLineEndPath?.moveTo(startX, startY)
            mBgPath?.moveTo(startX, startY)
            startX += progressWidth
            mLineEndPath?.lineTo(startX, startY)
            mBgPath?.lineTo(startX, startY)
    

    初始化动画

    和Path储存在list中一样,每个节点的不同阶段的动画,储存在list中

      for (i in 0 until mCount) {
                // 请求状态默认为请求中
                mRequestStatusList.add(REQUEST_STATUS_REQUESTING)
    
                // 横线动画
                var lineAnimator = ValueAnimator.ofFloat(0F, 1F).setDuration(lineProgressTime)
                lineAnimator?.addUpdateListener {
                    if (mStage == STAGE_LINE) {
                        var progress = MAX_PROGRESS * (it.getAnimatedValue() as Float)
                        mCurrentProgress = progress.toInt()
                        // 动画结束后,由横线阶段->圆圈阶段
                        if (mCurrentProgress == MAX_PROGRESS) {
                            mStage = STAGE_CIRCLE
                            onStatusChange()
                        }
                        postInvalidate()
                    }
                }
                mAnimatorLineList.add(lineAnimator)
    
                // 圆圈动画
                var circleAnimator = ValueAnimator.ofFloat(0F, 1F).setDuration(circleTime)
                // 无限循环
                circleAnimator?.repeatCount = ValueAnimator.INFINITE
                circleAnimator?.addUpdateListener {
                    if (mStage == STAGE_CIRCLE) {
                        var progress = MAX_PROGRESS * (it.getAnimatedValue() as Float)
                        mCurrentProgress = progress.toInt()
    
                        // 无限动画最后的进度可能不是max值
                        if (mCurrentProgress == MAX_PROGRESS || mCurrentProgress == MAX_PROGRESS - 1) {
                            // 动画一圈结束后,判断请求状态是否仍是请求中
                            if (mRequestStatusList[mNode] != REQUEST_STATUS_REQUESTING) {
                                // 不是请求中的话,则停止动画,开始圆圈内容动画
                                circleAnimator?.cancel()
                                mStage = STAGE_CIRCLE_CONTENT
                                onStatusChange()
                            }
                        }
                        postInvalidate()
                    }
                }
                mAnimatorCircleList.add(circleAnimator)
    
                // 圆圈内容动画
                var circleContentAnimator = ValueAnimator.ofFloat(0F, 1F).setDuration(circleContentTime)
                circleContentAnimator?.addUpdateListener {
                    if (mStage == STAGE_CIRCLE_CONTENT) {
                        var progress = MAX_PROGRESS * (it.getAnimatedValue() as Float)
                        mCurrentProgress = progress.toInt()
                        // 动画结束后
                        if (mCurrentProgress == MAX_PROGRESS) {
                            // 如果请求成功了,执行回调,进入下一个节点,再次进入横线阶段
                            if (mRequestStatusList[mNode] == REQUEST_STATUS_SUCCESS) {
                                progressListener?.onRequestScuccess(mNode)
                                mStage = STAGE_LINE
                                mNode++
                                onStatusChange()
                            } else {
                                // 请求失败了,状态更新为失败状态,执行回调
                                mStatus = STATUS_FAILURE
                                progressListener?.onRequestFailure(mNode)
                            }
    
                        }
                        postInvalidate()
                    }
                }
                mAnimatorCircleContentList.add(circleContentAnimator)
            }
    
            // 最后一段横线动画,单独处理 
            mAnimatorEnd = ValueAnimator.ofFloat(0F, 1F).setDuration(lineProgressTime)
            mAnimatorEnd?.addUpdateListener {
                if (mNode == mCount) { // 如果当前节点超过最后一个节点
                    var progress = MAX_PROGRESS * (it.getAnimatedValue() as Float)
                    mCurrentProgress = progress.toInt()
                    // 动画结束后,状态为完成状态,执行回调
                    if (mCurrentProgress == MAX_PROGRESS) {
                        mStatus = STATUS_COMPLE
                        progressListener?.onComplete()
                    }
                    postInvalidate()
                }
            }
    

    绘制进度

    遍历所有节点,绘制

                    for (i in 0..mNode) {
                        if (i == mCount) { // 所有阶段结束后的最后一条线
                            drawLastLine(canvas)
                        } else {// 正常阶段
                            if (i < mNode) { // 已经过去的阶段
                                drawPastNode(canvas, i)
                            } else if (i == mNode) { // 请求中的阶段
                                drawCurrentNode(i, canvas)
                            }
                        }
    
    

    绘制不同阶段的进度

     when (mStage) {
                STAGE_LINE -> {
                    mMeasure!!.setPath(mLinePathList[i], false)
                    var path = Path()
                    var start = 0F
                    var stop = mMeasure!!.length * (mCurrentProgress.toFloat() / MAX_PROGRESS)
                    mMeasure!!.getSegment(start, stop, path, true)
                    canvas.drawPath(path, mProgressPaint)
                }
                STAGE_CIRCLE -> {
                    canvas.drawPath(mLinePathList[i], mProgressPaint)
                    mMeasure!!.setPath(mCirclePathList[i], false)
                    var path = Path()
                    var start = 0F
                    var stop = mMeasure!!.length * (mCurrentProgress.toFloat() / MAX_PROGRESS)
                    mMeasure!!.getSegment(start, stop, path, true)
                    canvas.drawPath(path, mProgressPaint)
                }
                STAGE_CIRCLE_CONTENT -> {
                    canvas.drawPath(mLinePathList[i], mProgressPaint)
                    canvas.drawPath(mCirclePathList[i], mProgressPaint)
    
                    mMeasure!!.setPath(mCircleContentPathList[i], false)
                    var path = Path()
                    var start = 0F
                    when (mRequestStatusList[mNode]) {
                        REQUEST_STATUS_SUCCESS -> {
                            var stop = (mMeasure!!.length
                                    ?: 0F) * (mCurrentProgress.toFloat() / MAX_PROGRESS)
                            mMeasure!!.getSegment(start, stop, path, true)
                            canvas.drawPath(path, mProgressPaint)
                        }
                        REQUEST_STATUS_FAILURE -> {
                            if (mCurrentProgress > 50) {// 进度后50%时
                                mMeasure!!.getSegment(0F, mMeasure!!.length
                                        ?: 0F, path, true)
                                canvas.drawPath(path, mProgressPaint)
                                mMeasure!!.nextContour()
                                var stop = (mMeasure!!.length
                                        ?: 0F) * ((mCurrentProgress.toFloat() - 50) / (MAX_PROGRESS / 2))
                                mMeasure!!.getSegment(start, stop, path, true)
                                canvas.drawPath(path, mProgressPaint)
                            } else { // 进度前50%时,只绘制叉号的一条线
                                var stop = (mMeasure!!.length
                                        ?: 0F) * (mCurrentProgress.toFloat() / (MAX_PROGRESS / 2))
                                mMeasure!!.getSegment(start, stop, path, true)
                                canvas.drawPath(path, mProgressPaint)
                            }
                        }
                    }
                }
            }
    

    最后

    大概就是这样吧,纯粹贴代码也不好理解,想了解的话可以移步github:https://github.com/ifadai/NodeProgress,有问题欢迎提出

    相关文章

      网友评论

          本文标题:模仿途虎的登录进度条——带节点进度条

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