自定义view-流程进度条(动态调控)
上一篇完成对固定4流程步骤进度图的编码,但是在实际生活中,可能地区业务的不同导致我们的认证流程的不同,我们也不可能针对每个地区都来编码一个对应的流程进度图,那样工作第一很大,第二完全是在浪费时间,所以这次我们来尝试是否能对其修改升级为动态调控的进度图
一、整理思路和逻辑
- 第一点我们要搞清楚的是,多几个流程步骤只是在固定宽高的区域内绘制增删我们的步骤,所以我们只需要动态绘制这些点位及对应状态点位上的显示图片和对应点位的文字信息(核心就是计算对应流程位置所在点)
- 增加一些核心的属性(颜色等不做分析):
1)总步骤数量stepCount
2)默认流程步骤数defaultStepCount
,我给出的默认值为6,当然也可以减少,最少两个步骤哦!
二、剖析view绘制过程
上面已经阐述了我们重要的绘制关键点,下面我们来对其进行进一步实施
- 基线绘制:
1)最底层的也就是最细那条基线我们不需要考虑,简单粗暴就是从view绘制区域从开头会绘制到尾部;
2)绘制粗线条:其必定是从绘制区域起点开始绘制,所以我们只需要计算出当前step的下一个step的X轴位置就行了,前提是step<stepCount
,若step>=stepCount
则计算当前setp在X轴的位置,Y轴方向的位置不变,上篇中的绘制区域宽为W(以下用的变量简称全部为上篇文章中代码定义的变量名,后面不再做提示),根据线段平分的一些特性得出:x = w * step * 1f / (stepCount - (step < stepCount ? 1 : 0))
这样我们就一行代码就绘制出了对应情况下的粗线条 - 因为有多于或少于默认值的状态效果图,所以我们无法像上次那样固定我们绘制方法,所以只能用for循环来绘制每一个流程步点当前状态下该显示什么样的图片(其实我是非常排斥的,只想用java8中stream,但由于api的限制木有办法)
- 流程状态图绘制:
在for循环中,假设当前该绘制第i
个流程步骤的显示:
1)首先我们先判断当前步骤step
是大于等于i
即step>=i
,若为真,该状态点则显示✅+外圈圆
;若为假,则有两种图片显示,我们需要进一步来进行判断,见下;
2)在step>=i
为假且step=i-1
为真即:step<1&&step=i-1
,那我们将绘制正在进行的原点外圈图片;
3)在step>=i
为假且step=i-1为假即:step<1&&step!=i-1
,那我们将绘制未完成❎的图片; - 图片位子的计算:
在绘制图片时,我们采用的是图片左侧距离和顶部距离值来确定图片的具体位置,而在Y轴方向上的顶部距离是不变的,所以我们仍然只考虑X轴方向距离的动态计算,参考上面我们粗线条的计算得出:w * 1f * (i - 1) / (stepCount - 1) - 对应图片X轴方向的半径
,在此需要提示一下,下面代码中用到maxImgXRadio
,其实就是我们最大尺寸图片X轴方向的半径,因此我们的计算结论w * 1f * (i - 1) / (stepCount - 1) - 对应图片X轴方向的半径
是没有问题,两个值等价替换。
三、编码
又到了大家最开心的编码部分了,根据上面的分析流程我们来进行编码
-
由于基线不变,直接进行绘制粗线条部分,由上编码可得:
@Override protected void onDraw(Canvas canvas) { int w = getWidth() - getPaddingLeft() - getPaddingRight() - maxImgXRadio * 2; int h = getHeight() - getPaddingTop() - getPaddingBottom(); canvas.translate(getPaddingLeft() + maxImgXRadio, getPaddingTop()); drawBaseLine(w, h, canvas); } private void drawBaseLine(int w, int h, Canvas canvas) { mPaint.reset(); mPaint.setColor(baseLineColor); canvas.drawLine(0, completeTop(h, null), w, completeTop(h, null), mPaint); mPaint.setStrokeWidth(10); //动态绘制部分,一行代码解决再也不用分段来 canvas.drawLine(0, completeTop(h, null), w * step * 1f / (stepCount - (step < stepCount ? 1 : 0)), completeTop(h, null), mPaint); mPaint.reset(); }
我们来看一下效果图,运行参数设置为(一下运行编码效果均以此参数运行来展示):
stepCount=7;step=5
-
接下来算是我们的重中之重啦,根据上面分析的来绘制,对应的状态图,定义方法
private void drawStepStateImg(Canvas canvas, int w, int h)
,代码为:@Override protected void onDraw(Canvas canvas) { int w = getWidth() - getPaddingLeft() - getPaddingRight() - maxImgXRadio * 2; int h = getHeight() - getPaddingTop() - getPaddingBottom(); canvas.translate(getPaddingLeft() + maxImgXRadio, getPaddingTop()); drawBaseLine(w, h, canvas); drawStepStateImg(canvas, w, h); } private void drawStepStateImg(Canvas canvas, int w, int h) { for (int i = 1; i <= stepCount; i++) { if (step < i) { if (step == (i - 1)) { canvas.drawBitmap(certification_ing_img, w * 1f * (i - 1) / (stepCount - 1) - maxImgXRadio, completeTop(h, certification_ing_img), mPaint); } else { canvas.drawBitmap(certification_not_finish_img, w * 1f * (i - 1) / (stepCount - 1) - certification_not_finish_img.getWidth() / 2.0f, completeTop(h, certification_not_finish_img), mPaint); } } else { canvas.drawBitmap(certification_ing_img, w * 1f * (i - 1) / (stepCount - 1) - maxImgXRadio, completeTop(h, certification_ing_img), mPaint); canvas.drawBitmap(certification_finish_img, w * 1f * (i - 1) / (stepCount - 1) - certification_finish_img.getWidth() / 2.0f, completeTop(h, certification_finish_img), mPaint); } } }
运行效果为:
-
最后绘制部分应该是流程提示文字:
1)在此应为是动态设置了,为了更好的伸缩性,我们将存储文字的数组改为可变长的集合,同样存储文字长度的数组也要更改为集合,并在自定义属性中增加文字数组的属性stepTextContent
,所以我们更改初始化方法init(Context context, AttributeSet attrs, int defStyleAttr)
中关于计算文字长度的部分为:stepTextContent = new ArrayList<>(); stepTextLength = new ArrayList<>(); if (textArray == null) { for (int i = 1; i <= stepCount; i++) { stepTextContent.add("Step " + i); stepTextLength.add(mTextPaint.measureText("Step " + i)); } } else { for (int i = 0; i < stepCount; i++) { stepTextContent.add(textArray[i].toString()); stepTextLength.add(mTextPaint.measureText(textArray[i].toString())); } }
2)接下来是我们绘制文字部分如下:
@Override protected synchronized void onDraw(Canvas canvas) { int w = getWidth() - getPaddingLeft() - getPaddingRight() - maxImgXRadio * 2; int h = getHeight() - getPaddingTop() - getPaddingBottom(); canvas.translate(getPaddingLeft() + maxImgXRadio, getPaddingTop()); drawBaseLine(w, h, canvas); drawStepStateImg(canvas, w, h); drawStepText(w, h, canvas); } private void drawStepText(int w, int h, Canvas canvas) { Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics(); mTextPaint.setColor(textColor); for (int i = 0; i < stepTextContent.size(); i++) { canvas.drawText(stepTextContent.get(i), i * 1.0f * w / (stepCount - 1) - stepTextLength.get(i) / 2.0f, textHeight * 3 / 2.0f - fontMetrics.bottom, mTextPaint); } }
先看一下不给出字符数组资源情况下效果:
在看一下各处数组资源1、2、3、4、5、6、7
情况下的效果:
四、动态设定stepCount
和stepTextContent
之前设置的都是在布局XML文件配置的,要想从代码角度来动态设置还需要我们进行一些改造如下:
-
设置路程数量及文字资源
public void setStepTextContent(@NonNull List<String> paramList) { this.stepCount = paramList.size(); if (stepCount < 2) { throw new IllegalArgumentException("参数长度异常,长度至少为2"); } stepTextContent.clear(); stepTextLength.clear(); this.stepTextContent.addAll(paramList); for (int i = 0; i < stepCount; i++) { stepTextLength.add(mTextPaint.measureText(stepTextContent.get(i))); } postInvalidate(); }
-
更改设置当前流程方法:
public void setStep(int step) { if (step > stepCount || step < 0) { throw new IllegalArgumentException("超出步骤范围"); } this.step = step; postInvalidate(); }
五、增加流式编程操作
每当用到for循环总有一种厌恶的心情油然而生,虽然在Android中对stream操作还有api的限制,但是阻挡不住我要增加strem编程的一些操作;
-
在
init(Context context, AttributeSet attrs, int defStyleAttr)
方法中在相应的api版本中使用strem编程如下:if (textArray == null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { IntStream.rangeClosed(1, stepCount) .forEach(value -> { stepTextContent.add("Step " + value); stepTextLength.add(mTextPaint.measureText("Step " + value)); }); } else { for (int i = 1; i <= stepCount; i++) { stepTextContent.add("Step " + i); stepTextLength.add(mTextPaint.measureText("Step " + i)); } } } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { IntStream.range(0, stepCount) .forEach(value -> { stepTextContent.add(textArray[value].toString()); stepTextLength.add(mTextPaint.measureText(textArray[value].toString())); }); } else { for (int i = 0; i < stepCount; i++) { stepTextContent.add(textArray[i].toString()); stepTextLength.add(mTextPaint.measureText(textArray[i].toString())); } } }
-
在方法
drawStepStateImg(Canvas canvas, int w, int h)
中将for循环在对应api版本中改为stream操作如下:private void drawStepStateImg(Canvas canvas, int w, int h) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { IntStream.rangeClosed(1, stepCount) .forEach(value -> { if (step < value) { if (step == (value - 1)) { canvas.drawBitmap(certification_ing_img, w * 1f * (value - 1) / (stepCount - 1) - maxImgXRadio, completeTop(h, certification_ing_img), mPaint); } else { canvas.drawBitmap(certification_not_finish_img, w * 1f * (value - 1) / (stepCount - 1) - certification_not_finish_img.getWidth() / 2.0f, completeTop(h, certification_not_finish_img), mPaint); } } else { canvas.drawBitmap(certification_ing_img, w * 1f * (value - 1) / (stepCount - 1) - maxImgXRadio, completeTop(h, certification_ing_img), mPaint); canvas.drawBitmap(certification_finish_img, w * 1f * (value - 1) / (stepCount - 1) - certification_finish_img.getWidth() / 2.0f, completeTop(h, certification_finish_img), mPaint); } }); } else { for (int i = 1; i <= stepCount; i++) { if (step < i) { if (step == (i - 1)) { canvas.drawBitmap(certification_ing_img, w * 1f * (i - 1) / (stepCount - 1) - maxImgXRadio, completeTop(h, certification_ing_img), mPaint); } else { canvas.drawBitmap(certification_not_finish_img, w * 1f * (i - 1) / (stepCount - 1) - certification_not_finish_img.getWidth() / 2.0f, completeTop(h, certification_not_finish_img), mPaint); } } else { canvas.drawBitmap(certification_ing_img, w * 1f * (i - 1) / (stepCount - 1) - maxImgXRadio, completeTop(h, certification_ing_img), mPaint); canvas.drawBitmap(certification_finish_img, w * 1f * (i - 1) / (stepCount - 1) - certification_finish_img.getWidth() / 2.0f, completeTop(h, certification_finish_img), mPaint); } } } }
-
在绘制文字方法
drawStepText(int w, int h, Canvas canvas)
中将for循环在对应api版本中改为stream操作如下:private void drawStepText(int w, int h, Canvas canvas) { Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics(); mTextPaint.setColor(textColor); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { IntStream.range(0, stepTextContent.size()) .forEach(value -> canvas.drawText(stepTextContent.get(value), value * 1.0f * w / (stepCount - 1) - stepTextLength.get(value) / 2.0f, textHeight * 3 / 2.0f - fontMetrics.bottom, mTextPaint)); } else { for (int i = 0; i < stepTextContent.size(); i++) { canvas.drawText(stepTextContent.get(i), i * 1.0f * w / (stepCount - 1) - stepTextLength.get(i) / 2.0f, textHeight * 3 / 2.0f - fontMetrics.bottom, mTextPaint); } } }
-
在动态设置方法
void setStepTextContent(@NonNull List<String> stepTextContent)
中将for循环在对应api版本中改为stream操作如下:public void setStepTextContent(@NonNull List<String> paramList) { this.stepCount = paramList.size(); if (stepCount < 2) { throw new IllegalArgumentException("参数长度异常,长度至少为2"); } stepTextContent.clear(); stepTextLength.clear(); this.stepTextContent.addAll(paramList); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { IntStream.range(0, stepCount) .forEach(value -> stepTextLength.add(mTextPaint.measureText(stepTextContent.get(value))) ); } else { for (int i = 0; i < stepCount; i++) { stepTextLength.add(mTextPaint.measureText(stepTextContent.get(i))); } } postInvalidate(); }
六、测试
由于是动态的所以我们用EditText来动态输入当前流程步骤
-
布局文件
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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=".ui.act.ViewActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/img_bg" android:orientation="horizontal"> <vip.zhuahilong.mydefineview.widget.CertificationProgress android:id="@+id/certificationProgress" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:padding="10dp" app:textColor="@color/white" app:textSize="10dp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:orientation="vertical" android:layout_height="wrap_content"> <LinearLayout android:layout_width="match_parent" android:orientation="horizontal" android:layout_height="wrap_content"> <android.support.design.widget.TextInputLayout android:layout_width="0dp" android:layout_weight="1" android:layout_height="?actionBarSize" android:layout_marginTop="20dp" android:orientation="horizontal"> <EditText android:id="@+id/stepCountEdit" android:layout_width="match_parent" android:layout_height="?actionBarSize" android:gravity="center" android:hint="输入StepCount"/> </android.support.design.widget.TextInputLayout> <android.support.design.widget.TextInputLayout android:layout_width="0dp" android:layout_weight="1" android:layout_height="?actionBarSize" android:layout_marginTop="20dp" android:orientation="horizontal"> <EditText android:id="@+id/nextStepEdit" android:layout_width="match_parent" android:layout_height="?actionBarSize" android:gravity="center" android:hint="输入NextStep"/> </android.support.design.widget.TextInputLayout> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:orientation="horizontal" android:gravity="center_horizontal" android:layout_height="wrap_content"> <Button android:id="@+id/stepCount" android:layout_width="0dp" android:layout_weight="1" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:layout_gravity="center_horizontal" android:layout_height="?actionBarSize" android:gravity="center" android:text="stepCount" android:textAllCaps="false" android:textSize="18sp" /> <Button android:id="@+id/nextStep" android:layout_width="0dp" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:layout_weight="1" android:layout_gravity="center_horizontal" android:layout_height="?actionBarSize" android:gravity="center" android:text="nextStep" android:textAllCaps="false" android:textSize="18sp" /> </LinearLayout> </LinearLayout> </RelativeLayout>
-
activity文件:
public class ViewActivity extends BaseActivity { @BindView(R.id.certificationProgress) CertificationProgress mCertificationProgress; @BindView(R.id.nextStepEdit) EditText nextStepEdit; @BindView(R.id.stepCountEdit) EditText stepCountEdit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_view); } @OnClick({R.id.nextStep,R.id.stepCount}) public void clickMethod(View view) { if (view.getId() == R.id.stepCount) { String stepCountStr = stepCountEdit.getText().toString(); if (!TextUtils.isEmpty(stepCountStr)) { try { int sum = Integer.parseInt(stepCountStr); List<String> stringList = new ArrayList<>(); for (int i = 1; i <= sum; i++) { stringList.add("Step " + i + " "); } mCertificationProgress.setStepTextContent(stringList); } catch (Exception e) { new AlertDialog.Builder(this) .setMessage(e.getMessage()) .show(); } } } else { String nextStepStr = nextStepEdit.getText().toString(); if (!TextUtils.isEmpty(nextStepStr)) { try { mCertificationProgress.setStep(Integer.parseInt(nextStepStr)); } catch (Exception e) { new AlertDialog.Builder(this) .setMessage(e.getMessage()) .show(); } } } } }
-
运行效果(以默认值显示绘制的):
1)测试默认步骤流程进度图,效果为:
2)step=[1,6] 、step=0、step<0、step>0
效果如下step=1 step=2 step=3 step=4 step=5 step=6 step=0 step<0 step>6
4)由于流程步数太多我们抽几个step测试,step取值范围为:step=[1,10] 、step=0、step<0、step>0
,效果如图:
|
|
|
|
|
|
|
|
|
5)上面测试了stepCount=10
是大于默认值,现在我们来测试小于默认值的,例如:stepCount=3
,效果如图:
6)step取值范围为:step=[1,3] 、step=0、step<0、step>0
,效果如图:
|
|
|
|
|
|
七、完整代码
至此我们升级版本就完成了,好累。。。。。,如果不严谨或者错误的地方请各位大佬留言指出谢谢!以下贴出代码:
-
自定义属性xml文件
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="CertificationProgress"> <attr name="textSize" format="dimension" /> <attr name="textColor" format="color" /> <attr name="baseLineColor" format="color" /> <attr name="stepCount" format="integer" /> <attr name="stepTextContent" format="string" /> </declare-styleable> </resources>
-
code
/** * @author :朱海龙 * @date :Created in 2019-06-21 15:19 * @description:关于认证进度等 * @modified By: * @version: */ public class CertificationProgress extends View { /** * 字体默认大小 */ public static final int defaultTextSize = 30; /** * 字体默认颜色 */ public static final int defaultTextColor = Color.BLACK; /** * 基线默认颜色 */ public static final int defaultBaseLineColor = Color.WHITE; /** * 默认操作流程步骤数量 */ public static final int defaultStepCount = 6; /** * 文字画笔 */ private TextPaint mTextPaint; /** * 文字大小 */ private int textSize; /** * 文字颜色 */ private int textColor; /** * 文字高度 */ private int textHeight; /** * 基线颜色 */ private int baseLineColor; /** * 通用画笔 */ private Paint mPaint; /** * 流程完成提示图 */ private Bitmap certification_finish_img; /** * 流程进行中提示图 */ private Bitmap certification_ing_img; /** * 流程未完成提示图 */ private Bitmap certification_not_finish_img; /** * maxImgYRadio : 图片中Y轴方向最大半径 * maxImgXRadio : 图片中X轴方向最大半径 */ private int maxImgYRadio, maxImgXRadio; /** * 当前step,流程的完成度 */ @Size(min = 0) private int step = 0; /** * 流程步骤总数 */ private int stepCount; /** * 流程步骤 */ private List<String> stepTextContent; /** * 流程步骤文字长度 */ private List<Float> stepTextLength; public CertificationProgress(Context context) { super(context); init(context, null, 0); } public CertificationProgress(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context, attrs, 0); } public CertificationProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr); } public void init(Context context, AttributeSet attrs, int defStyleAttr) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CertificationProgress); textSize = (int) typedArray.getDimension(R.styleable.CertificationProgress_textSize, defaultTextSize); textColor = typedArray.getColor(R.styleable.CertificationProgress_textColor, defaultTextColor); baseLineColor = typedArray.getColor(R.styleable.CertificationProgress_baseLineColor, defaultBaseLineColor); stepCount = typedArray.getInteger(R.styleable.CertificationProgress_stepCount, defaultStepCount); CharSequence[] textArray = typedArray.getTextArray(R.styleable.CertificationProgress_stepTextContent); typedArray.recycle(); mPaint = new Paint(); mTextPaint = new TextPaint(); mTextPaint.setTextSize(textSize); stepTextContent = new ArrayList<>(); stepTextLength = new ArrayList<>(); Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics(); textHeight = (int) (fontMetrics.bottom - fontMetrics.top); if (textArray == null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { IntStream.rangeClosed(1, stepCount) .forEach(value -> { stepTextContent.add("Step " + value); stepTextLength.add(mTextPaint.measureText("Step " + value)); }); } else { for (int i = 1; i <= stepCount; i++) { stepTextContent.add("Step " + i); stepTextLength.add(mTextPaint.measureText("Step " + i)); } } } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { IntStream.range(0, stepCount) .forEach(value -> { stepTextContent.add(textArray[value].toString()); stepTextLength.add(mTextPaint.measureText(textArray[value].toString())); }); } else { for (int i = 0; i < stepCount; i++) { stepTextContent.add(textArray[i].toString()); stepTextLength.add(mTextPaint.measureText(textArray[i].toString())); } } } certification_finish_img = BitmapFactory.decodeResource(getResources(), R.drawable.certification_finish); certification_ing_img = BitmapFactory.decodeResource(getResources(), R.drawable.certification_ing); certification_not_finish_img = BitmapFactory.decodeResource(getResources(), R.drawable.certification_not_finish); maxImgYRadio = Math.max(certification_ing_img.getHeight(), Math.max(certification_finish_img.getHeight(), certification_not_finish_img.getHeight())) >> 1; maxImgXRadio = Math.max(certification_ing_img.getWidth(), Math.max(certification_not_finish_img.getWidth(), certification_finish_img.getWidth())) >> 1; } @Override protected synchronized void onDraw(Canvas canvas) { int w = getWidth() - getPaddingLeft() - getPaddingRight() - maxImgXRadio * 2; int h = getHeight() - getPaddingTop() - getPaddingBottom(); canvas.translate(getPaddingLeft() + maxImgXRadio, getPaddingTop()); drawBaseLine(w, h, canvas); drawStepStateImg(canvas, w, h); drawStepText(w, h, canvas); } /** * 绘制流程对应的状态图 * @param canvas * @param w * @param h */ private void drawStepStateImg(Canvas canvas, int w, int h) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { IntStream.rangeClosed(1, stepCount) .forEach(value -> { if (step < value) { if (step == (value - 1)) { canvas.drawBitmap(certification_ing_img, w * 1f * (value - 1) / (stepCount - 1) - maxImgXRadio, completeTop(h, certification_ing_img), mPaint); } else { canvas.drawBitmap(certification_not_finish_img, w * 1f * (value - 1) / (stepCount - 1) - certification_not_finish_img.getWidth() / 2.0f, completeTop(h, certification_not_finish_img), mPaint); } } else { canvas.drawBitmap(certification_ing_img, w * 1f * (value - 1) / (stepCount - 1) - maxImgXRadio, completeTop(h, certification_ing_img), mPaint); canvas.drawBitmap(certification_finish_img, w * 1f * (value - 1) / (stepCount - 1) - certification_finish_img.getWidth() / 2.0f, completeTop(h, certification_finish_img), mPaint); } }); } else { for (int i = 1; i <= stepCount; i++) { if (step < i) { if (step == (i - 1)) { canvas.drawBitmap(certification_ing_img, w * 1f * (i - 1) / (stepCount - 1) - maxImgXRadio, completeTop(h, certification_ing_img), mPaint); } else { canvas.drawBitmap(certification_not_finish_img, w * 1f * (i - 1) / (stepCount - 1) - certification_not_finish_img.getWidth() / 2.0f, completeTop(h, certification_not_finish_img), mPaint); } } else { canvas.drawBitmap(certification_ing_img, w * 1f * (i - 1) / (stepCount - 1) - maxImgXRadio, completeTop(h, certification_ing_img), mPaint); canvas.drawBitmap(certification_finish_img, w * 1f * (i - 1) / (stepCount - 1) - certification_finish_img.getWidth() / 2.0f, completeTop(h, certification_finish_img), mPaint); } } } } /** * 绘制流程文字 * @param w * @param h * @param canvas */ private void drawStepText(int w, int h, Canvas canvas) { Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics(); mTextPaint.setColor(textColor); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { IntStream.range(0, stepTextContent.size()) .forEach(value -> canvas.drawText(stepTextContent.get(value), value * 1.0f * w / (stepCount - 1) - stepTextLength.get(value) / 2.0f, textHeight * 3 / 2.0f - fontMetrics.bottom, mTextPaint)); } else { for (int i = 0; i < stepTextContent.size(); i++) { canvas.drawText(stepTextContent.get(i), i * 1.0f * w / (stepCount - 1) - stepTextLength.get(i) / 2.0f, textHeight * 3 / 2.0f - fontMetrics.bottom, mTextPaint); } } } /** * 绘制baseline * @param w * @param h * @param canvas */ private void drawBaseLine(int w, int h, Canvas canvas) { mPaint.reset(); mPaint.setColor(baseLineColor); canvas.drawLine(0, completeTop(h, null), w, completeTop(h, null), mPaint); mPaint.setStrokeWidth(10); canvas.drawLine(0, completeTop(h, null), w * step * 1f / (stepCount - (step < stepCount ? 1 : 0)), completeTop(h, null), mPaint); mPaint.reset(); } /** * 计算top距离 * @param h * @param bitmap * @return */ private float completeTop(int h, Bitmap bitmap) { return (h + textHeight * 2 - (bitmap == null ? 0 : bitmap.getHeight())) / 2.0f; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int setWidth, setHeight; int widthMode = MeasureSpec.getMode(widthMeasureSpec); int measureWidth = MeasureSpec.getSize(widthMeasureSpec); DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); if (widthMode == MeasureSpec.EXACTLY) { setWidth = measureWidth; } else { setWidth = displayMetrics.widthPixels; } int heightMode = MeasureSpec.getMode(heightMeasureSpec); int measureHeight = MeasureSpec.getSize(heightMeasureSpec); if (heightMode == MeasureSpec.EXACTLY) { setHeight = measureHeight; } else { int h = textHeight * 2 + (maxImgYRadio << 1) + getPaddingTop() + getPaddingBottom(); setHeight = Math.min(h, measureHeight); } setMeasuredDimension(setWidth, setHeight); } /** * 设置流程的完成度 * @param step */ public synchronized void setStep(int step) { if (step > stepCount || step < 0) { throw new IllegalArgumentException("超出步骤范围"); } this.step = step; postInvalidate(); } /** * 动态设置流程步骤 * @param paramList */ public synchronized void setStepTextContent(@NonNull List<String> paramList) { this.stepCount = paramList.size(); if (stepCount < 2) { throw new IllegalArgumentException("参数长度异常,长度至少为2"); } stepTextContent.clear(); stepTextLength.clear(); this.stepTextContent.addAll(paramList); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { IntStream.range(0, stepCount) .forEach(value -> stepTextLength.add(mTextPaint.measureText(stepTextContent.get(value))) ); } else { for (int i = 0; i < stepCount; i++) { stepTextLength.add(mTextPaint.measureText(stepTextContent.get(i))); } } postInvalidate(); } }