本文小編為大家詳細介紹“Android怎么新建水平節點進度條”,內容詳細,步驟清晰,細節處理妥當,希望這篇“Android怎么新建水平節點進度條”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
前幾天在網上沒有找到合適的橫向節點進度條,自己動手寫了一個,先來看看效果圖
我們看到小圓圈和文字有幾種狀態呢?
第一個空心的小圓圈是處理完成的狀態
第二個實心的小圓圈是處理中的狀態
第三個實心的小圓圈是待處理的狀態
沒錯,我們看到了小圓圈和文字有三種處理狀態
我們寫一個類繼承自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()獲取文字的高度,然后設置文字的寬高,代碼中的注釋寫的很詳細,我們就不再細說了
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>
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) } }
mProgressIndexSet正在處理的節點索引結合,創建Handler對象模擬調用網絡接口,1秒后返回數據
讀到這里,這篇“Android怎么新建水平節點進度條”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。