自定义View - 仿QQ运动步数进度效果

95 阅读2分钟

学习来源地址

一、实现效果

ezgif.com-video-to-gif-converted.gif

二、实现

1.自定义view组成:

1.外圆弧: 属性:宽度、颜色

2.内圆弧 属性:宽度、颜色

3.文字 属性:文字大小、文字颜色

<declare-styleable name="QQStepView">
    <!-- 外圆弧颜色-->
    <attr name="outerColor" format="color"/>
    <!-- 内圆弧颜色-->
    <attr name="innerColor" format="color"/>
    <!-- 圆弧宽度-->
    <attr name="borderWidth" format="dimension"/>
    <!-- 文字大小-->
    <attr name="stepTextSize" format="dimension"/>
    <!-- 文字颜色-->
    <attr name="stepTextColor" format="color"/>
</declare-styleable>

2.java代码实现自定义view

1.获取自定义属性值

2.onMeasure方法确认宽高

3.onDraw绘制view

4.提供方法外部设置当前步数

public class QQStepView extends View {

    private static final int START_ANGLE = 135;
    private static final int MAX_SWEEP_ANGLE = 270;
    private int mOuterColor = Color.RED;
    private int mInnerColor = Color.BLACK;
    private int mBorderWidth = SizeUtils.dip2px(getContext(), 15); // 20px
    private int mStepTextSize = SizeUtils.sp2px(getContext(), 14);
    private int mStepTextColor = Color.GREEN;
    private Paint mOutPaint, mInnerPaint, mTextPaint;
    // 绘制圆弧的范围
    private RectF mRectF;
    private  int mStepMax = 1000;
    private int mCurrentStep = 0;
    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);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QQStepView);
        mOuterColor = array.getColor(R.styleable.QQStepView_outerColor, mOuterColor);
        mInnerColor = array.getColor(R.styleable.QQStepView_innerColor, mInnerColor);
        // 假如xml设置宽度14dp,这里获取到是转换后的px
        mBorderWidth = (int) array.getDimension(R.styleable.QQStepView_borderWidth, mBorderWidth);
        mStepTextColor = array.getColor(R.styleable.QQStepView_stepTextColor, mStepTextColor);
        // 假如xml设置字体14sp,这里获取到是转换后的px
        mStepTextSize = (int) array.getDimension(R.styleable.QQStepView_stepTextSize, mStepTextSize);
        array.recycle();

        // 1.外圆弧画笔
        mOutPaint = new Paint();
        mOutPaint.setStrokeWidth(mBorderWidth);
        mOutPaint.setColor(mOuterColor);
        mOutPaint.setAntiAlias(true);
        // 圆角
        mOutPaint.setStrokeCap(Paint.Cap.ROUND);
        mOutPaint.setStyle(Paint.Style.STROKE);

        // 1.内圆弧画笔
        mInnerPaint = new Paint();
        mInnerPaint.setStrokeWidth(mBorderWidth);
        mInnerPaint.setColor(mInnerColor);
        mInnerPaint.setAntiAlias(true);
        // 圆角
        mInnerPaint.setStrokeCap(Paint.Cap.ROUND);
        mInnerPaint.setStyle(Paint.Style.STROKE);

        // 3.文字画笔
        mTextPaint = new Paint();
        mTextPaint.setTextSize(mStepTextSize);
        mTextPaint.setColor(mStepTextColor);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int minSize;
        if (widthMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.AT_MOST) {
            // 只要有一个mode是wrap_content, 则默认宽高是40dp
            minSize = SizeUtils.dip2px(getContext(), 40);
        } else {
            // 宽高不一致
            int width = MeasureSpec.getSize(widthMeasureSpec);
            int height = MeasureSpec.getSize(widthMeasureSpec);
            minSize = Math.min(width, height);
        }
        setMeasuredDimension(minSize, minSize);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // 画圆弧的区域 (mBorderWidth / 2)的作用是保证圆弧可以显示完全
        if (mRectF == null) {
            mRectF = new RectF(mBorderWidth / 2, mBorderWidth / 2, getWidth() - mBorderWidth / 2, getHeight() - mBorderWidth / 2);
        }
        // 1.画外圆弧
        canvas.drawArc(mRectF, START_ANGLE, MAX_SWEEP_ANGLE, false, mOutPaint);
        // 2.画内圆弧
        float sweepAngle = mCurrentStep * 1.0f / mStepMax * MAX_SWEEP_ANGLE;
        canvas.drawArc(mRectF, START_ANGLE, sweepAngle, false, mInnerPaint);
        // 3.画文字:找基线画文字
        // x: 控件宽度/2 - 文字宽度/2
        String stepText = mCurrentStep + "";
        Rect textBounds = new Rect();
        mTextPaint.getTextBounds(stepText, 0, stepText.length(), textBounds);
        int dx = getWidth() / 2 - textBounds.width() / 2;
        // 基线y
        Paint.FontMetricsInt fontMetricsInt = mTextPaint.getFontMetricsInt();
        int dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
        int baseLine = getHeight() / 2 + dy;
        canvas.drawText(stepText, dx, baseLine, mTextPaint);
    }

    public synchronized void setStepMax(int stepMax) {
        mStepMax = stepMax;
    }

    public synchronized void setCurrentStep(int currentStep) {
        mCurrentStep = currentStep;
        invalidate();
    }
}

3.使用自定义view:

button = findViewById(R.id.btn);
qqStepView = findViewById(R.id.qq_step_view);
qqStepView.setStepMax(4000);
qqStepView.setCurrentStep(1500);

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 通过外部设置步数,以及外部设置不同动画效果,避免写死在自定view内。
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 3000);
        valueAnimator.setDuration(2000);
        valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(@NonNull ValueAnimator animation) {
                int currentStep = (int) animation.getAnimatedValue();
                qqStepView.setCurrentStep(currentStep);
            }
        });
        valueAnimator.start();
    }
});