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

目录

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

前言
效果图
前几天在网上没有找到合适的横向节点进度条,自己动手写了一个,先来看看效果图
Android新建水平节点进度条示例
文章图片


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

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


代码
package cn.wwj.customview.widgetimport android.content.Contextimport android.graphics.Canvasimport android.graphics.Colorimport android.graphics.Paintimport android.graphics.Rectimport android.text.TextPaintimport android.util.AttributeSetimport android.util.Logimport androidx.annotation.Nullableimport androidx.appcompat.widget.AppCompatTextViewimport androidx.core.content.ContextCompatimport androidx.core.view.marginTopimport cn.wwj.customview.Rimport cn.wwj.customview.dp2pximport cn.wwj.customview.sp2px/** * 节点进度条 */class NodePointProcessBar : AppCompatTextView {/*** 文字画笔*/private lateinit var mTextPaint: TextPaint/*** 圆画笔*/private lateinit var mCirclePaint: Paintprivate 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 = 0private 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 = 0fprivate var mContentWidth = 0f/*** 节点底部的文字列表*/private var mTextList: List = mutableListOf()/*** 选中项集合*/private var mProcessIndexSet: Set = mutableSetOf()/*** 文字同宽高的矩形,用来测量文字*/private var mTextBoundList: MutableList = 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 = truemTextPaint.textSize = textSizemTextPaint.color = mWaitProcessTextColor// 设置圆圈画笔mCirclePaint = Paint()mCirclePaint.isAntiAlias = truemCirclePaint.color = mProcessTextColormCirclePaint.style = Paint.Style.STROKEmCirclePaint.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 = 0ffor (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 / 2mCirclePaint.strokeWidth = mCircleBorder//绘制文字和圆形for (i in 0 until mCircleCount) {if (mProcessIndexSet.contains(i)) {// 正在处理中if (mProcessIndexSet.size == i + 1) {mCirclePaint.style = Paint.Style.FILL// 正在处理中的文字颜色mTextPaint.color = mProcessTextColormCirclePaint.color = mProcessTextColor} else {//处理完成圆圈空心mCirclePaint.style = Paint.Style.STROKE//处理完成文字颜色mTextPaint.color = mCompleteTextColormCirclePaint.color = mProcessTextColor}} else {//待处理mCirclePaint.color = mWaitProcessTextColormCirclePaint.style = Paint.Style.FILLmTextPaint.color = mWaitProcessTextColor}//每一段文字宽度val textWidth = mTextBoundList[i].width()// 每一段宽度val itemWidth = width * 1f / mCircleCount// 每一段文字居中// |----text----|----text----|//一段文字一段文字//每一段文字起始的X坐标val textX = itemWidth / 2f - textWidth / 2f + i * itemWidthcanvas.drawText(mTextList[i], textX, textY, mTextPaint)//每一个圆圈的Y坐标val circleY = height / 2f - mCircleRadius - mTextCircleMargin / 2//每一个圆圈的X坐标val circleX = itemWidth / 2 + i * itemWidthcanvas.drawCircle(circleX,circleY,mCircleRadius,mCirclePaint)// 画线,两个圆圈之间一条线段mCirclePaint.strokeWidth = mLineWidthif (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 - mLineMargincanvas.drawLine(lineStartX,circleY,lineEndX,circleY,mCirclePaint)}Log.d("tag", "--------itemWidth=$itemWidth")}if (isDebug) {mCirclePaint.color = Color.REDcanvas.drawLine(0f,height / 2f - 1f.dp2px() / 2,width * 1F,height / 2f + 1f.dp2px() / 2,mCirclePaint)}}/*** 供外部调用,展示内容* @param titles 要展示的内容列表* @param progressIndexSet 节点选中项集合*/fun setNodeData(titles: List, progressIndexSet: Set) {mTextList = titlesmProcessIndexSet = progressIndexSetmeasureText()calcContentWidthHeight()invalidate()}/*** 获取文字的基线*/private fun getBaseline(p: Paint): Float {val fontMetrics: Paint.FontMetrics = p.fontMetricsreturn (fontMetrics.bottom - fontMetrics.top) - fontMetrics.descent}}

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

声明下style
Android新建水平节点进度条示例
文章图片

attrs.xml

新建一个ExtendUtil.kt文件
fun Int.sp2px(): Int {val displayMetrics = Resources.getSystem().displayMetricsreturn TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this.toFloat(), displayMetrics).toInt()}fun Float.sp2px(): Float {val displayMetrics = Resources.getSystem().displayMetricsreturn TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this, displayMetrics)}fun Int.dp2px(): Int {val displayMetrics = Resources.getSystem().displayMetricsreturn TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), displayMetrics).toInt()}fun Float.dp2px(): Float {val displayMetrics = Resources.getSystem().displayMetricsreturn TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, displayMetrics)}


接着创建布局文件
activity_node_progress_bar.xml


再Activity中使用它
package cn.wwj.customviewimport android.os.Bundleimport android.os.Handlerimport android.os.Looperimport androidx.appcompat.app.AppCompatActivityimport cn.wwj.customview.widget.NodePointProcessBar/** * 节点进度Activity */class NodeProgressBarActivity : AppCompatActivity() {/*** 数据结合*/private val mTextList: List = mutableListOf("提交申请", "商家处理", "寄回商品", "商家退款", "退款成功")/*** 正在处理的节点索引结合*/private var mProgressIndexSet: Set = 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秒后返回数据
Android新建水平节点进度条示例
文章图片

节点全部处理完成.png
项目地址,在customview这模块下
https://github.com/githubwwj/MyAndroid
【Android新建水平节点进度条示例】以上就是Android新建水平节点进度条示例的详细内容,更多关于Android水平节点进度条的资料请关注脚本之家其它相关文章!

    推荐阅读