转载请注明原创出处,谢谢!
- GitHub: @Ricco
横向实现逻辑
使用自定义控件,先计算得到每个圆心的x轴坐标,然后绘制出圆心,长条,文字
- 获取控件的宽度
- 通过控件的宽度减去所有边距,除以数据个数 - 1得到圆心到圆心的距离(绘制圆点和各个线段使用)
- 通过圆心,圆心与文字的边距,文字的高度,计算文字显示的基线(绘制文字使用)
- for循环,得到每个圆心的x轴
- 第一个圆点到最后一个圆心绘制灰线
- 如果比例大于0,小于等于100,每个圆点开始到百分比绘制绿线
- 如果比例大于0,绘制绿圆点,否则绘制灰圆点
- 根据圆点位置,绘制文字
StepView.java
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import androidx.annotation.Nullable;
/**
* 自定义进度view
*
* @author Fenggr
*/
public class StepView extends View {
/**
* 数据
*/
private String[] mTitles = new String[]{};
/**
* 进度
*/
private int[] mProgress = new int[]{};
/**
* 画笔
*/
private Paint paint;
/**
* 控件的宽高
*/
private int widthSize, heightSize;
/**
* 圆半径
*/
private int radius;
/**
* 每个点的圆心x位置
*/
private float[] proX;
/**
* 每段进度条的长度
*/
private float[] bgX;
/**
* 第一个和最后一个圆心的padding值
*/
private int start, end;
/**
* 颜色 【到达的圆,未到达的圆,到达的字,未到达的字,到达的线,未到达的线】
*/
private int[] colors = new int[]{0xFF09BB07, 0xFFE2E2E2, 0xFF333333, 0xFF999999, 0xFF09BB07, 0xFFE2E2E2};
/**
* 文字基线
*/
private float textBaseline;
public StepView (Context context) {
super(context);
initView();
}
public StepView (Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView() {
radius = (int) dp2px(7);
start = (int) dp2px(21);
end = (int) dp2px(21);
// 圆和文字间距
int rtPadding = (int) dp2px(3);
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(dp2px(3));
paint.setTextSize(dp2px(14));
paint.setTextAlign(Paint.Align.CENTER);
FontMetrics fm = paint.getFontMetrics();
// 文字高度
int textHeight = (int) Math.ceil(fm.bottom - fm.top);
// 圆 + 间距 + 文字高度
heightSize = radius * 2 + rtPadding + textHeight;
// 因为setTextAlign(Paint.Align.CENTER);
// textBaseline(文字基线位置) = heightSize(控件高度) - Baseline(文字基线)
// Baseline = (FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom
textBaseline = heightSize - textHeight / 2f + fm.bottom;
}
public void setData(String[] titles, int[] progress) {
// 有数据,并且长度大于1
if (titles != null && progress != null && titles.length == progress.length && titles.length > 1) {
this.mTitles = titles;
this.mProgress = progress;
proX = new float[titles.length];
bgX = new float[titles.length];
// post后执行,保证widthSize有值
post(() -> {
// 紧挨着的两个圆心的距离
float interval = (widthSize - radius * 2f - start - end) / (titles.length - 1);
for (int i = 0; i < titles.length; i++) {
// 每个圆心的位置
proX[i] = start + radius + interval * i;
// 每段背景的到达位置
if (mProgress[i] > 0 && mProgress[i] <= 100) {
bgX[i] = proX[i] + interval * mProgress[i] / 100f;
} else {
bgX[i] = proX[i];
}
}
postInvalidate();
});
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
widthSize = MeasureSpec.getSize(widthMeasureSpec);
// 设置控件的宽,高(圆+字+间隔)
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (proX == null || proX.length <= 1) {
return;
}
// 背景
// 从头到尾全灰
paint.setColor(colors[5]);
canvas.drawLine(proX[0], radius, proX[proX.length - 1], radius, paint);
// 画绿
paint.setColor(colors[4]);
for (int i = 0; i < proX.length - 1; i++) {
if (mProgress[i] > 0 && mProgress[i] <= 100) {
canvas.drawLine(proX[i], radius, bgX[i], radius, paint);
}
}
// 圆点
for (int i = 0; i < proX.length; i++) {
paint.setColor(mProgress[i] > 0 ? colors[0] : colors[1]);
canvas.drawCircle(proX[i], radius, radius, paint);
}
// 文字
for (int i = 0; i < proX.length; i++) {
paint.setColor(mProgress[i] > 0 ? colors[2] : colors[3]);
canvas.drawText(mTitles[i], proX[i], textBaseline, paint);
}
}
private static float dp2px(float dp) {
Resources r = Resources.getSystem();
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
}
}
使用方法
stepView.setData(new String[]{"已受理","操作中","已完结"}, new int{100,50,0});
ps:
- public void setData(String[] titles, int[] progress) ,使用int数组的方式,方便控制每一段的进度
2.现在的控件所以属性都是写死了,如果有需要可以自己扩展
纵向实现逻辑
使用RecyclerView通过控制item的显示隐藏即可实现
- item分左右2个部分,左部分包含(上灰色线,中灰色圆点,下灰色线,中黄色圆点),右部分为数据。其中黄色圆点大于灰色圆点,并将其盖住
- 在adapter中判断显示隐藏
第一条数据隐藏【上灰色线】,显示【黄色大圆点】
最后一条数据隐藏【下灰色线】,隐藏【黄色大圆点】
其他数据隐藏【黄色大圆点】即可
item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="32dp"
android:layout_height="match_parent"
android:layout_marginStart="5dp">
<View
android:id="@+id/vTopLine"
android:layout_width="2dp"
android:layout_height="10dp"
android:layout_centerHorizontal="true"
android:background="#CCCCCC" />
<View
android:id="@+id/vDot"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"
android:background="@drawable/shape_cccccc_1000dp" />
<View
android:id="@+id/vBottomLine"
android:layout_width="2dp"
android:layout_height="match_parent"
android:layout_below="@id/vDot"
android:layout_centerHorizontal="true"
android:background="#CCCCCC" />
<ImageView
android:id="@+id/ivDot2"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="6dp"
android:background="@drawable/shape_e39c55_1000dp"
android:contentDescription="@null"
android:src="@drawable/ic_check_white_24dp" />
</RelativeLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tvDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="5dp"
android:textSize="14sp"
tools:text="2022-03-03 15:17:32" />
<TextView
android:id="@+id/tvContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="5dp"
android:layout_marginBottom="10dp"
tools:text="xxx将订单状态操作为已派单" />
</LinearLayout>
</LinearLayout>
ativity.xml
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvLogStep"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
app:spanCount="1"
tools:itemCount="3"
tools:listitem="@layout/item" />
Adapter.java
public class LogStepAdapter extends BaseQuickAdapter<Bean, BaseViewHolder> {
public LogStepAdapter() {
super(R.layout.item);
}
@Override
protected void convert(@NonNull BaseViewHolder helper, Bean item) {
helper.setText(R.id.tvDate, "" + item.getDate())
.setText(R.id.tvContent, "" + item.getContent())
.setTextColor(R.id.tvDate, helper.getLayoutPosition() == 0 ? 0xFFE39C55 : 0xFF333333)
.setTextColor(R.id.tvContent, helper.getLayoutPosition() == 0 ? 0xFFE39C55 : 0xFF666666)
.setVisible(R.id.ivDot2, helper.getLayoutPosition() == 0) // 第一个显示黄色对勾
.setGone(R.id.vTopLine, helper.getLayoutPosition() != 0) // 第一个隐藏上面的线
.setGone(R.id.vBottomLine, helper.getLayoutPosition() != mData.size() - 1); // 最后一个隐藏下面的线
TextView tvDate = helper.getView(R.id.tvDate);
TextView tvContent = helper.getView(R.id.tvContent);
if (helper.getLayoutPosition() == 0) {
tvDate.setTextSize(14);
tvContent.setTextSize(14);
} else {
tvDate.setTextSize(12);
tvContent.setTextSize(12);
}
}
}