分类 默认分类 下的文章

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>

Debug Release采用不同资源

From Steffen Funke

Just to give a working example to my comment above:

declare a resValue in your defaultConfig which will become the Application's name. (Attention: if you choose to name it app_name, be sure to delete the original app_name property from your strings.xml file, or it will clash.)

defaultConfig {
    // applicationId, versionCode, etc.

    (...)

    // define your base Applications name here
    resValue 'string', 'app_name', 'MyApp'
}

set your productFlavors as you did already. You can leave them empty if it is ok for you to concat your App's name with the flavor's name only, or provide an explicit resValue, which will override the value from defaultConfig.

productFlavors {
    dev {
        // resValue 'string', 'app_name', 'MyAppDevFlavor'
    }

    prod {
        // resValue 'string', 'app_name', 'MyAppProdFlavor'
    }
}

configure the resValue's name at gradle configuration time

android.applicationVariants.all { variant ->
    // get app_name field from defaultConfig
    def appName = variant.mergedFlavor.resValues.get('app_name').getValue()

    // concat new App name with each flavor's name
    appName = "${appName}"
    variant.productFlavors.each { flavor ->
        appName += " ${flavor.name}"
    }

    // optionally add buildType name
    appName += " ${variant.buildType.name}"

    // your requirement: if buildType == debug, add DEV to App name
    if(variant.buildType.name == "debug") {
        appName += " DEV"
    }

    // set new resVale
    variant.resValue 'string', 'app_name', appName
}

In your AndroidManifest, set the app_name field:

    <application
    ...
    android:label="@string/app_name"
    ...
    >

As I mentioned above, be sure to delete the default app_name property from strings.xml

啦啦啦,试用typecho

Typecho

传说中的轻博客

今天心血来潮建了一个离线博客,以后技术各方面的东西都往这里面写吧,当然也免不了吐槽。主要typecho支持markdown,比较方便,又没有workpress那么笨重

2016年3月23日