本文小编为大家详细介绍“Android怎么新建水平节点进度条”,内容详细,步骤清晰,细节处理妥当,希望这篇“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.sp2pxclass 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() } 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) } } 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 ) } } 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.customviewimport android.os.Bundleimport android.os.Handlerimport android.os.Looperimport androidx.appcompat.app.AppCompatActivityimport cn.wwj.customview.widget.NodePointProcessBarclass 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秒后返回数据
读到这里,这篇“Android怎么新建水平节点进度条”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网行业资讯频道。