效果图如下:
public class LineCircle extends View {
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Path path = new Path();
/**
* 线的颜色
*/
private int mPathColor = Color.parseColor("#eeeeee");
/**
* 外圆的颜色
*/
private int outerCircleColor = Color.parseColor("#96ccff");
/**
* 内圆的颜色
*/
private int innerCircleColor = Color.parseColor("#2988e2");
/**
* 外圆半径
*/
private int outRadius = Utils.dp2px(5f).intValue();
/**
* 内圆半径
*/
private int inRadius = Utils.dp2px(2.5f).intValue();
/**
* 通过 {@link #add(int)} 添加位置的圆的半径
*/
private int listRadius = Utils.dp2px(2.5f).intValue();
/**
* 是否为头部
*/
private boolean mHead = false;
/**
* 是否发为尾部
*/
private boolean mEnd = false;
/**
* 是否选中
*/
private boolean isSelect = false;
//偏移
private int offsetY;
/**
* 默认的圆在高度上的百分比
*/
private float mScale = 0.3f;
/**
* 原点 y 轴位置
*/
private ArrayList<Integer> mList = new ArrayList<>();
public LineCircle(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
int width = getWidth();
// 默认圆的高度
int scaleHeight;
if (offsetY == 0) {
scaleHeight = (int) (getHeight() * mScale);
} else {
scaleHeight = offsetY;
}
int center = width / 2;
paint.setColor(mPathColor);
paint.setStrokeWidth(Utils.dp2px(1f));
path.moveTo(width / 2, 50);
path.lineTo(width / 2, getHeight());
//画线
if (mHead) {
canvas.drawLine(center, scaleHeight, center, getHeight(), paint);
} else if (mEnd) {
if (mList.size() > 0 && mList.get(mList.size() - 1) > scaleHeight) {
canvas.drawLine(center, 0, center, mList.get(mList.size() - 1), paint);
} else {
canvas.drawLine(center, 0, center, scaleHeight, paint);
}
} else {
canvas.drawLine(center, 0, center, getHeight(), paint);
}
//画圆
if (isSelect) {
paint.setColor(outerCircleColor);
canvas.drawCircle(center, scaleHeight, outRadius, paint);
paint.setColor(innerCircleColor);
canvas.drawCircle(center, scaleHeight, inRadius, paint);
} else {
paint.setColor(mPathColor);
canvas.drawCircle(center, scaleHeight, listRadius, paint);
}
if (mList.size() > 0) {
for (int i = 0; i < mList.size(); i++) {
paint.setColor(mPathColor);
canvas.drawCircle(center, mList.get(i), listRadius, paint);
}
}
}
/**
* 添加别的圆点
* 需传入具体的高度,以像素为单位
*
* 全部添加完成后需调用 {@link #addSave()} ,
* 注意,需按顺序添加,从小往大
*
*
* @param offsetY 偏移
*/
public void add(int offsetY) {
mList.add(offsetY);
}
public void add(List<Integer> offsetY) {
mList.clear();
mList.addAll(offsetY);
}
/**
* 添加完成后执行
*/
public void addSave() {
invalidate();
}
public void clear() {
mList.clear();
}
/**
* 是否为第一个
*/
public void setHead(boolean head) {
this.mHead = head;
invalidate();
}
/**
* 是否为最后一个
*
* @param end
*/
public void setEnd(boolean end) {
this.mEnd = end;
invalidate();
}
/**
* 设置线的颜色
*
* @param color
*/
public void pathColor(int color) {
this.mPathColor = color;
invalidate();
}
/**
* 设置圆高度的百分比
*
* @param mScale
*/
public void setScale(float mScale) {
this.mScale = mScale;
invalidate();
}
/**
* 是否选中默认的圆。
* 高度使用 {@link #setSelect(boolean)} 进行设置
*
* @param isSelect
*/
public void setSelect(boolean isSelect) {
setSelect(isSelect, 0);
}
/**
* 是否选中默认的圆,以及对应的位置
*
* @param isSelect
*/
public void setSelect(boolean isSelect, int offsetY) {
this.isSelect = isSelect;
this.offsetY = offsetY;
invalidate();
}
}
布局如下:
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat 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:orientation="horizontal">
<com.fengtong.core.ui.view.LineCircle
android:id="@+id/record_line"
android:layout_width="15dp"
android:layout_height="match_parent"
android:layout_marginStart="18dp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/record_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/_666666"
android:textSize="12sp"
tools:ignore="HardcodedText"
tools:text="2020-06-17" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/record_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/record_date"
android:orientation="vertical"
android:paddingBottom="20dp">
</androidx.appcompat.widget.LinearLayoutCompat>
</RelativeLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
使用:
1,需要配合 recyclerView 使用。也可单独使用
2,调用 setSelect 方法设置是否选中默认的,以及默认圆的位置,可通过百分比,或者 具体的Y轴值来设置。
3,通过调用 add 方法添加其他的 圆点 ,需要传入Y 轴位置。
下面是在 recyclerView 中使用的:
//当前条目的类型数据
String tag = entity.getField(MultipleFields.TAG);
//当前条目的数据
String date = entity.getField(MultipleFields.DATE);
List<String> list = entity.getField(MultipleFields.LIST);
//拿到条目中对应的控件
AppCompatTextView dateTv = holder.getView(R.id.record_date);
LineCircle line = holder.getView(R.id.record_line);
LinearLayoutCompat layout = holder.getView(R.id.record_layout);
//设置值
dateTv.setText(date);
//添加dateTv 初始化完成的监听
dateTv.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
// 计算dateTv 的位置
int offsetY = dateTv.getTop() + (dateTv.getHeight() / 2);
//设置默认圆的位置,以及选中。也可单独设置选中,通过 setScale 设置百分比,从 0 到 1
line.setSelect(true, offsetY);
});
//判断条目是否为 第一个 或者最后一个
if (tag.equals("head")) {
line.setHead(true);
} else if (tag.equals("end")) {
line.setEnd(true);
}
//遍历数据,动态的创建 View 并进行添加的指定的 layout 中。
//然后计算 view 的位置,通过 add 方法进行设置。
//最后需调用 addSave
for (int i = 0; i < list.size(); i++) {
AppCompatTextView textView = (AppCompatTextView) LayoutInflater.from(holder.itemView.getContext())
.inflate(R.layout.layout_record_text, layout, false);
textView.setText(list.get(i));
layout.addView(textView);
textView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
//拿到中心点
int top = textView.getTop() + layout.getTop() + (textView.getHeight() / 2);
//绘制圆点
line.add(top);
});
line.addSave();
}