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

简介: 本文比较详细的介绍了绘制圆环及圆弧的基础知识,为实现钉钉运动步数打下基础,实现了下面的效果,实现钉钉运动就灰常简单了,本文实现的初步效果如下:如果想直接看钉钉运动的最终效果,请戳:Android进阶之自定义控件(2)高仿钉钉运动步数实现可动的进度圆环(下)Animation.

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

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

img_e846d6fd2f1477a28685e0ca588310f3.gif
Animation.gif

1、圆环的绘制
2、绘制背景圆环和进度圆环
3、绘制中间的文字
(1)使用drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)绘制圆环:
img_d093e0d8f03bba697e61b48648c9f60f.png
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);
    }
}

img_bc56fc212710b235922a872b0d41e758.png
image.png

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


img_35ad1d27d9f604baf5a47e96ce1818b0.png
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);

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


img_7049025487a8d2a977d848813310a1f4.png
image.png

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

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

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


img_ea8e49fd6f4d511baf2cf4beeea53ce4.png
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);
    }

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


img_89b3beb3d5af1c96eec93ef216c81808.png
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)绘制居中的进度文字


img_33877873086c24770fbf490c806dcb34.png
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);
img_f84f2366e0fc9010e48b4c28fbd83336.png
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>
相关文章
|
4天前
|
API Android开发 开发者
Android经典实战之使用ViewCompat来处理View兼容性问题
本文介绍Android中的`ViewCompat`工具类,它是AndroidX库核心部分的重要兼容性组件,确保在不同Android版本间处理视图的一致性。文章列举了设置透明度、旋转、缩放、平移等功能,并提供了背景色、动画及用户交互等实用示例。通过`ViewCompat`,开发者可轻松实现跨版本视图操作,增强应用兼容性。
25 5
|
19天前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
View的绘制和事件处理是两个重要的主题,上一篇《图解 Android事件分发机制》已经把事件的分发机制讲得比较详细了,这一篇是针对View的绘制,View的绘制如果你有所了解,基本分为measure、layout、draw 过程,其中比较难理解就是measure过程,所以本篇文章大幅笔地分析measure过程,相对讲得比较详细,文章也比较长,如果你对View的绘制还不是很懂,对measure过程掌握得不是很深刻,那么耐心点,看完这篇文章,相信你会有所收获的。
40 2
|
20天前
|
机器学习/深度学习 人工智能 算法
探索AI在医疗影像分析中的应用探索安卓开发中的自定义View组件
【7月更文挑战第31天】随着人工智能技术的飞速发展,其在医疗健康领域的应用日益广泛。本文将聚焦于AI技术在医疗影像分析中的运用,探讨其如何通过深度学习模型提高诊断的准确性和效率。我们将介绍一些关键的深度学习算法,并通过实际代码示例展示这些算法是如何应用于医学影像的处理和分析中。文章旨在为读者提供对AI在医疗领域应用的深刻理解和实用知识。
22 0
|
27天前
|
消息中间件 调度 Android开发
Android经典面试题之View的post方法和Handler的post方法有什么区别?
本文对比了Android开发中`View.post`与`Handler.post`的使用。`View.post`将任务加入视图关联的消息队列,在视图布局后执行,适合视图操作。`Handler.post`更通用,可调度至特定Handler的线程,不仅限于视图任务。选择方法取决于具体需求和上下文。
27 0
|
前端开发 Android开发
Android进阶之自定义View(2)高仿钉钉运动步数实现可动的进度圆环(下)
接着上篇Android进阶之自定义控件(2)高仿钉钉运动步数实现可动的进度圆环(上)的基础,我们来实现钉钉运动的效果: 《一》View效果分析: 对钉钉运动的效果进行分析: 1、圆弧应该是从135°起,绘制了270°。
1227 0
|
2天前
|
JavaScript 前端开发 Java
FFmpeg开发笔记(四十七)寒冬下安卓程序员的几个技术转型发展方向
IT寒冬使APP开发门槛提升,安卓程序员需转型。选项包括:深化Android开发,跟进Google新技术如Kotlin、Jetpack、Flutter及Compose;研究Android底层框架,掌握AOSP;转型Java后端开发,学习Spring Boot等框架;拓展大前端技能,掌握JavaScript、Node.js、Vue.js及特定框架如微信小程序、HarmonyOS;或转向C/C++底层开发,通过音视频项目如FFmpeg积累经验。每条路径都有相应的书籍和技术栈推荐,助你顺利过渡。
13 3
FFmpeg开发笔记(四十七)寒冬下安卓程序员的几个技术转型发展方向
|
6天前
|
Java Android开发 iOS开发
探索安卓与iOS开发的差异:平台选择对项目成功的影响
在移动应用开发的世界中,选择正确的平台是关键。本文通过比较安卓和iOS开发的核心差异,揭示平台选择如何影响应用的性能、用户体验和市场覆盖。我们将深入探讨各自的开发环境、编程语言、用户界面设计原则以及发布流程,以帮助开发者和企业做出明智的决策。
27 9
|
1天前
|
搜索推荐 Android开发 iOS开发
探索安卓与iOS开发的差异性与互补性
【8月更文挑战第19天】在移动应用开发的广阔天地中,安卓与iOS两大平台各据一方,引领着行业的潮流。本文将深入探讨这两个平台在开发过程中的不同之处以及它们之间的互补关系,旨在为开发者提供一个全面的视角,帮助他们更好地把握市场动态,优化开发策略。通过分析各自的开发环境、编程语言、用户界面设计、性能考量及市场分布等方面,我们将揭示安卓与iOS开发的独特魅力和挑战,同时指出如何在这两者之间找到平衡点,实现跨平台的成功。
|
3天前
|
移动开发 开发工具 Android开发
探索安卓与iOS开发的差异:技术选择的影响
【8月更文挑战第17天】 在移动应用开发的广阔天地中,安卓和iOS两大平台各领风骚。本文通过比较这两个平台的编程语言、开发工具及市场策略,揭示了技术选择对开发者和产品成功的重要性。我们将从开发者的视角出发,深入探讨不同平台的技术特性及其对项目实施的具体影响,旨在为即将步入移动开发领域的新手提供一个清晰的指南,同时给予资深开发者新的思考角度。
|
6天前
|
Java 开发工具 Android开发
探索安卓与iOS开发的差异:从新手到专家的旅程
在数字时代的浪潮中,移动应用开发成为了连接世界的桥梁。本文将带你走进安卓与iOS这两大移动操作系统的开发世界,通过比较它们的编程语言、开发工具和环境、用户界面设计以及市场分布等方面,揭示各自的独特之处。无论你是初涉编程的新手,还是寻求进阶的开发者,这篇文章都将为你提供宝贵的洞见,助你在移动应用开发的征途上一帆风顺。
20 5

热门文章

最新文章