美文网首页Android自定义View
Android进阶之自定义View(2)高仿钉钉运动步数实现可动

Android进阶之自定义View(2)高仿钉钉运动步数实现可动

作者: 一只懂音乐的码虫 | 来源:发表于2018-08-08 08:42 被阅读145次

    本文比较详细的介绍了绘制圆环及圆弧的基础知识,为实现钉钉运动步数打下基础,实现了下面的效果,实现钉钉运动就灰常简单了,本文实现的初步效果如下:

    如果想直接看钉钉运动的最终效果,请戳:Android进阶之自定义控件(2)高仿钉钉运动步数实现可动的进度圆环(下)

    Animation.gif
    1、圆环的绘制
    2、绘制背景圆环和进度圆环
    3、绘制中间的文字
    (1)使用drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)绘制圆环:
    image.png
     public class SportStepView extends View {
        private Paint mPaint;
        //圆环绘制的宽度
        private int mRoundWidth = 40;
    
        public SportStepView(Context context) {
            this(context, null);
        }
    
        public SportStepView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public SportStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            //获取宽的模式
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            //获取宽的尺寸
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
            
            //对wrap_content这种模式进行处理
            if (heightMode == MeasureSpec.AT_MOST) {
                heightSize = widthSize;
            } 
            //绘制圆环以宽度为标准,保存丈量结果
            setMeasuredDimension(widthSize, heightSize);
        }
    
        private void init() {
            mPaint = new Paint();
            mPaint.setAntiAlias(true);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(Color.RED);
            mPaint.setStrokeWidth(mRoundWidth);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
          //绘制圆,设置画笔的Style为Paint.Style.STROKE,则绘制出来的为圆环,否则绘制出来的为圆
          //canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mRoundWidth / 2, mPaint);
            RectF oval = new RectF(0 , 0, getWidth(), getWidth());
            //画圆弧 useCenter:是否显示圆内的横线 下面的绘制0,360的圆弧,也可以实现绘制圆环的效果
            canvas.drawArc(oval, 0, 360, false, mPaint);
        }
    }
    
    
    image.png

    会发现显示不全,绘制超出边界了,因为圆的宽度是在当前半径向两边展开的。如下图分析得知,圆所在的矩形区域不是rect(为屏幕的矩形区域),而是real Rect所在的区域:


    image.png

    因此只需要修改如下即可。

     RectF oval = new RectF(0 + mRoundWidth / 2, 0 + mRoundWidth / 2, getWidth() - mRoundWidth / 2, getWidth() - mRoundWidth / 2);
    // RectF oval = new RectF(0 , 0, getWidth(), getWidth());
    //画圆弧 useCenter:是否显示圆内的横线 下面的绘制0,360的圆弧,也可以实现绘制圆环的效果
     canvas.drawArc(oval, 0, 360, false, mPaint);
    

    修改后达到我们的预期效果:


    image.png

    (2)如果只是绘制上面的圆环效果,还可以使用: canvas.drawCircle()的方式实现,这种方法更简单:

     //绘制圆,设置画笔的Style为Paint.Style.STROKE,则绘制出来的为圆环,否则绘制出来的为圆
     //由于圆环本身有宽度,所以半径要减去圆环宽度的一半,不然一部分圆会在view外面。
     canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mRoundWidth / 2, mPaint);
    

    (3)接下来我们来实现背景圆环+进度圆环的效果了,利用drawCircle绘制背景圆环,drawArc()绘制进度圆环,预期效果如下:


    image.png

    这个很简单,再画个圆弧即可:

       @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //绘制背景圆环,设置画笔的Style为Paint.Style.STROKE,则绘制出来的为圆环,否则绘制出来的为圆
            //正常情况下
           canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mRoundWidth / 2, mPaint);
    
           //正常情况下,绘制进度圆环
          RectF oval = new RectF(0 + mProgressRoundWidth / 2, 0 + mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2);
                //画圆弧 useCenter:是否显示圆内的横线 下面的绘制0,360的圆弧,也可以实现绘制圆环的效果
                canvas.drawArc(oval, 0, 300, false, mProgressPaint);
        }
    

    但是光这样处理,会有个小问题,就是当背景圆环和进度圆环宽度不一致时,会出现下面的问题。


    image.png

    解决方法:以宽度较大为准即可。

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            /**
             * 如果背景圆环和进度圆环宽度不一致,都以较大的宽度为准绘制。避免出现两者显示不居中的问题
             */
            //绘制背景圆环,设置画笔的Style为Paint.Style.STROKE,则绘制出来的为圆环,否则绘制出来的为圆
    //        canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, mPaint);
            if (mRoundWidth < mProgressRoundWidth) {
                canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mProgressRoundWidth / 2, mPaint);
            } else {
                //正常情况下
                canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mRoundWidth / 2, mPaint);
            }
    
            if (mRoundWidth < mProgressRoundWidth) {
                // //正常情况下,绘制进度圆环
                RectF oval = new RectF(0 + mProgressRoundWidth / 2, 0 + mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2);
                //        RectF oval = new RectF(0 , 0, getWidth(), getWidth());
                //画圆弧 useCenter:是否显示圆内的横线 下面的绘制0,360的圆弧,也可以实现绘制圆环的效果
                canvas.drawArc(oval, 0, 300, false, mProgressPaint);
            } else {
                //绘制进度圆环
                RectF oval = new RectF(0 + mRoundWidth / 2, 0 + mRoundWidth / 2, getWidth() - mRoundWidth / 2, getWidth() - mRoundWidth / 2);
                //画圆弧 useCenter:是否显示圆内的横线 下面的绘制0,360的圆弧,也可以实现绘制圆环的效果
                canvas.drawArc(oval, 0, 300, false, mProgressPaint);
            }
        }
    

    (4)绘制居中的进度文字


    image.png

    代码实现:

    
       //绘制中间的文字
            Rect textRect = new Rect();
            //进度百分比
            int progressPercent = (int) (mCurrentProgress * 1f / mMaxProgress * 100);
            String mShowText = progressPercent + "%";
            mTextPaint.getTextBounds(mShowText, 0, mShowText.length(), textRect);
            canvas.drawText(mShowText, getWidth() / 2 - textRect.width() / 2, getHeight() / 2 + textRect.height() / 2, mTextPaint);
    
    image.png

    (5)处理圆环进度和文字进度的动态显示
    方法一:开一个分线程,动态改变进度的值,不断绘制达到进度变化的效果。
    方法二:下篇会介绍到_
    在测试的Activity中使用:

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    //        mytextview = findViewById(R.id.mytextview);
            final SportStepView sportStepView = findViewById(R.id.sportstepview);
            mCurrentProgress = 80;
            //速度,值越大,变化速度越快
            rate = 1;
            //开一个分线程,动态改变进度的值,不断绘制达到进度变化的效果
            new Thread(new Runnable() {
                @Override
                public void run() {
                    sportStepView.setCurrentProgress(0);
    
                    for (int i = 0; i < mCurrentProgress / rate; i++) {
                        sportStepView.setCurrentProgress(sportStepView.getCurrentProgress() + rate);
                        SystemClock.sleep(20);
    //                pb_progress.invalidate();//invalidate()必须在主线程中执行,此处不能使用
                        sportStepView.postInvalidate();//强制重绘,postInvalidate()可以在主线程也可以在分线程中执行
                    }
                }
            }).start();
        }
    

    完整代码:

    package com.example.jojo.learn.customview;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Rect;
    import android.graphics.RectF;
    import android.support.annotation.Nullable;
    import android.util.AttributeSet;
    import android.util.TypedValue;
    import android.view.View;
    
    import com.example.jojo.learn.R;
    
    /**
     * Created by JoJo on 2018/7/31.
     * wechat:18510829974
     * description: 仿钉钉运动步数
     */
    
    public class SportStepView extends View {
    
        //绘制背景圆环的画笔
        private Paint mPaint;
        //绘制外面进度的圆环的画笔
        private Paint mProgressPaint;
        //绘制外面进度的圆环的画笔
        private Paint mTextPaint;
        //背景圆弧的绘制的宽度
        private int mRoundWidth = 40;
        //进度圆环的宽度
        private float mProgressRoundWidth = 60;
        private int mTextSize = 40;//单位 sp
    
        //圆环最大进度
        private int mMaxProgress = 100;
        //圆环当前进度
        private int mCurrentProgress = 0;
    
        public SportStepView(Context context) {
            this(context, null);
        }
    
        public SportStepView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public SportStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            //获取宽的模式
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            //获取宽的尺寸
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            //对wrap_content这种模式进行处理
            if (heightMode == MeasureSpec.AT_MOST) {
                heightSize = widthSize;
            }
            //以宽度为标准保存丈量结果
            setMeasuredDimension(widthSize, heightSize);
        }
    
        private void init() {
            mPaint = new Paint();
            mPaint.setAntiAlias(true);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(Color.RED);
            mPaint.setStrokeWidth(mRoundWidth);
    
            mProgressPaint = new Paint();
            mProgressPaint.setAntiAlias(true);// 抗锯齿效果
            mProgressPaint.setStyle(Paint.Style.STROKE);
            mProgressPaint.setColor(Color.YELLOW);
            mProgressPaint.setStrokeCap(Paint.Cap.ROUND);// 圆形笔头
            mProgressPaint.setStrokeWidth(mProgressRoundWidth);
    
            mTextPaint = new Paint();
            mTextPaint.setAntiAlias(true);// 抗锯齿效果
            mTextPaint.setStyle(Paint.Style.STROKE);
            mTextPaint.setColor(Color.BLACK);
            mTextPaint.setTextSize(sp2px(mTextSize));
    
    
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
    
            /**
             * 如果背景圆环和进度圆环宽度不一致,都以较大的宽度为准绘制。避免出现两者显示不居中的问题
             */
            //绘制背景圆环,设置画笔的Style为Paint.Style.STROKE,则绘制出来的为圆环,否则绘制出来的为圆
    //        canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, mPaint);
            if (mRoundWidth < mProgressRoundWidth) {
                canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mProgressRoundWidth / 2, mPaint);
            } else {
                //正常情况下
                canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mRoundWidth / 2, mPaint);
            }
    
            if (mRoundWidth < mProgressRoundWidth) {
                // //正常情况下,绘制进度圆环
                RectF oval = new RectF(0 + mProgressRoundWidth / 2, 0 + mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2);
                //        RectF oval = new RectF(0 , 0, getWidth(), getWidth());
                //画圆弧 useCenter:是否显示圆内的横线 下面的绘制0,360的圆弧,也可以实现绘制圆环的效果
                canvas.drawArc(oval, 0 + 90, mCurrentProgress * 1f / mMaxProgress * 360, false, mProgressPaint);
            } else {
                //绘制进度圆环
                RectF oval = new RectF(0 + mRoundWidth / 2, 0 + mRoundWidth / 2, getWidth() - mRoundWidth / 2, getWidth() - mRoundWidth / 2);
                //画圆弧 useCenter:是否显示圆内的横线 下面的绘制0,360的圆弧,也可以实现绘制圆环的效果
                canvas.drawArc(oval, 0 + 90, mCurrentProgress * 1f / mMaxProgress * 360, false, mProgressPaint);
            }
    
    
            //绘制中间的文字
            Rect textRect = new Rect();
            //进度百分比
            int progressPercent = (int) (mCurrentProgress * 1f / mMaxProgress * 100);
            String mShowText = progressPercent + "%";
            mTextPaint.getTextBounds(mShowText, 0, mShowText.length(), textRect);
            canvas.drawText(mShowText, getWidth() / 2 - textRect.width() / 2, getHeight() / 2 + textRect.height() / 2, mTextPaint);
        }
    
        public void setCurrentProgress(int currentProgress) {
            this.mCurrentProgress = currentProgress;
        }
    
        public void setMaxProgress(int maxProgress) {
            this.mMaxProgress = maxProgress;
        }
    
        public int getMaxProgress() {
            return mMaxProgress;
        }
    
        public int getCurrentProgress() {
            return mCurrentProgress;
        }
    
    
        /**
         * 将sp转换成px
         *
         * @param sp
         * @return
         */
        private int sp2px(int sp) {
            return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
                    getResources().getDisplayMetrics());
        }
    }
    
    

    涉及到的自定义属性

       <!--SportStepView-->
        <declare-styleable name="SportStepView">
            <!--圆环半径-->
            <attr name="radius" format="dimension"></attr>
            <attr name="outerRoundColor" format="color"></attr>
            <attr name="innerRoundColor" format="color"></attr>
        </declare-styleable>
    

    相关文章

      网友评论

        本文标题:Android进阶之自定义View(2)高仿钉钉运动步数实现可动

        本文链接:https://www.haomeiwen.com/subject/sjdhvftx.html