实现效果主要效果分为三个部分:
1.固定蓝色的大圆弧 color borderWidth
2.可以变化的小圆弧(红色) color borderWidth
3.中间的步数文字 color textSize
drawArc方法
startAngle 确定角度的起始位置
sweepAngle 确定扫过的角度
useCenter 是否使用中心:true,连接矩形中心及弧;false不显示,(是否显示半径连线,true表示显示圆弧与圆心的半径连线,false表示不显示)(Paint.Style.FILL时)连接弧的起点终点
public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
@NonNull Paint paint) {
super.drawArc(oval, startAngle, sweepAngle, useCenter, paint);
}
顺时针,起点从135度开始扫了270度的距离到终点
左边距离=控件宽度的一半减去文字宽度的一半
具体代码
public class QQStepView extends View {
private int mOuterColor = Color.RED;
private int mInnerColor = Color.BLUE;
private int mBorderWidth = 20;//20px
private int mStepTextSize;
private int mStepTextColor;
private Paint mOutPaint, mInnerPaint, mTextPaint;
//总共的,当前的步数
private int mStepMax = 100;//默认值
private int mCurrentStep = 50;
public QQStepView(Context context) {
this(context, null);
}
public QQStepView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public QQStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//1.分析效果:
//2.确定自定义属性,编写attrs.xml
//3.在布局中使用
//4.在自定义View中获取自定义属性
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QQStepView);
mOuterColor = array.getColor(R.styleable.QQStepView_outerColor, mOuterColor);
mInnerColor = array.getColor(R.styleable.QQStepView_innerColor, mInnerColor);
mBorderWidth = (int) array.getDimension(R.styleable.QQStepView_borderWidth, mBorderWidth);
mStepTextSize = array.getDimensionPixelSize(R.styleable.QQStepView_stepTextSize, mStepTextSize);
mStepTextColor = array.getColor(R.styleable.QQStepView_stepTextColor, mStepTextColor);
array.recycle();
mOutPaint = new Paint();
mOutPaint.setAntiAlias(true);
mOutPaint.setStrokeWidth(mBorderWidth);
mOutPaint.setColor(mOuterColor);
mOutPaint.setStrokeCap(Paint.Cap.ROUND);//设置头部和尾部为圆形
mOutPaint.setStyle(Paint.Style.STROKE);//空心
mInnerPaint = new Paint();
mInnerPaint.setAntiAlias(true);
mInnerPaint.setStrokeWidth(mBorderWidth);
mInnerPaint.setColor(mInnerColor);
mInnerPaint.setStrokeCap(Paint.Cap.ROUND);//设置头部和尾部为圆形
mInnerPaint.setStyle(Paint.Style.STROKE);//空心
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(mStepTextColor);
mTextPaint.setTextSize(mStepTextSize);
//7.其他
}
//5.onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//调用者在布局文件中可能 wrap_content,这个时候你可以去判断模式如果是AT-MOST去设置一个默认值,或者抛出异常让用户不可以设置wrap_content
//这里我默认设置为100dp
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST) {
width = dip2px(getContext(), 100);
}
if (heightMode == MeasureSpec.AT_MOST) {
height = dip2px(getContext(),100);
}
//获取模式 AT_MOST 40dp
//宽高不一致 取最小值 确保是个正方形,正方向才好画圆
setMeasuredDimension(width > height ? height : width, width > height ? height : width);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//6.画外圆弧,
//区域
//getWidth和getHeight是获取自定义View的宽和高在onMeasure执行完后才能调用
int center = getWidth() / 2;
int radius = center - mBorderWidth / 2;//mBorderWidth如果不除与2的话也行
//center - radius和center + radius是考虑描边宽度的问题,如果直接是用右边getWidth和下边getHeight,左边和上边为0的话,这样画出来的圆弧会出边界
//因此我们要考虑描边宽度的问题,让他小一点,画的时候不要出界。
RectF rectF = new RectF(center - radius, center - radius, center + radius, center + radius);
//起始角度135到270顺时针扫
canvas.drawArc(rectF, 135, 270, false, mOutPaint);
//7.画内圆弧,
if (mStepMax == 0) {
//如果没设置总步数就不让他往下执行
return;
}
//不能向外圆弧一样写死 百分比 是使用者设置的从外面传
float sweepAngle = (float) mCurrentStep / mStepMax;
canvas.drawArc(rectF, 135, 270 * sweepAngle, false, mInnerPaint);
//8.画文字
String stepText = mCurrentStep + "";
Rect textBounds = new Rect();
//文字的宽度需要画笔去测量
mTextPaint.getTextBounds(stepText, 0, stepText.length(), textBounds);
int dx = getWidth() / 2 - textBounds.width() / 2;//左边距离=控件宽度的一半减去文字宽度的一半
//基线
Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
int dy = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
int baseLine = getHeight() / 2 + dy;
canvas.drawText(stepText, dx, baseLine, mTextPaint);
}
//7.其他 写几个方法动起来
public void setStepMax(int stepMax) {
this.mStepMax = stepMax;
}
public void setCurrentStep(int currentStep) {
this.mCurrentStep = currentStep;
//不断绘制 调用onDraw
invalidate();
}
}
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
xmlns:yiran="http://schemas.android.com/apk/res-auto">
<com.example.customview.customview.qqstep.QQStepView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/step_view"
yiran:outerColor="@color/mainColor"
yiran:innerColor="@color/purple_700"
yiran:borderWidth="6dp"
yiran:stepTextColor="@color/purple_700"
yiran:stepTextSize="16sp"
/>
</LinearLayout>
使用
public class ViewActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_qqstepview);
QQStepView qqStepView=(QQStepView) findViewById(R.id.step_view);
qqStepView.setStepMax(4000);//设置最大值4000
//属性动画
//1秒钟从0变化到3000
ValueAnimator valueAnimator= ObjectAnimator.ofFloat(0,3000);
valueAnimator.setDuration(1000);
valueAnimator.setInterpolator(new DecelerateInterpolator());//设置动画效果前面快后面慢一点
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentStep=(float) animation.getAnimatedValue();
qqStepView.setCurrentStep((int) currentStep);
}
});
valueAnimator.start();
}
}
效果图