自定义画一个圆弧


今天,我想实现的一个效果是,画一个圆弧,然后这个圆弧可以根据数字的变化,动态滑过去。
原图效果是这样的: 圆弧动态加载

自定义属性操作

  • 首先还是,先想一下,需要什么属性。
    (1)内圆颜色
    (2)外圆颜色
    (3)圆弧的边框宽度
    (4)中间字体大小
    (5)中间字体的颜色
  • 在attrs.xml自定义属性
    <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>
  • 在layout布局使用该自定义,并赋值

  • 自定义的代码要怎么写呢

(1)在构造方法获取属性、初始化画笔、给画笔属性赋值包括颜色大小样式。这里用到三个画笔,要画内圆弧、外圆弧、字体。

    public QQStepView(Context context, 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);
        // 5.onMeasure()
        // 6.画外圆弧 ,内圆弧 ,文字
        // 7.其他
    }

(2)onMeasure方法,没什么好说的,就是设置控件宽高,给它一个特定的宽高就好了,这里后面再优化都行

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 调用者在布局文件中可能  wrap_content
        // 获取模式 AT_MOST  40DP

        // 宽度高度不一致 取最小值,确保是个正方形
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(width>height?height:width,width>height?height:width);
    }

(3)onDraw方法才是重点!要怎么画圆弧,怎么画内圆弧、怎么画外圆弧,怎么画字体。

  • 画圆弧使用 canvas.drawArc(rectF,startAngle,sweepAngle,useCenter,paint)
    这个drawArc有5个参数。
    (1)rectF:就是一个矩形对象,用来确定位置和矩形范围。
RectF rectF = new RectF(mBorderWidth/2,mBorderWidth/2,getWidth()-mBorderWidth/2,getHeight()-mBorderWidth/2);

这个RectF的构造方法有四个参数,分别是 left,top,right,bottom。以页面的右上角为原点。分别用来确定左边距,上边距,右边距,下边距。
这里为什么左边、上边不从0,0开始,右边距为什么不是直接到getWidth控件宽度?这里要确认上下左右,可以运行试试,看着效果来调整。

RectF rectF = new RectF(0,0,getWidth(),getHeight());
// 上面这样的话,上边距和左边距和右边距都会溢出一半画笔的宽度。,所以这个时候要做对应的调整,从画笔宽度一半开始画起。
RectF rectF = new RectF(mBorderWidth/2,mBorderWidth/2
,getWidth()-mBorderWidth/2,getHeight()-mBorderWidth/2);

(2)回到canvas.drawArc的使用

①下面第二个参数135: 是从圆的右边0度开始,逆时针旋转画135度,而这里的135度,就是从135度位置开始画圆弧
②第三个参数270:就是从第二个参数开始画,逆时针扫过270度
③false:表示是否闭环,false代表不闭环。

canvas.drawArc(rectF,135,270,false,mOutPaint);

(3)要画两次圆弧,一个是外圆弧,一个是内圆弧。内圆弧不断变化,这样才能体现出进度条的变化

RectF rectF = new RectF(mBorderSize/2,mBorderSize/2
,getWidth()-mBorderSize/2,getHeight());
// 画出弧形。 第一个参数是从多少度开始画, 第二个参数是走了270度,即终点是130+270度
canvas.drawArc(rectF,130,270,false,outterPaint);
// 也就是从130度为起点, 终点是 130+270 度
float sweepAngle = (float)currentStep/maxStep;
if(sweepAngle == 0) return;
canvas.drawArc(rectF,130,sweepAngle * 270,false,innerPaint);

内圆弧要扫过多少进度,取决于sweepAngle,而sweepAngle又取决于currentStep。

  • 开始画圆弧中间的文字
    (1)确认文字的落笔横坐标位置:
String stepText = currentStep + " ";
Rect rect = new Rect();
textPaint.getTextBounds(stepText,0,stepText.length(),rect);
int dx = getWidth()/2 - rect.width()/2; // 文字的起始位置

textView总体宽度的一半减文字宽度的一半,就是开始画文字的横坐标位置。
(2)确定文字基线,还是和之前自定义TextView一样的

Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
int dy = (fontMetrics.bottom - fontMetrics.top)/2 - fontMetrics.bottom;
int baseLine = getHeight()/2 + dy;

使用动画,不断改变currentStep,并调用invalidate不断重绘该View。:

    // 7.其他 写几个方法动起来
    public synchronized void setStepMax(int stepMax){
        this.mStepMax = stepMax;
    }

    public synchronized void setCurrentStep(int currentStep){
        this.mCurrentStep = currentStep;
        // 不断绘制  onDraw()
        invalidate();
    }
        qqStepView.setStepMax(4000);

        // 属性动画 后面讲的内容
        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();

这里用到属性动画,回调onAnimationUpdate方法,监听valueAnimator值的变化。改变currentStep值。

封闭完整圆型进度条

  • 先用outterPaint画一个空心的圆。使用canvas.drawCircle方法
canvas.drawCircle(getWidth()/2,getWidth()/2,getWidth()/2 -mBorderSize/2,outterPaint);

这里四个参数分别代表:
(1)第一、二个参数,代表圆心的横坐标和纵坐标,那就是宽的一半。就是圆心的横纵坐标。
(3)第三个参数就是圆的半径。 为什么直接用宽的一半不可以,我运行试了一下,它就是会有一部分溢出规定的矩形,而且溢出的长度就是画笔宽度的一半。 所以把宽的一半-画笔宽度的一半,就刚好在矩形范围内了

  • 再用innerPaint画一个0到360度的圆弧,动态扫过去
RectF rectF = new RectF(mBorderSize/2,mBorderSize/2,getWidth()-mBorderSize/2,getHeight() - mBorderSize/2);

float sweepAngle = (float)currentStep/maxStep;
if(sweepAngle == 0) return;
canvas.drawArc(rectF,0,sweepAngle * 360,false,innerPaint);

其实上面画圆弧,设置的矩形范围,也是和上面画圆一样的,没有从画笔宽度一半开始画,还有,没有减掉画笔宽度的一半就是会溢出。

源码在下面:

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 = 0;
    private int mCurrentStep = 0;

    public QQStepView(Context context) {
        this(context,null);
    }
    public QQStepView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public QQStepView(Context context, 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);
        // 5.onMeasure()
        // 6.画外圆弧 ,内圆弧 ,文字
        // 7.其他
    }


    // 5.onMeasure()
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 调用者在布局文件中可能  wrap_content
        // 获取模式 AT_MOST  40DP

        // 宽度高度不一致 取最小值,确保是个正方形
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(width>height?height:width,width>height?height:width);
    }
    // 6.画外圆弧 ,内圆弧 ,文字
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 6.1 画外圆弧    分析:圆弧闭合了  思考:边缘没显示完整  描边有宽度 mBorderWidth  圆弧

        // int center = getWidth()/2;
        // int radius = getWidth()/2 - mBorderWidth/2;
        // RectF rectF = new RectF(center-radius,center-radius
        // ,center+radius,center+radius);

        // 画出一个正确大小范围的矩形!
        RectF rectF = new RectF(mBorderWidth/2,mBorderWidth/2
        ,getWidth()-mBorderWidth/2,getHeight()-mBorderWidth/2);
        // 研究研究

        canvas.drawArc(rectF,135,270,false,mOutPaint);
        if(mStepMax == 0)return;
        // 6.2 画内圆弧  怎么画肯定不能写死  百分比  是使用者设置的从外面传
        float sweepAngle = (float)mCurrentStep/mStepMax;
        canvas.drawArc(rectF,135,sweepAngle*270,false,mInnerPaint);

        // 6.3 画文字
        String stepText = mCurrentStep+"";
        Rect textBounds = new Rect();
        mTextPaint.getTextBounds(stepText, 0, stepText.length(), textBounds);
        int dx = getWidth()/2 - textBounds.width()/2;
        // 基线 baseLine
        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 synchronized void setStepMax(int stepMax){
        this.mStepMax = stepMax;
    }

    public synchronized void setCurrentStep(int currentStep){
        this.mCurrentStep = currentStep;
        // 不断绘制  onDraw()
        invalidate();
    }
}
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final QQStepView qqStepView = (QQStepView) findViewById(R.id.step_view);
        qqStepView.setStepMax(4000);

        // 属性动画 后面讲的内容
        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();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值