LoadingCircleView 会转圈圈的圆弧的实现

这个转圈圈,不是单纯的转圈圈,而是最近在安卓上很常见的那个很难用语言描述的不知道是谁发明出来的一段圆上的弧在旋转然后忽长忽短的那个。我期初看了好久也没太看懂他的规律,根据canvas的drawArc方法,我原来是想用startAngle和endAngle来描述圆弧,后来发现太复杂,灵光一闪发现用drawArc里面的startAngle和sweepAngle就能做到,而且逻辑相对不复杂。

public class LoadingCircleView extends View {

    private int strokeColor = Color.RED; // TODO: use a default from R.color...
    private int mCircleLineStrokeWidth = 8;
    private boolean drawCircle = true;
    private int circleBgColor = Color.WHITE;

    private RectF mRectF;
    private Paint mPaint;
    private int mWidth;
    private int mHeight;
    private int startAngle = -90;
    private int sweepAngle = 0;
    private Timer timer;
    private RedrawTimerTask timerTask;
    private boolean isRotating = false;
    private MyHandler handler = new MyHandler(this);

    public LoadingCircleView(Context context) {
        super(context);
        mContext = context;
        init(null, 0);
    }

    public LoadingCircleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        init(attrs, 0);
    }

    public LoadingCircleView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mContext = context;
        init(attrs, defStyle);
    }

    private void init(AttributeSet attrs, int defStyle) {
        // Load attributes
        if(attrs != null){
            final TypedArray a = getContext().obtainStyledAttributes(
                    attrs, R.styleable.LoadingCircleView, defStyle, 0);

            strokeColor = a.getColor(
                    R.styleable.LoadingCircleView_strokeColor,
                    getResources().getColor(R.color.theme_dark_blue));
            drawCircle = a.getBoolean(R.styleable.LoadingCircleView_drawCircle,true);
            circleBgColor = a.getColor(R.styleable.LoadingCircleView_circleBgColor,circleBgColor);
            a.recycle();
        }else{
            strokeColor = getResources().getColor(R.color.theme_dark_blue);
        }

        mPaint = new Paint();
        mRectF = new RectF();
        mPaint.setAntiAlias(true);

        addOnLayoutChangeListener(new OnLayoutChangeListener() {
            @Override
            public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                mWidth = getWidth()-getPaddingLeft()-getPaddingRight();
                mHeight = getHeight()-getPaddingTop()-getPaddingBottom();
                if (mWidth != mHeight) {
                    int min = Math.min(mWidth, mHeight);
                    mWidth = min;
                    mHeight = min;
                }
                mCircleLineStrokeWidth = mWidth/15;
                if(mCircleLineStrokeWidth==0){
                    mCircleLineStrokeWidth = 1;
                }

                if(drawCircle){
                    mRectF.left = getPaddingLeft() + (int)(mHeight*0.2) + mCircleLineStrokeWidth / 2; 
                    mRectF.top = getPaddingTop() + (int)(mHeight*0.2) + mCircleLineStrokeWidth / 2; 
                    mRectF.right = getPaddingLeft() + (int)(mHeight*0.8) - mCircleLineStrokeWidth / 2; 
                    mRectF.bottom = getPaddingTop() + (int)(mHeight*0.8) - mCircleLineStrokeWidth / 2;
                }else{
                    mWidth -= mCircleLineStrokeWidth*2;
                    mHeight -= mCircleLineStrokeWidth*2;
                    mRectF.left = getPaddingLeft() + mCircleLineStrokeWidth / 2; // 左上角x
                    mRectF.top = getPaddingTop() + mCircleLineStrokeWidth / 2; // 左上角y
                    mRectF.right = getPaddingLeft() + mWidth + mCircleLineStrokeWidth / 2; // 左下角x
                    mRectF.bottom = getPaddingTop() + mHeight + mCircleLineStrokeWidth / 2; // 右下角y
                }
            }
        });
    }

    private void invalidateTextPaintAndMeasurements() {
        
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawColor(Color.TRANSPARENT);

        if(drawCircle){
            mPaint.setColor(circleBgColor);
            mPaint.setStyle(Paint.Style.FILL);
            canvas.drawCircle(getPaddingLeft()+((float)mWidth)/2,getPaddingTop()+((float)mWidth)/2,((float)mWidth)/2,mPaint);
        }

        mPaint.setStrokeWidth(mCircleLineStrokeWidth);
        mPaint.setStyle(Paint.Style.STROKE);

        /*mPaint.setColor(Color.rgb(0xe9, 0xe9, 0xe9));
        canvas.drawArc(mRectF, -90, 360, false, mPaint);*/

        mPaint.setColor(strokeColor);
        canvas.drawArc(mRectF, startAngle, sweepAngle, false, mPaint);
    }

    public void startRotation(){
        if(isRotating){
            return;
        }
        isRotating = true;
        if(timer!=null){
            timer.cancel();
        }
        startAngle = -90;
        sweepAngle = 30;
        timer = new Timer();
        timerTask = new RedrawTimerTask(this);
        timer.schedule(timerTask,0,15);
    }

    public void stopRotation(){
        if(timer!=null){
            timer.cancel();
        }
        startAngle = -90;
        sweepAngle = 0;
        isRotating = false;
    }

    public void setProgress(float progress){
        if(progress<0 || progress>1){
            return;
        }
        if(isRotating){
            stopRotation();
        }
        startAngle = -90;
        sweepAngle = (int)(progress * 360);
        invalidate();
    }

    private class RedrawTimerTask extends TimerTask{

        private WeakReference<LoadingCircleView> v;

        private int direction = 0;
        private int startAngle = -90;
        private int sweepAngle = 30;

        public RedrawTimerTask(LoadingCircleView view){
            v = new WeakReference<LoadingCircleView>(view);
        }

        @Override
        public void run(){
            LoadingCircleView view = v.get();
            if(view == null){
                cancel();
                return;
            }
            startAngle = startAngle + 3 + direction * 6;
            sweepAngle = sweepAngle + 6 - direction * 12;

            if(sweepAngle == 30 && direction == 1){
                startAngle = startAngle%360;
                direction = 0;
                sweepAngle = -30;
            }else if(sweepAngle == 360){
                startAngle = startAngle%360;
                direction = 1;
                sweepAngle = 330;
            }

            int finalSweepAngle = sweepAngle;
            if(finalSweepAngle < 30){
                finalSweepAngle = 30;
            }else if(finalSweepAngle > 330){
                finalSweepAngle = 330;
            }
            view.handler.removeMessages(0);
            view.handler.sendMessage(Message.obtain(view.handler,0,startAngle,finalSweepAngle));
        }
    }

    private static class MyHandler extends Handler {

        private WeakReference<LoadingCircleView> v;

        MyHandler(LoadingCircleView view){
            v = new WeakReference<LoadingCircleView>(view);
        }

        @Override
        public void handleMessage(Message msg){
            LoadingCircleView view = v.get();
            if(view == null){
                return;
            }
            view.startAngle = msg.arg1;
            view.sweepAngle = msg.arg2;
            view.invalidate();
        }
    }
}

需要定义styleable

<declare-styleable name="LoadingCircleView">
    <attr name="strokeColor" format="color" /><!--圆弧颜色-->
    <attr name="drawCircle" format="boolean" /><!--是否在底层画一个圆-->
    <attr name="circleBgColor" format="color" /><!--是否圆的颜色-->
</declare-styleable>

标签: none

添加新评论