NodeProgressBar
一.簡介
Android日常開發中我們可能會遇到開發一個帶節點的進度條的需求,這個需求看似簡單,實際上可以挖掘出不少東西。做的好的話也可以做成相對通用的自定義組件。
二.自定義屬性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="NodeProgressBar">
<attr name="nodeCount" format="integer" />
<attr name="nodeWidth" format="dimension" />
<attr name="nodeHeight" format="dimension" />
<attr name="nodeUnreached" format="reference" />
<attr name="nodeReached" format="reference" />
<attr name="nodeFinished" format="reference" />
<attr name="nodeFailed" format="reference" />
<attr name="nodeRatio" format="float" />
<attr name="topTxtEnable" format="boolean" />
<attr name="topTxtSize" format="dimension" />
<attr name="topTxtColor" format="color" />
<attr name="topTxtGap" format="dimension" />
<attr name="topTxtStyle" format="enum">
<enum name="common" value="0" />
<enum name="bold" value="1" />
<enum name="italic" value="2" />
</attr>
<attr name="bottomTxtEnable" format="boolean" />
<attr name="bottomTxtSize" format="dimension" />
<attr name="bottomTxtColor" format="color" />
<attr name="bottomTxtGap" format="dimension" />
<attr name="bottomTxtStyle" format="enum">
<enum name="common" value="0" />
<enum name="bold" value="1" />
<enum name="italic" value="2" />
</attr>
<attr name="bottomWarnTxtColor" format="color" />
<attr name="bottomWarnTxtStyle" format="enum">
<enum name="common" value="0" />
<enum name="bold" value="1" />
<enum name="italic" value="2" />
</attr>
<attr name="lineWidth" format="dimension" />
<attr name="reachedLineColor" format="color" />
<attr name="unreachedLineColor" format="color" />
<attr name="regionWidth" format="dimension" />
</declare-styleable>
</resources>
-
nodeCount——節點個數
-
nodeWidth——節點寬度
-
nodeHeight——節點高度
-
nodeUnreached——未到達節點的圖片
-
nodeReached——已到達節點的圖片
-
nodeFailed——失敗節點的圖片
-
nodeFinished——完成節點的圖片
-
nodeRatio——節點縮放比,到達或未到達節點比失敗或完成節點
-
topTxtEnable——上方文字是否可見
-
topTxtSize——上方文字尺寸
-
topTxtColor——上方文字顏色
-
topTxtGap——上方文字與節點的間距
-
topTxtStyle——上方文字的樣式(常規或加粗或斜體)
-
bottomTxtEnable——下方文字是否可見
-
bottomTxtSize——下方文字尺寸
-
bottomTxtColor——下方文字顏色
-
bottomTxtGap——下方文字與節點的間距
-
bottomTxtStyle——下方文字的樣式(常規或加粗或斜體)
-
bottomWarnTxtColor——下方提醒文字的顏色,如果節點是失敗類型,可能會用到
-
bottomWarnTxtStyle——下方提醒文字的樣式
-
lineWidth——連線寬度
-
unreachedLineColor——未到達連線的顏色
-
reachedLineColor——已到達連線的顏色
-
regionWidth——節點區域寬度,該寬度用來計算重要元素的座標
三.數據定義
/**
* 節點對象
*/
public static class Node {
public interface NodeState {
int UNREACHED = 1;
int REACHED = 2;
int FINISHED = 3;
int FAILED = 4;
}
public interface LineState {
int REACHED = 0;
int UNREACHED = 1;
}
// 節點上方文字
public String topTxt;
// 節點下方文字
public String bottomTxt;
// 節點狀態
public int nodeState;
// 節點後連線狀態
public int nodeAfterLineState;
}
- 一個幾點只有topTxt + bottomTxt + nodeState + nodeAfterLineState四個屬性
- NodeState定義了節點的四種類型
- LineState定義了連接線的兩種類型
除了面的各種自定義屬性,還支持動態設置以下屬性。其中setNodeList()是必須的,將節點列表塞進來才能進行相應的繪製。
/**
* 上方文字是否生效
*
* @param mTopTxtEnable
*/
public void setTopTxtEnable(boolean mTopTxtEnable) {
this.mTopTxtEnable = mTopTxtEnable;
invalidate();
}
/**
* 下方文字是否生效
*
* @param mBottomTxtEnable
*/
public void setBottomTxtEnable(boolean mBottomTxtEnable) {
this.mBottomTxtEnable = mBottomTxtEnable;
invalidate();
}
/**
* 設置節點信息
*
* @param mNodeList
*/
public void setNodeList(@NonNull List<Node> mNodeList) {
this.mNodeList = mNodeList;
this.mNodeCount = mNodeList.size();
invalidate();
}
四.核心代碼
package com.openld.nodeprogressbar.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.openld.nodeprogressbar.R;
import java.util.ArrayList;
import java.util.List;
/**
* author: lllddd
* created on: 2020/7/1 21:10
* description:
*/
public class NodeProgressBar extends View {
// 自定義View寬度
private int mWidth;
// 自定義View高度
private int mHeight;
// 節點個數
private int mNodeCount;
// 節點寬度
private int mNodeWidth;
// 節點高度
private int mNodeHeight;
// 未到達節點的資源id
private int mNodeUnreached;
// 已經到達節點的資源id
private int mNodeReached;
// 已完成節點的資源id
private int mNodeFinished;
// 失敗節點的資源id
private int mNodeFailed;
// 節點大小比例,用於處理成功/失敗節點比到達/未到達節點大的情況
private float mNodeRatio;
// 上方文字是否生效
private boolean mTopTxtEnable;
// 上方文字大小
private int mTopTxtSize;
// 上方文字顏色
private int mTopTxtColor;
// 上方文字距離節點的距離
private int mTopTxtGap;
// 上方文字的樣式
private int mTopTxtStyle;
// 下方文字是否生效
private boolean mBottomTxtEnable;
// 下方文字的大小
private int mBottomTxtSize;
// 下方文字的顏色
private int mBottomTxtColor;
// 下方文字距離節點的距離
private int mBottomTxtGap;
// 下方文字的樣式
private int mBottomTxtStyle;
// 下方提示文字的顏色(失敗的節點)
private int mBottomWarnTxtColor;
// 相仿提示文字的樣式(失敗的節點使用)
private int mBottomWarnTxtStyle;
// 連接線的寬度
private int mLineWidth;
// 已到達的連接線顏色
private int mReachedLineColor;
// 未到達的連接線的顏色
private int mUnreachedLineColor;
// 節點區域橫向寬度
private int mRegionWidth;
// 上方文字畫筆
private Paint mPaintTopTxt;
// 底部文字畫筆
private Paint mPaintBottomTxt;
// 底部提示文字的畫筆
private Paint mPaintBottomWarnTxt;
// 節點畫筆
private Paint mPaintNode;
// 未到達連線畫筆
private Paint mPaintUnreachedLine;
// 已到達連線畫筆
private Paint mPaintReachedLine;
// 未到達節點Bitmap
private Bitmap mNodeUnreachedBitmap;
// 已到達節點Bitmap
private Bitmap mNodeReachedBitmap;
// 失敗節點Bitmap
private Bitmap mNodeFailedBitmap;
// 已完成節點Bitmap
private Bitmap mNodeFinishedBitmap;
// 上方文字的中心座標列表
private List<Location> mTopTxtLocationList;
// 中間節點的中心文字座標列表
private List<Location> mNodeLocationList;
// 下方文字的中心座標列表
private List<Location> mBottomTxtLocationList;
private List<Node> mNodeList = new ArrayList<>();
public NodeProgressBar(Context context) {
this(context, null);
}
public NodeProgressBar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public NodeProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.NodeProgressBar);
mNodeCount = ta.getInt(R.styleable.NodeProgressBar_nodeCount, 0);
mNodeWidth = ta.getDimensionPixelSize(R.styleable.NodeProgressBar_nodeWidth, 0);
mNodeHeight = ta.getDimensionPixelSize(R.styleable.NodeProgressBar_nodeHeight, 0);
mNodeUnreached = ta.getResourceId(R.styleable.NodeProgressBar_nodeUnreached, R.drawable.node_unreached);
mNodeReached = ta.getResourceId(R.styleable.NodeProgressBar_nodeReached, R.drawable.node_unreached);
mNodeFinished = ta.getResourceId(R.styleable.NodeProgressBar_nodeFinished, R.drawable.node_unreached);
mNodeFailed = ta.getResourceId(R.styleable.NodeProgressBar_nodeFailed, R.drawable.node_unreached);
mNodeRatio = ta.getFloat(R.styleable.NodeProgressBar_nodeRatio, 1.0F);
mTopTxtEnable = ta.getBoolean(R.styleable.NodeProgressBar_topTxtEnable, false);
mTopTxtSize = ta.getDimensionPixelSize(R.styleable.NodeProgressBar_topTxtSize, 0);
mTopTxtColor = ta.getColor(R.styleable.NodeProgressBar_topTxtColor, Color.TRANSPARENT);
mTopTxtGap = ta.getDimensionPixelSize(R.styleable.NodeProgressBar_topTxtGap, 0);
mTopTxtStyle = ta.getInteger(R.styleable.NodeProgressBar_topTxtStyle, TxtStyle.BOLD);
mBottomTxtEnable = ta.getBoolean(R.styleable.NodeProgressBar_bottomTxtEnable, false);
mBottomTxtSize = ta.getDimensionPixelSize(R.styleable.NodeProgressBar_bottomTxtSize, 0);
mBottomTxtColor = ta.getColor(R.styleable.NodeProgressBar_bottomTxtColor, Color.TRANSPARENT);
mBottomTxtGap = ta.getDimensionPixelSize(R.styleable.NodeProgressBar_bottomTxtGap, 0);
mBottomTxtStyle = ta.getInteger(R.styleable.NodeProgressBar_bottomTxtStyle, TxtStyle.COMMON);
mBottomWarnTxtColor = ta.getColor(R.styleable.NodeProgressBar_bottomWarnTxtColor, Color.TRANSPARENT);
mBottomWarnTxtStyle = ta.getInteger(R.styleable.NodeProgressBar_bottomWarnTxtStyle, TxtStyle.COMMON);
mLineWidth = ta.getDimensionPixelSize(R.styleable.NodeProgressBar_lineWidth, 0);
mReachedLineColor = ta.getColor(R.styleable.NodeProgressBar_reachedLineColor, Color.TRANSPARENT);
mUnreachedLineColor = ta.getColor(R.styleable.NodeProgressBar_unreachedLineColor, Color.TRANSPARENT);
mRegionWidth = ta.getDimensionPixelSize(R.styleable.NodeProgressBar_regionWidth, 0);
ta.recycle();
configBitmaps(context);
configPaints();
}
/**
* 配置畫筆
*/
private void configPaints() {
// 上方文字畫筆屬性設置
configTopTxtPaint();
// 下方文字畫筆屬性設置
configBottomTxtPaint();
// 下方提示文字畫筆屬性設置
configBottomWarnTxtPaint();
// 節點畫筆屬性設置
configNodePaint();
// 未到達連接線畫筆屬性設置
configUnreachedLinePaint();
// 已到達連接線畫筆屬性設置
configReachedLinePaint();
}
/**
* 已到達連接線畫筆屬性設置
*/
private void configReachedLinePaint() {
mPaintReachedLine = new Paint();
mPaintReachedLine.setColor(mReachedLineColor);
mPaintReachedLine.setStrokeWidth(mLineWidth);
mPaintReachedLine.setStyle(Paint.Style.FILL);
mPaintReachedLine.setAntiAlias(true);
}
/**
* 未到達連接線畫筆屬性設置
*/
private void configUnreachedLinePaint() {
mPaintUnreachedLine = new Paint();
mPaintUnreachedLine.setColor(mUnreachedLineColor);
mPaintUnreachedLine.setStrokeWidth(mLineWidth);
mPaintUnreachedLine.setStyle(Paint.Style.FILL);
mPaintUnreachedLine.setAntiAlias(true);
}
/**
* 節點畫筆屬性設置
*/
private void configNodePaint() {
mPaintNode = new Paint();
mPaintNode.setAntiAlias(true);
}
/**
* 下方提示文字畫筆屬性設置
*/
private void configBottomWarnTxtPaint() {
mPaintBottomWarnTxt = new Paint();
mPaintBottomWarnTxt.setTextSize(mBottomTxtSize);
mPaintBottomWarnTxt.setColor(mBottomWarnTxtColor);
mPaintBottomWarnTxt.setTextAlign(Paint.Align.CENTER);
mPaintBottomWarnTxt.setAntiAlias(true);
if (TxtStyle.COMMON == mBottomWarnTxtStyle) {
mPaintBottomWarnTxt.setTypeface(Typeface.DEFAULT);
} else if (TxtStyle.BOLD == mBottomWarnTxtStyle) {
mPaintBottomWarnTxt.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
} else if (TxtStyle.ITALIC == mBottomWarnTxtStyle) {
mPaintBottomWarnTxt.setTypeface(Typeface.defaultFromStyle(Typeface.ITALIC));
}
}
/**
* 下方文字畫筆屬性設置
*/
private void configBottomTxtPaint() {
mPaintBottomTxt = new Paint();
mPaintBottomTxt.setTextSize(mBottomTxtSize);
mPaintBottomTxt.setColor(mBottomTxtColor);
mPaintBottomTxt.setTextAlign(Paint.Align.CENTER);
mPaintBottomTxt.setAntiAlias(true);
if (TxtStyle.COMMON == mBottomTxtStyle) {
mPaintBottomTxt.setTypeface(Typeface.DEFAULT);
} else if (TxtStyle.BOLD == mBottomTxtStyle) {
mPaintBottomTxt.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
} else if (TxtStyle.ITALIC == mBottomTxtStyle) {
mPaintBottomTxt.setTypeface(Typeface.defaultFromStyle(Typeface.ITALIC));
}
}
/**
* 上方文字畫筆屬性設置
*/
private void configTopTxtPaint() {
mPaintTopTxt = new Paint();
mPaintTopTxt.setTextSize(mTopTxtSize);
mPaintTopTxt.setColor(mTopTxtColor);
mPaintTopTxt.setTextAlign(Paint.Align.CENTER);
mPaintTopTxt.setAntiAlias(true);
if (TxtStyle.COMMON == mTopTxtStyle) {
mPaintTopTxt.setTypeface(Typeface.DEFAULT);
} else if (TxtStyle.BOLD == mTopTxtStyle) {
mPaintTopTxt.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
} else if (TxtStyle.ITALIC == mTopTxtStyle) {
mPaintTopTxt.setTypeface(Typeface.defaultFromStyle(Typeface.ITALIC));
}
}
/**
* 配置bitmap
*
* @param context
*/
private void configBitmaps(Context context) {
Resources resources = context.getResources();
mNodeUnreachedBitmap = BitmapFactory.decodeResource(resources, R.drawable.node_unreached);
mNodeReachedBitmap = BitmapFactory.decodeResource(resources, R.drawable.node_reached);
mNodeFailedBitmap = BitmapFactory.decodeResource(resources, R.drawable.node_failed);
mNodeFinishedBitmap = BitmapFactory.decodeResource(resources, R.drawable.node_finished);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
// if (mTopTxtEnable && mBottomTxtEnable) {// 上下文字都展示
// mHeight = mTopTxtSize + mTopTxtGap + mNodeHeight + mBottomTxtGap + mBottomTxtSize;
// } else if (mTopTxtEnable) {// 僅上方文字展示
// mHeight = mTopTxtSize + mTopTxtGap + mNodeHeight;
// } else if (mBottomTxtEnable) {// 僅下方文字展示
// mHeight = mNodeHeight + mBottomTxtGap + mBottomTxtSize;
// } else {// 不展示上下文字
// mHeight = mNodeHeight;
// }
// 上線各加1dp的餘量,防止個別情況下展示不全
setMeasuredDimension(mWidth, mHeight);
}
@Override
@SuppressLint("DrawAllocation")
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mNodeCount <= 0 || mNodeList == null || mNodeList.isEmpty() || mNodeList.size() != mNodeCount) {
return;
}
// 初始化位置列表
initLocationLists();
// 測量座標
measureLocations();
// 繪製上方文字
if (mTopTxtEnable) {
for (int i = 0; i < mNodeCount; i++) {
Node node = mNodeList.get(i);
if (TextUtils.isEmpty(node.topTxt)) {
continue;
}
Paint.FontMetrics metrics = mPaintTopTxt.getFontMetrics();
int x = mTopTxtLocationList.get(i).x;
int y = (int) (mTopTxtLocationList.get(i).y + Math.abs(mPaintTopTxt.ascent() + mPaintTopTxt.descent() / 2));
canvas.drawText(node.topTxt, x, y, mPaintTopTxt);
}
}
// 繪製連線
for (int i = 0; i < mNodeCount; i++) {
Node node = mNodeList.get(i);
if (i == mNodeCount - 1) {
break;
}
int x1 = mNodeLocationList.get(i).x;
int y1 = mNodeLocationList.get(i).y;
int x2 = mNodeLocationList.get(i + 1).x;
int y2 = mNodeLocationList.get(i + 1).y;
if (Node.LineState.UNREACHED == node.nodeAfterLineState) {
canvas.drawLine(x1, y1, x2, y2, mPaintUnreachedLine);
} else if (Node.LineState.REACHED == node.nodeAfterLineState) {
canvas.drawLine(x1, y1, x2, y2, mPaintReachedLine);
} else {
canvas.drawLine(x1, y1, x2, y2, mPaintUnreachedLine);
}
}
// 繪製節點
for (int i = 0; i < mNodeCount; i++) {
Node node = mNodeList.get(i);
int x = mNodeLocationList.get(i).x;
int y = mNodeLocationList.get(i).y;
if (Node.NodeState.UNREACHED == node.nodeState) {
Rect rect = new Rect(0, 0, mNodeUnreachedBitmap.getWidth(), mNodeUnreachedBitmap.getHeight());
RectF rectF = new RectF(x - mNodeRatio * mNodeWidth / 2, y - mNodeRatio * mNodeHeight / 2, x + mNodeRatio * mNodeWidth / 2, y + mNodeRatio * mNodeHeight / 2);
canvas.drawBitmap(mNodeUnreachedBitmap, rect, rectF, mPaintNode);
} else if (Node.NodeState.REACHED == node.nodeState) {
Rect rect = new Rect(0, 0, mNodeUnreachedBitmap.getWidth(), mNodeUnreachedBitmap.getHeight());
RectF rectF = new RectF(x - mNodeRatio * mNodeWidth / 2, y - mNodeRatio * mNodeHeight / 2, x + mNodeRatio * mNodeWidth / 2, y + mNodeRatio * mNodeHeight / 2);
canvas.drawBitmap(mNodeReachedBitmap, rect, rectF, mPaintNode);
} else if (Node.NodeState.FAILED == node.nodeState) {
Rect rect = new Rect(0, 0, mNodeUnreachedBitmap.getWidth(), mNodeUnreachedBitmap.getHeight());
RectF rectF = new RectF(x - 1.0F * mNodeWidth / 2, y - 1.0F * mNodeHeight / 2, x + 1.0F * mNodeWidth / 2, y + mNodeHeight / 2);
canvas.drawBitmap(mNodeFailedBitmap, rect, rectF, mPaintNode);
} else if (Node.NodeState.FINISHED == node.nodeState) {
Rect rect = new Rect(0, 0, mNodeUnreachedBitmap.getWidth(), mNodeUnreachedBitmap.getHeight());
RectF rectF = new RectF(x - 1.0F * mNodeWidth / 2, y - 1.0F * mNodeHeight / 2, x + 1.0F * mNodeWidth / 2, y + 1.0F * mNodeHeight / 2);
canvas.drawBitmap(mNodeFinishedBitmap, rect, rectF, mPaintNode);
}
}
// 繪製下方文字
if (mBottomTxtEnable) {
for (int i = 0; i < mNodeCount; i++) {
Node node = mNodeList.get(i);
if (TextUtils.isEmpty(node.bottomTxt)) {
continue;
}
int x = mBottomTxtLocationList.get(i).x;
int y = (int) (mBottomTxtLocationList.get(i).y + Math.abs(mPaintBottomTxt.ascent() + mPaintBottomTxt.descent() / 2));
if (Node.NodeState.FAILED != node.nodeState) {
canvas.drawText(node.bottomTxt, x, y, mPaintBottomTxt);
} else {
canvas.drawText(node.bottomTxt, x, y, mPaintBottomWarnTxt);
}
}
}
}
private void initLocationLists() {
if (mTopTxtLocationList != null) {
mTopTxtLocationList.clear();
} else {
mTopTxtLocationList = new ArrayList<>();
}
if (mNodeLocationList != null) {
mNodeLocationList.clear();
} else {
mNodeLocationList = new ArrayList<>();
}
if (mBottomTxtLocationList != null) {
mBottomTxtLocationList.clear();
} else {
mBottomTxtLocationList = new ArrayList<>();
}
}
/**
* 測量元素的中心座標
*/
private void measureLocations() {
if (mNodeCount == 1) {
// 上方文字的中心座標
if (mTopTxtEnable) {
Location topTxtLoc = new Location();
topTxtLoc.x = mWidth / 2;
topTxtLoc.y = mTopTxtSize / 2;
mTopTxtLocationList.add(topTxtLoc);
}
// 節點的中心座標
if (mTopTxtEnable) {
Location nodeLoc = new Location();
nodeLoc.x = mWidth / 2;
nodeLoc.y = mTopTxtSize + mTopTxtGap + mNodeHeight / 2;
mNodeLocationList.add(nodeLoc);
} else {
Location nodeLoc = new Location();
nodeLoc.x = mWidth / 2;
nodeLoc.y = mNodeHeight / 2;
mNodeLocationList.add(nodeLoc);
}
// 下方文字的中心座標
if (mTopTxtEnable && mBottomTxtEnable) {
Location bottomTxtLoc = new Location();
bottomTxtLoc.x = mWidth / 2;
bottomTxtLoc.y = mTopTxtSize + mTopTxtGap + mNodeHeight + mBottomTxtGap + mBottomTxtSize / 2;
mBottomTxtLocationList.add(bottomTxtLoc);
} else if (mBottomTxtEnable) {
Location bottomTxtLoc = new Location();
bottomTxtLoc.x = mWidth / 2;
bottomTxtLoc.y = mNodeHeight + mBottomTxtGap + mBottomTxtSize / 2;
mBottomTxtLocationList.add(bottomTxtLoc);
}
return;
}
int space = (mWidth - mRegionWidth * mNodeCount) / (mNodeCount - 1);
for (int i = 0; i < mNodeCount; i++) {
// 上方文字的中心座標
if (mTopTxtEnable) {
Location topTxtLoc = new Location();
topTxtLoc.x = mRegionWidth / 2 + i * space + i * mRegionWidth;
topTxtLoc.y = mTopTxtSize / 2;
mTopTxtLocationList.add(topTxtLoc);
}
// 節點的中心座標
if (mTopTxtEnable) {
Location nodeLoc = new Location();
nodeLoc.x = mRegionWidth / 2 + i * space + i * mRegionWidth;
nodeLoc.y = mTopTxtSize + mTopTxtGap + mNodeHeight / 2;
mNodeLocationList.add(nodeLoc);
} else {
Location nodeLoc = new Location();
nodeLoc.x = mRegionWidth / 2 + i * space + i * mRegionWidth;
nodeLoc.y = mNodeHeight / 2;
mNodeLocationList.add(nodeLoc);
}
// 下方文字的中心座標
if (mTopTxtEnable && mBottomTxtEnable) {
Location bottomTxtLoc = new Location();
bottomTxtLoc.x = mRegionWidth / 2 + i * space + i * mRegionWidth;
bottomTxtLoc.y = mTopTxtSize + mTopTxtGap + mNodeHeight + mBottomTxtGap + mBottomTxtSize / 2;
mBottomTxtLocationList.add(bottomTxtLoc);
} else if (mBottomTxtEnable) {
Location bottomTxtLoc = new Location();
bottomTxtLoc.x = mRegionWidth / 2 + i * space + i * mRegionWidth;
bottomTxtLoc.y = mNodeHeight + mBottomTxtGap + mBottomTxtSize / 2;
mBottomTxtLocationList.add(bottomTxtLoc);
}
}
}
/**
* 上方文字是否生效
*
* @param mTopTxtEnable
*/
public void setTopTxtEnable(boolean mTopTxtEnable) {
this.mTopTxtEnable = mTopTxtEnable;
invalidate();
}
/**
* 下方文字是否生效
*
* @param mBottomTxtEnable
*/
public void setBottomTxtEnable(boolean mBottomTxtEnable) {
this.mBottomTxtEnable = mBottomTxtEnable;
invalidate();
}
/**
* 設置節點信息
*
* @param mNodeList
*/
public void setNodeList(@NonNull List<Node> mNodeList) {
this.mNodeList = mNodeList;
this.mNodeCount = mNodeList.size();
invalidate();
}
/**
* 中心座標
*/
private static class Location {
int x;
int y;
}
/**
* 節點對象
*/
public static class Node {
public interface NodeState {
int UNREACHED = 1;
int REACHED = 2;
int FINISHED = 3;
int FAILED = 4;
}
public interface LineState {
int REACHED = 0;
int UNREACHED = 1;
}
// 節點上方文字
public String topTxt;
// 節點下方文字
public String bottomTxt;
// 節點狀態
public int nodeState;
// 節點後連線狀態
public int nodeAfterLineState;
}
/**
* 字體
*/
public interface TxtStyle {
int COMMON = 0;
int BOLD = 1;
int ITALIC = 2;
}
}
五.使用示例
5.1 activity_main.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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.openld.nodeprogressbar.view.NodeProgressBar
android:id="@+id/node_progress_bar1"
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginRight="8dp"
app:bottomTxtColor="#444444"
app:bottomTxtEnable="true"
app:bottomTxtGap="5dp"
app:bottomTxtSize="14sp"
app:bottomTxtStyle="common"
app:bottomWarnTxtColor="@color/colorOrange"
app:bottomWarnTxtStyle="italic"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:lineWidth="2dp"
app:nodeCount="5"
app:nodeFailed="@drawable/node_failed"
app:nodeFinished="@drawable/node_finished"
app:nodeHeight="20dp"
app:nodeRatio="0.8"
app:nodeReached="@drawable/node_reached"
app:nodeUnreached="@drawable/node_unreached"
app:nodeWidth="20dp"
app:reachedLineColor="@color/colorAccent"
app:regionWidth="48dp"
app:topTxtColor="#000000"
app:topTxtEnable="true"
app:topTxtGap="15dp"
app:topTxtSize="16sp"
app:topTxtStyle="bold"
app:unreachedLineColor="#AAAAAA" />
<com.openld.nodeprogressbar.view.NodeProgressBar
android:id="@+id/node_progress_bar2"
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginRight="8dp"
app:bottomTxtColor="#444444"
app:bottomTxtEnable="true"
app:bottomTxtGap="5dp"
app:bottomTxtSize="14sp"
app:bottomTxtStyle="common"
app:bottomWarnTxtColor="@color/colorOrange"
app:bottomWarnTxtStyle="italic"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/node_progress_bar1"
app:lineWidth="2dp"
app:nodeCount="5"
app:nodeFailed="@drawable/node_failed"
app:nodeFinished="@drawable/node_finished"
app:nodeHeight="20dp"
app:nodeRatio="0.8"
app:nodeReached="@drawable/node_reached"
app:nodeUnreached="@drawable/node_unreached"
app:nodeWidth="20dp"
app:reachedLineColor="@color/colorAccent"
app:regionWidth="48dp"
app:topTxtColor="#000000"
app:topTxtEnable="true"
app:topTxtGap="15dp"
app:topTxtSize="16sp"
app:topTxtStyle="bold"
app:unreachedLineColor="#AAAAAA" />
</androidx.constraintlayout.widget.ConstraintLayout>
5.2 MainActivity.java
package com.openld.nodeprogressbar;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import com.openld.nodeprogressbar.view.NodeProgressBar;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private NodeProgressBar mNodeProgressBar1;
private NodeProgressBar mNodeProgressBar2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNodeProgressBar1 = findViewById(R.id.node_progress_bar1);
mNodeProgressBar2 = findViewById(R.id.node_progress_bar2);
initNodeProgressBar1();
initNodeProgressBar2();
}
private void initNodeProgressBar2() {
List<NodeProgressBar.Node> nodeList = new ArrayList<>();
NodeProgressBar.Node node1 = new NodeProgressBar.Node();
node1.nodeState = NodeProgressBar.Node.NodeState.REACHED;
node1.nodeAfterLineState = NodeProgressBar.Node.LineState.REACHED;
node1.topTxt = "青銅";
node1.bottomTxt = "入門";
nodeList.add(node1);
NodeProgressBar.Node node2 = new NodeProgressBar.Node();
node2.nodeState = NodeProgressBar.Node.NodeState.REACHED;
node2.nodeAfterLineState = NodeProgressBar.Node.LineState.REACHED;
node2.topTxt = "白銀";
node2.bottomTxt = "初級";
nodeList.add(node2);
NodeProgressBar.Node node3 = new NodeProgressBar.Node();
node3.nodeState = NodeProgressBar.Node.NodeState.REACHED;
node3.nodeAfterLineState = NodeProgressBar.Node.LineState.REACHED;
node3.topTxt = "黃金";
node3.bottomTxt = "中級";
nodeList.add(node3);
NodeProgressBar.Node node4 = new NodeProgressBar.Node();
node4.nodeState = NodeProgressBar.Node.NodeState.REACHED;
node4.nodeAfterLineState = NodeProgressBar.Node.LineState.REACHED;
node4.topTxt = "鑽石";
node4.bottomTxt = "高級";
nodeList.add(node4);
NodeProgressBar.Node node5 = new NodeProgressBar.Node();
node5.nodeState = NodeProgressBar.Node.NodeState.FINISHED;
node5.nodeAfterLineState = NodeProgressBar.Node.LineState.REACHED;
node5.topTxt = "星耀";
node5.bottomTxt = "專家";
nodeList.add(node5);
mNodeProgressBar2.setNodeList(nodeList);
}
private void initNodeProgressBar1() {
List<NodeProgressBar.Node> nodeList = new ArrayList<>();
NodeProgressBar.Node node1 = new NodeProgressBar.Node();
node1.nodeState = NodeProgressBar.Node.NodeState.REACHED;
node1.nodeAfterLineState = NodeProgressBar.Node.LineState.REACHED;
node1.topTxt = "青銅";
node1.bottomTxt = "入門";
nodeList.add(node1);
NodeProgressBar.Node node2 = new NodeProgressBar.Node();
node2.nodeState = NodeProgressBar.Node.NodeState.REACHED;
node2.nodeAfterLineState = NodeProgressBar.Node.LineState.REACHED;
node2.topTxt = "白銀";
node2.bottomTxt = "初級";
nodeList.add(node2);
NodeProgressBar.Node node3 = new NodeProgressBar.Node();
node3.nodeState = NodeProgressBar.Node.NodeState.FAILED;
node3.nodeAfterLineState = NodeProgressBar.Node.LineState.REACHED;
node3.topTxt = "黃金";
node3.bottomTxt = "中級";
nodeList.add(node3);
NodeProgressBar.Node node4 = new NodeProgressBar.Node();
node4.nodeState = NodeProgressBar.Node.NodeState.UNREACHED;
node4.nodeAfterLineState = NodeProgressBar.Node.LineState.UNREACHED;
node4.topTxt = "鑽石";
node4.bottomTxt = "高級";
nodeList.add(node4);
NodeProgressBar.Node node5 = new NodeProgressBar.Node();
node5.nodeState = NodeProgressBar.Node.NodeState.UNREACHED;
node5.nodeAfterLineState = NodeProgressBar.Node.LineState.UNREACHED;
node5.topTxt = "星耀";
node5.bottomTxt = "專家";
nodeList.add(node5);
mNodeProgressBar1.setNodeList(nodeList);
}
}