<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
前幾天在網上沒有找到合適的橫向節點進度條,自己動手寫了一個,先來看看效果圖
我們看到小圓圈和文字有幾種狀態呢?
我們寫一個類繼承自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秒後返回資料
節點全部處理完成.png
專案地址,在customview這模組下
https://github.com/githubwwj/MyAndroid
以上就是Android新建水平節點進度條範例的詳細內容,更多關於Android水平節點進度條的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45