Android新建水平节点进度条示例

目录
  • 前言
    • 效果图
    • 圆圈和文字状态
    • 文字居中
    • 代码
    • 声明下style
    • 接着创建布局文件
    • 再Activity中使用它
    • mTextList数据集合

前言

效果图

前几天在网上没有找到合适的横向节点进度条,自己动手写了一个,先来看看效果图

圆圈和文字状态

我们看到小圆圈和文字有几种状态呢?

  • 第一个空心的小圆圈是处理完成的状态
  • 第二个实心的小圆圈是处理中的状态
  • 第三个实心的小圆圈是待处理的状态
    没错,我们看到了小圆圈和文字有三种处理状态

文字居中

我们写一个类继承自AppCompatTextView,通过onMeasure方法得到控件的宽高,通过Paint的getTextBounds()也可以知道文字的宽高,我们看到有5个节点需要处理,我们把屏幕划分成5个等份,每个等份都相等,这里用itemWidth 表示每个相同的等份。文字居中的写法很简单,

itemWidth / 2 - textWidth / 2

代码

package cn.wwj.customview.widget
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.text.TextPaint
import android.util.AttributeSet
import android.util.Log
import androidx.annotation.Nullable
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.content.ContextCompat
import androidx.core.view.marginTop
import cn.wwj.customview.R
import cn.wwj.customview.dp2px
import cn.wwj.customview.sp2px
/**
 * 节点进度条
 */
class NodePointProcessBar : AppCompatTextView {
    /**
     * 文字画笔
     */
    private lateinit var mTextPaint: TextPaint
    /**
     * 圆画笔
     */
    private lateinit var mCirclePaint: Paint
    private var isDebug = false
    /**
     * 已完成文字颜色
     */
    private var mCompleteTextColor: Int = ContextCompat.getColor(context, android.R.color.black)
    /**
     * 处理中文字颜色
     */
    private var mProcessTextColor: Int = ContextCompat.getColor(context, R.color.purple)
    /**
     * 待处理的文字颜色
     */
    private var mWaitProcessTextColor: Int = ContextCompat.getColor(context, R.color.gray_text)
    /**
     * 绘制的节点个数,由底部节点标题数量控制
     */
    private var mCircleCount = 0
    private var TAG = "NodePointProcessBar"
    /**
     * 圆的半径
     */
    private var mCircleRadius = 5f.dp2px()
    /**
     * 圆圈的边框线
     */
    private var mCircleBorder = 1f.dp2px()
    /**
     * 线的宽度
     */
    private var mLineWidth = 1f.dp2px()
    /**
     * 线之间的左右边距
     */
    private var mLineMargin = 4f.dp2px()
    /**
     * 文字和圆圈之间的距离
     */
    var mTextCircleMargin = 7f.dp2px()
    /**
     * 文字的水平边距
     */
    private var mTextLeftRightMargin = 8f.dp2px()
    /**
     * 计算内容的高度 和 宽度
     */
    private var mContentHeight = 0f
    private var mContentWidth = 0f
    /**
     * 节点底部的文字列表
     */
    private var mTextList: List<String> = mutableListOf()
    /**
     * 选中项集合
     */
    private var mProcessIndexSet: Set<Int> = mutableSetOf()
    /**
     * 文字同宽高的矩形,用来测量文字
     */
    private var mTextBoundList: MutableList<Rect> = mutableListOf()
    /**
     * 计算文字宽高的矩形
     */
    private val mRect = Rect()
    constructor(context: Context) : this(context, null)
    constructor(context: Context, @Nullable attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(
        context: Context,
        @Nullable attrs: AttributeSet?,
        defStyleAttr: Int
    ) : super(context, attrs, defStyleAttr) {
        val appearance = context.obtainStyledAttributes(attrs, R.styleable.NodePointProcessBar)
        mCompleteTextColor = appearance.getColor(
            R.styleable.NodePointProcessBar_completedTextColor, mCompleteTextColor
        )
        mWaitProcessTextColor = appearance.getColor(
            R.styleable.NodePointProcessBar_processTextColor, mWaitProcessTextColor
        )
        mProcessTextColor =
            appearance.getColor(
                R.styleable.NodePointProcessBar_waitProcessTextColor,
                mProcessTextColor
            )
        isDebug = appearance.getBoolean(
            R.styleable.NodePointProcessBar_isDebug, isDebug
        )
        mTextCircleMargin = appearance.getDimension(
            R.styleable.NodePointProcessBar_textCircleMargin, mTextCircleMargin
        )
        mTextLeftRightMargin = appearance.getDimension(
            R.styleable.NodePointProcessBar_textLeftRightMargin, mTextLeftRightMargin
        )
        mCircleRadius = appearance.getDimension(
            R.styleable.NodePointProcessBar_npbCircleRadius, mCircleRadius
        )
        mCircleBorder = appearance.getDimension(
            R.styleable.NodePointProcessBar_circleBorder, mCircleBorder
        )
        mLineWidth = appearance.getDimension(
            R.styleable.NodePointProcessBar_lineWidth, mLineWidth
        )
        mLineMargin = appearance.getDimension(
            R.styleable.NodePointProcessBar_lineMargin, mLineMargin
        )
        initPaint()
        show(mTextList, mProcessIndexSet)
    }
    /**
     * 初始化画笔属性
     */
    private fun initPaint() {
        // 设置文字画笔
        mTextPaint = TextPaint()
        mTextPaint.isAntiAlias = true
        mTextPaint.textSize = textSize
        mTextPaint.color = mWaitProcessTextColor
        // 设置圆圈画笔
        mCirclePaint = Paint()
        mCirclePaint.isAntiAlias = true
        mCirclePaint.color = mProcessTextColor
        mCirclePaint.style = Paint.Style.STROKE
        mCirclePaint.strokeWidth = mCircleBorder
    }
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        Log.d(TAG, "---------------onMeasure()")
        measureText()
        val widthSize = if (MeasureSpec.EXACTLY == widthMode) {
            MeasureSpec.getSize(widthMeasureSpec)
        } else {
            mContentWidth.toInt()
        }
        val heightSize = if (MeasureSpec.EXACTLY == heightMode) {
            MeasureSpec.getSize(heightMeasureSpec)
        } else {
            mContentHeight.toInt()
        }
        /**
         * 设置控件的宽高
         */
        setMeasuredDimension(widthSize, heightSize)
        calcContentWidthHeight()
    }
    /**
     * 测量文字的长宽,将文字视为rect矩形
     */
    private fun measureText() {
        Log.d(TAG, "---------------measureText()")
        mTextBoundList.clear()
        for (name in mTextList) {
            mRect.setEmpty()
            mTextPaint.getTextBounds(name, 0, name.length, mRect)
            mTextBoundList.add(mRect)
        }
    }
    /**
     * 获取内容的高度,如果控件的宽度小于内容的宽度,意味着一行放不下了,文字的大小减小1sp,重新测量文字的宽高,重新
     */
    private fun calcContentWidthHeight() {
        // 一开始没有传递文字的
        mContentHeight = if (mTextBoundList.isNotEmpty()) {
            mCircleRadius * 2 + mTextCircleMargin + mRect.height() + getBaseline(mTextPaint)
        } else {
            mTextPaint.getTextBounds("中", 0, 1, mRect)
            mCircleRadius * 2 + mTextCircleMargin + mRect.height() + getBaseline(mTextPaint)
        }
        if (measuredWidth == 0 || mTextBoundList.isEmpty()) {
            return
        }
        mContentWidth = 0f
        for (rect in mTextBoundList) {
            mContentWidth += rect.width()
        }
        Log.d(TAG, "---------------measuredWidth=$measuredWidth,mContentWidth=$mContentWidth")
        // 如果控件的宽度小于内容的宽度加文本的边距,意味着一行放不下了,文字的大小减小1sp,重新测量文字的宽高后,设置控件得高度
        // 如果控件的宽度大于内容的宽度加文本的边距,意味着一行放得下,设置控件得高度
        if (measuredWidth - mContentWidth < (mTextLeftRightMargin * (mTextList.size - 1))) {
            mTextPaint.textSize = mTextPaint.textSize - 1f.sp2px()
            measureText()
            calcContentWidthHeight()
            return
        }
        setMeasuredDimension(measuredWidth, mContentHeight.toInt())
    }
    override fun onDraw(canvas: Canvas) {
        //若未设置节点标题或者选中项的列表,则取消绘制
        if (mTextList.isEmpty() || mTextBoundList.isEmpty()) {
            return
        }
        //画灰色圆圈的个数
        mCircleCount = mTextList.size
        // 每一段文字的Y坐标
        val textY = getBaseline(mTextPaint) + height / 2 + marginTop / 2
        mCirclePaint.strokeWidth = mCircleBorder
        //绘制文字和圆形
        for (i in 0 until mCircleCount) {
            if (mProcessIndexSet.contains(i)) {
                // 正在处理中
                if (mProcessIndexSet.size == i + 1) {
                    mCirclePaint.style = Paint.Style.FILL
                    // 正在处理中的文字颜色
                    mTextPaint.color = mProcessTextColor
                    mCirclePaint.color = mProcessTextColor
                } else {
                    //处理完成圆圈空心
                    mCirclePaint.style = Paint.Style.STROKE
                    //处理完成文字颜色
                    mTextPaint.color = mCompleteTextColor
                    mCirclePaint.color = mProcessTextColor
                }
            } else {
                //待处理
                mCirclePaint.color = mWaitProcessTextColor
                mCirclePaint.style = Paint.Style.FILL
                mTextPaint.color = mWaitProcessTextColor
            }
            //每一段文字宽度
            val textWidth = mTextBoundList[i].width()
            // 每一段宽度
            val itemWidth = width * 1f / mCircleCount
            // 每一段文字居中
            // |----text----|----text----|
            //    一段文字       一段文字
            //每一段文字起始的X坐标
            val textX = itemWidth / 2f - textWidth / 2f + i * itemWidth
            canvas.drawText(mTextList[i], textX, textY, mTextPaint)
            //每一个圆圈的Y坐标
            val circleY = height / 2f - mCircleRadius - mTextCircleMargin / 2
            //每一个圆圈的X坐标
            val circleX = itemWidth / 2 + i * itemWidth
            canvas.drawCircle(
                circleX,
                circleY,
                mCircleRadius,
                mCirclePaint
            )
            // 画线,两个圆圈之间一条线段
            mCirclePaint.strokeWidth = mLineWidth
            if (i < mCircleCount - 1) {
                //已经处理过的线颜色
                if (mProcessIndexSet.contains(i + 1)) {
                    mCirclePaint.color = mProcessTextColor
                } else {
                    // 待处理的线段颜色
                    mCirclePaint.color = mWaitProcessTextColor
                }
                // 线段起始 x 坐标
                val lineStartX = itemWidth * i + itemWidth / 2f + mCircleRadius + mLineMargin
                // 线段结束 x 坐标
                val lineEndX =
                    itemWidth * i + itemWidth + itemWidth / 2f - mCircleRadius - mLineMargin
                canvas.drawLine(
                    lineStartX,
                    circleY,
                    lineEndX,
                    circleY,
                    mCirclePaint
                )
            }
            Log.d("tag", "--------itemWidth=$itemWidth")
        }
        if (isDebug) {
            mCirclePaint.color = Color.RED
            canvas.drawLine(
                0f,
                height / 2f - 1f.dp2px() / 2,
                width * 1F,
                height / 2f + 1f.dp2px() / 2,
                mCirclePaint
            )
        }
    }
    /**
     * 供外部调用,展示内容
     * @param titles 要展示的内容列表
     * @param progressIndexSet 节点选中项集合
     */
    fun setNodeData(titles: List<String>, progressIndexSet: Set<Int>) {
        mTextList = titles
        mProcessIndexSet = progressIndexSet
        measureText()
        calcContentWidthHeight()
        invalidate()
    }
    /**
     * 获取文字的基线
     */
    private fun getBaseline(p: Paint): Float {
        val fontMetrics: Paint.FontMetrics = p.fontMetrics
        return (fontMetrics.bottom - fontMetrics.top) - fontMetrics.descent
    }
}

这里的show()方法用于展示内容,第一个参数要展示的内容列表,第二个参数代表节点选中项集合,紧接着测量文字的宽高,调用这个方法calcContentWidthHeight()获取文字的高度,然后设置文字的宽高,代码中的注释写的很详细,我们就不再细说了

声明下style

attrs.xml

 <declare-styleable name="NodePointProcessBar">
        <attr name="completedTextColor" format="color" />
        <attr name="processTextColor" format="color" />
        <attr name="waitProcessTextColor" format="color" />
        <attr name="textCircleMargin" format="dimension" />
        <attr name="textLeftRightMargin" format="dimension" />
        <attr name="npbCircleRadius" format="dimension" />
        <attr name="circleBorder" format="dimension" />
        <attr name="lineWidth" format="dimension" />
        <attr name="lineMargin" format="dimension" />
        <attr name="isDebug" format="boolean" />
    </declare-styleable>

新建一个ExtendUtil.kt文件

fun Int.sp2px(): Int {
    val displayMetrics = Resources.getSystem().displayMetrics
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this.toFloat(), displayMetrics)
        .toInt()
}
fun Float.sp2px(): Float {
    val displayMetrics = Resources.getSystem().displayMetrics
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this, displayMetrics)
}
fun Int.dp2px(): Int {
    val displayMetrics = Resources.getSystem().displayMetrics
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), displayMetrics)
        .toInt()
}
fun Float.dp2px(): Float {
    val displayMetrics = Resources.getSystem().displayMetrics
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, displayMetrics)
}

接着创建布局文件

activity_node_progress_bar.xml

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <cn.wwj.customview.widget.NodePointProcessBar
        android:id="@+id/nodePointPb"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:layout_marginHorizontal="10dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:isDebug="false"
        app:lineWidth="1dp"
        app:lineMargin="5dp"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

再Activity中使用它

package cn.wwj.customview
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.appcompat.app.AppCompatActivity
import cn.wwj.customview.widget.NodePointProcessBar
/**
 * 节点进度Activity
 */
class NodeProgressBarActivity : AppCompatActivity() {
    /**
     * 数据结合
     */
    private val mTextList: List<String> = mutableListOf("提交申请", "商家处理", "寄回商品", "商家退款", "退款成功")
    /**
     * 正在处理的节点索引结合
     */
    private var mProgressIndexSet: Set<Int> = mutableSetOf(0, 1,2,3,4,6)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_node_progress_bar)
        val nodePointPb: NodePointProcessBar = findViewById(R.id.nodePointPb)
        Handler(Looper.getMainLooper()).postDelayed({
            nodePointPb.setNodeData(mTextList, mProgressIndexSet)
        }, 1000)
    }
}

mTextList数据集合

mProgressIndexSet正在处理的节点索引结合,创建Handler对象模拟调用网络接口,1秒后返回数据

节点全部处理完成.png

项目地址,在customview这模块下

https://github.com/githubwwj/MyAndroid

以上就是Android新建水平节点进度条示例的详细内容,更多关于Android水平节点进度条的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android实现进度条(ProgressBar)的功能与用法

    进度条(ProgressBar)的功能与用法,供大家参考,具体内容如下 进度条是UI界面中一种实用的UI组件,用于显示一个耗时操作显示出来的百分比,进度条可以动态的显示进度,避免是用户觉得系统长时间未反应,提高用户的体验. 下面程序简单示范了进度条的用法,界面布局文件如下: 在layout下的activity_main中: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andr

  • Android studio实现日期 、时间选择器与进度条

    本文实例为大家分享了Android studio实现日期 .时间选择器与进度条,供大家参考,具体内容如下 日期选择器 public void onclick(View v){         Calendar calendar=Calendar.getInstance();         new DatePickerDialog( this, new DatePickerDialog.OnDateSetListener() {             @Override            

  • Android中实现ProgressBar菊花旋转进度条的动画效果

    在一些常见到的加载中需要显示一个加载动画,如旋转的菊花,旋转的圈圈等等动画-,然后我们现在就来说下怎么去试下它吧 一.菊花的旋转动画 1.新建一个drawable文件 在res/drawable下新建一个progressbar_style.xml文件定义一个旋转动画 <animated-rotate xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable

  • 详解Android如何自定义view实现圆形进度条

    Android中实现进度条有很多种方式,自定义进度条一般是继承progressBar或继承view来实现,本篇中讲解的是第二种方式. 先上效果图: 实现圆形进度条总体来说并不难,还是跟往常一样继承view,初始化画笔,按下面的步骤一步步来就好了.对初学者来说动画效果可能比较陌生,我们可以使用属性动画中的valueAnimator来实现动画效果. 实现步骤: 1.画出一个灰色的圆环作为背景. 2.画出上层的圆环覆盖下方的圆环. 3.加入动画效果 值得注意的是怎么设置圆环和文字的位置. 画出矩形只需

  • Android实现绚丽的自定义进度条

    目录 前言 效果图 实现步骤 1.绘制背景圆形矩形 2.绘制进度 3.绘制文字 4.加入动画 完整代码 前言 进度条是在Android项目中很常用的组件之一,如果想要理解它是怎么实现的,首先还需要了解自定义view和Android坐标系相关的知识,下面我来详细地介绍一下自定义进度条的实现过程. 本项目源码:https://gitee.com/tu_erhongjiang/android-progress-bar 效果图 实现步骤 1.绘制背景圆形矩形 首先要画出一个圆形矩形,RectF里面传递的

  • Android Studio实现进度条效果

    本文实例为大家分享了Android Studio实现进度条效果的具体代码,供大家参考,具体内容如下 实验作业 要求一个进度条,进度随机 效果图 xml代码 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://s

  • Android新建水平节点进度条示例

    目录 前言 效果图 圆圈和文字状态 文字居中 代码 声明下style 接着创建布局文件 再Activity中使用它 mTextList数据集合 前言 效果图 前几天在网上没有找到合适的横向节点进度条,自己动手写了一个,先来看看效果图 圆圈和文字状态 我们看到小圆圈和文字有几种状态呢? 第一个空心的小圆圈是处理完成的状态 第二个实心的小圆圈是处理中的状态 第三个实心的小圆圈是待处理的状态没错,我们看到了小圆圈和文字有三种处理状态 文字居中 我们写一个类继承自AppCompatTextView,通过

  • Android自定义多节点进度条显示的实现代码(附源码)

    亲们里面的线段颜色和节点图标都是可以自定义的. 在没给大家分享实例代码之前,先给大家展示下效果图: main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rl_parent" xmlns:tools="http://schemas.android.com/tools" android:layou

  • Android自定义水平渐变进度条

    先看进度条的效果: 具体实现: 新建类,继承自View,在onDraw中进行绘制: import android.content.Context; import android.graphics.Canvas; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.RectF; import android.graphics.Shader; import and

  • Android编程实现通知栏进度条效果的方法示例

    本文实例讲述了Android编程实现通知栏进度条效果的方法.分享给大家供大家参考,具体如下: /** * 通知管理工具类 * * @description: * @author ldm * @date 2016-5-3 上午9:39:56 */ public class NotificationUtil { private Context mContext; // NotificationManager : 是状态栏通知的管理类,负责发通知.清楚通知等. private Notification

  • android实现节点进度条效果

    本文实例为大家分享了android实现节点进度条效果展示的具体代码,供大家参考,具体内容如 代码: package utils.android.view.lxz; import java.util.ArrayList; import java.util.List; import com.community.custom.android.R; import android.app.Activity; import android.content.Context; import android.gr

  • Android 自定义View实现多节点进度条功能

    前言 最近项目有一个节点进度条的小需求,完成后,想分享出来希望可以帮到有需要的同学. 真机效果图 自定义View完整代码 开箱即用~,注释已经炒鸡详细了 /** * @description: 节点进度条 * @author: DMingO * @date: 2020/4/15 */ public class PointProcessBar extends View { /** * 未选中时的连线画笔 */ private Paint mLinePaint; /** * 选中时的连线画笔 */

  • Android编程实现自定义进度条颜色的方法

    本文实例讲述了Android编程实现自定义进度条颜色的方法.分享给大家供大家参考,具体如下: android 自定义进度条颜色 先看图 基于产品经理各种自定义需求,经过查阅了解,下面是自己对android自定义进度条的学习过程! 这个没法了只能看源码了,还好下载了源码, sources\base\core\res\res\  下应有尽有,修改进度条颜色只能找progress ,因为是改变样式,首先找styles.xml 找到xml后,进去找到 <style name="Widget.Pro

  • Python编程使用PyQt5库实现动态水波进度条示例

    目录 原理介绍 代码实操 最近做了一个小项目,里面有一个需求需要添加一个动态进度条,进度条的样式就类似于水波来回起伏的那种形状,下面就是最初的展示效果(有一点区别,这里我加了一个进度自动增加的功能): 下面先说一下这个效果的制作原理 原理介绍 在介绍动态效果之前需要先看一下静态的: 如果仔细观察的话,静态图效果的呈现在于先后的两个线条的绘制,产生水波的主要是由于两线条的左右的水平错位 以及 设置的透明度不同 所造成的: 想要形成最后的水波荡漾的视觉效果,只需要把数张线条连续走向的静态图拼接在一起

  • Android Canva实现渐变进度条

    目录 前言 前言 标题说渐变进度条是为了方便理解,这里本身的项目背景是一款表盘的分针.先上图: 表盘 周圈蓝色的渐变条(分针)就是本次要实现的东西. 1.拆分 首先,熟悉Canvas的朋友应该知道它可以画出各种形状,但偏偏没有一头是圆的环形(这里不考虑使用path绘制).所以我们不得不把它拆分为2个形状:圆环与圆. 2.绘制圆环 绘制圆环有很多种方法,比如画2个圆取补集之类的.这里直接使用canvas.drawArc()函数来画.先看看函数原型: void drawArc (RectF oval

  • Android动态自定义圆形进度条

    效果图: A.绘制圆环,圆弧,文本 //1.画圆环 //原点坐标 float circleX = width / 2; float circleY = width / 2; //半径 float radius = width / 2 - roundWidth / 2; //设置画笔的属性 paint.setColor(roundColor); paint.setStrokeWidth(roundWidth); paint.setStyle(Paint.Style.STROKE); canvas.

随机推荐