Android 自定义View 之 饼状进度条

news2024/11/25 10:27:17

饼状进度条

  • 前言
  • 正文
    • 一、XML样式
    • 二、构造方法
    • 三、测量
    • 四、绘制
      • ① 绘制描边
      • ① 绘制进度
    • 五、API方法
    • 六、使用
    • 七、源码

前言

  前面写了圆环进度条,这次我们来写一个饼状进度条,首先看一下效果图:
在这里插入图片描述

正文

  效果图感觉怎么样呢?下面我们来实现这个自定义View,依然是写在EasyView这个项目中,这是一个自定义View库,我会把自己写的自定义View都放在里面,文中如果代码不是很全的话,你可以找到文章最后的源码去查看,话不多说,我们开始吧。

一、XML样式

  根据上面的效果图,我们首先来确定XML中的属性样式,在attrs.xml中添加如下代码:

	<!--饼状进度条-->
    <declare-styleable name="PieProgressBar">
        <!--半径-->
        <attr name="radius" />
        <!--最大进度-->
        <attr name="maxProgress" />
        <!--当前进度-->
        <attr name="progress" />
        <!--进度条进度颜色-->
        <attr name="progressbarColor" />
        <!--进度条描边宽度-->
        <attr name="strokeWidth"/>
        <!--进度是否渐变-->
        <attr name="gradient" />
        <!--渐变颜色数组-->
        <attr name="gradientColorArray" />
        <!--自定义开始角度 0 ,90,180,270-->
        <attr name="customAngle">
            <enum name="right" value="0" />
            <enum name="bottom" value="90" />
            <enum name="left" value="180" />
            <enum name="top" value="270" />
        </attr>
    </declare-styleable>

  这里的公共属性我就抽离了出来,因为之前写过圆环进度条,有一些属性是可以通用的,并且我在饼状进度条中增加了开始的角度,之前是默认是从0°开始,现在可以根据属性设置开始的角度,并且我增加了渐变颜色。

二、构造方法

  现在属性样式已经有了,下一步就是写自定义View的构造方法了,在com.easy.view包下新建一个PieProgressBar 类,里面的代码如下所示:

public class PieProgressBar extends View {

    /**
     * 半径
     */
    private int mRadius;
    /**
     * 进度条宽度
     */
    private int mStrokeWidth;
    /**
     * 进度条进度颜色
     */
    private int mProgressColor;
    /**
     * 开始角度
     */
    private int mStartAngle = 0;

    /**
     * 当前角度
     */
    private float mCurrentAngle = 0;
    /**
     * 结束角度
     */
    private int mEndAngle = 360;
    /**
     * 最大进度
     */
    private float mMaxProgress;
    /**
     * 当前进度
     */
    private float mCurrentProgress;
    /**
     * 是否渐变
     */
    private boolean isGradient;
    /**
     * 渐变颜色数组
     */
    private int[] colorArray;
    /**
     * 动画的执行时长
     */
    private long mDuration = 1000;
    /**
     * 是否执行动画
     */
    private boolean isAnimation = false;

    public PieProgressBar(Context context) {
        this(context, null);
    }

    public PieProgressBar(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PieProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PieProgressBar);
        mRadius = array.getDimensionPixelSize(R.styleable.PieProgressBar_radius, 80);
        mStrokeWidth = array.getDimensionPixelSize(R.styleable.PieProgressBar_strokeWidth, 8);
        mProgressColor = array.getColor(R.styleable.PieProgressBar_progressbarColor, ContextCompat.getColor(context, R.color.tx_default_color));
        mMaxProgress = array.getInt(R.styleable.PieProgressBar_maxProgress, 100);
        mCurrentProgress = array.getInt(R.styleable.PieProgressBar_progress, 0);
        //是否渐变
        isGradient = array.getBoolean(R.styleable.PieProgressBar_gradient, false);
        //渐变颜色数组
        CharSequence[] textArray = array.getTextArray(R.styleable.PieProgressBar_gradientColorArray);
        if (textArray != null) {
            colorArray = new int[textArray.length];
            for (int i = 0; i < textArray.length; i++) {
                colorArray[i] = Color.parseColor((String) textArray[i]);
            }
        }
        mStartAngle = array.getInt(R.styleable.PieProgressBar_customAngle, 0);
        array.recycle();
    }
}

  这里声明了一些变量,然后写了3个构造方法,在第三个构造方法中进行属性的赋值。

三、测量

  这里测量就比较简单了,和之前的圆环进度条差不多,代码如下所示:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = 0;
        switch (MeasureSpec.getMode(widthMeasureSpec)) {
            case MeasureSpec.UNSPECIFIED:
            case MeasureSpec.AT_MOST:   //wrap_content
                width = mRadius * 2;
                break;
            case MeasureSpec.EXACTLY:   //match_parent
                width = MeasureSpec.getSize(widthMeasureSpec);
                break;
        }
        //Set the measured width and height
        setMeasuredDimension(width, width);
    }

  因为不需要进行子控件处理,所以我们只要一个圆和描边就行了,下面看绘制的方法。

四、绘制

  绘制这里就是绘制描边和进度,绘制的代码如下所示:

    @Override
    protected void onDraw(Canvas canvas) {
        int centerX = getWidth() / 2;
        @SuppressLint("DrawAllocation")
        RectF rectF = new RectF(0,0,centerX * 2,centerX * 2);
        //绘制描边
        drawStroke(canvas, centerX);
        //绘制进度
        drawProgress(canvas, rectF);
    }

  在绘制之前首先要确定中心点,因为我们是一个圆环,实际上也是一个圆,圆的宽高一样,所以中心点的x、y轴的位置就是一样的,然后是确定一个矩形的左上和右下两个位置的坐标点,通过这两个点就能绘制一个矩形,接下来就是绘制进度条背景。

① 绘制描边

    /**
     * 绘制描边
     *
     * @param canvas 画布
     * @param centerX 中心点
     */
    private void drawStroke(Canvas canvas, int centerX) {
        Paint paint = new Paint();
        paint.setColor(mProgressColor);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(mStrokeWidth);
        paint.setAntiAlias(true);
        canvas.drawCircle(centerX, centerX, mRadius - (mStrokeWidth / 2), paint);
    }

这里的要点就是我们需要设置画笔的类型为描边,然后设置描边宽度,这样我们就可以画一个空心圆,就成了描边,然后我们绘制进度。

① 绘制进度

    /**
     * 绘制进度条背景
     */
    private void drawProgress(Canvas canvas, RectF rectF) {
        Paint paint = new Paint();
        //画笔的填充样式,Paint.Style.STROKE 描边
        paint.setStyle(Paint.Style.FILL);
        //抗锯齿
        paint.setAntiAlias(true);
        //画笔的颜色
        paint.setColor(mProgressColor);
        //是否设置渐变
        if (isGradient && colorArray != null) {
            paint.setShader(new RadialGradient(rectF.centerX(), rectF.centerY(), mRadius, colorArray, null, Shader.TileMode.MIRROR));
        }
        if (!isAnimation) {
            mCurrentAngle = 360 * (mCurrentProgress / mMaxProgress);
        }
        //开始画圆弧
        canvas.drawArc(rectF, mStartAngle, mCurrentAngle, true, paint);
    }

  因为背景是一个圆环,所以这里的画笔设置就比较注意一些,看一下就会了,这里最重要的是drawArc,用于绘制及角度圆,像下图这样,画了4/1的进度,同时增加是否渐变的设置,这里的开始角度是动态的。

在这里插入图片描述

五、API方法

  还需要提供一些方法在代码中调用,下面是这些方法的代码:

    /**
     * 设置角度
     * @param angle 角度
     */
    public void setCustomAngle(int angle) {
        if (angle >= 0 && angle < 90) {
            mStartAngle = 0;
        } else if (angle >= 90 && angle < 180) {
            mStartAngle = 90;
        } else if (angle >= 180 && angle < 270) {
            mStartAngle = 180;
        } else if (angle >= 270 && angle < 360) {
            mStartAngle = 270;
        } else if (angle >= 360) {
            mStartAngle = 0;
        }
        invalidate();
    }

    /**
     * 设置是否渐变
     */
    public void setGradient(boolean gradient) {
        isGradient = gradient;
        invalidate();
    }

    /**
     * 设置渐变的颜色
     */
    public void setColorArray(int[] colorArr) {
        if (colorArr == null) return;
        colorArray = colorArr;
    }

    /**
     * 设置当前进度
     */
    public void setProgress(float progress) {
        if (progress < 0) {
            throw new IllegalArgumentException("Progress value can not be less than 0");
        }
        if (progress > mMaxProgress) {
            progress = mMaxProgress;
        }
        mCurrentProgress = progress;
        mCurrentAngle = 360 * (mCurrentProgress / mMaxProgress);
        setAnimator(mStartAngle, mCurrentAngle);
    }

    /**
     * 设置动画
     *
     * @param start  开始位置
     * @param target 结束位置
     */
    private void setAnimator(float start, float target) {
        isAnimation = true;
        ValueAnimator animator = ValueAnimator.ofFloat(start, target);
        animator.setDuration(mDuration);
        animator.setTarget(mCurrentAngle);
        //动画更新监听
        animator.addUpdateListener(valueAnimator -> {
            mCurrentAngle = (float) valueAnimator.getAnimatedValue();
            invalidate();
        });
        animator.start();
    }

  那么到此为止这个自定义View就完成了,下面我们可以在PieProgressBarActivity中使用了。

六、使用

   关于使用,我在写这个文章的时候这个自定义View已经加入到仓库中了,可以通过引入依赖的方式,例如在app模块中使用,则打开app模块下的build.gradle,在dependencies{}闭包下添加即可,之后记得要Sync Now

dependencies {
    implementation 'io.github.lilongweidev:easyview:1.0.4'
}

   或者你在自己的项目中完成了刚才上述的所有步骤,那么你就不用引入依赖了,直接调用就好了,不过要注意更改对应的包名,否则会爆红的。

  先修改activity_pie_progress_bar.xml的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".used.PieProgressBarActivity">

    <com.easy.view.PieProgressBar
        android:id="@+id/progress"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:customAngle="right"
        app:gradient="false"
        app:gradientColorArray="@array/color"
        app:maxProgress="100"
        app:progress="5"
        app:progressbarColor="@color/green"
        app:radius="80dp" />

    <CheckBox
        android:id="@+id/cb_gradient"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="是否渐变" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="开始角度:"
            android:textColor="@color/black" />

        <RadioGroup
            android:id="@+id/rg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <RadioButton
                android:id="@+id/rb_0"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:checked="true"
                android:text="0%" />

            <RadioButton
                android:id="@+id/rb_90"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="90%" />

            <RadioButton
                android:id="@+id/rb_180"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="180%" />

            <RadioButton
                android:id="@+id/rb_270"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="270%" />
        </RadioGroup>
    </LinearLayout>


    <Button
        android:id="@+id/btn_set_progress"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="随机设置进度" />

    <Button
        android:id="@+id/btn_set_progress_0"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="设置0%进度" />

    <Button
        android:id="@+id/btn_set_progress_100"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="设置100%进度" />
</LinearLayout>

在strings.xml中增加渐变色,代码如下:

    <string-array name="color">
        <item>#00FFF7</item>
        <item>#FFDD00</item>
        <item>#FF0000</item>
    </string-array>

首先要注意看是否能够预览,我这里是可以预览的,如下图所示:

在这里插入图片描述

PieProgressBarActivity中使用,如下所示:

public class PieProgressBarActivity extends EasyActivity<ActivityPieProgressBarBinding> {

    @SuppressLint("NonConstantResourceId")
    @Override
    protected void onCreate() {
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        //是否渐变
        binding.cbGradient.setOnCheckedChangeListener((buttonView, isChecked) -> {
            binding.cbGradient.setText(isChecked ? "渐变" : "不渐变");
            binding.progress.setGradient(isChecked);
        });
        //开始角度
        binding.rg.setOnCheckedChangeListener((group, checkedId) -> {
            int angle = 0;
            switch (checkedId) {
                case R.id.rb_0:
                    angle = 0;
                    break;
                case R.id.rb_90:
                    angle = 90;
                    break;
                case R.id.rb_180:
                    angle = 180;
                    break;
                case R.id.rb_270:
                    angle = 270;
                    break;
            }
            binding.progress.setCustomAngle(angle);
        });
        //设置随机进度值
        binding.btnSetProgress.setOnClickListener(v -> {
            int progress = Math.abs(new Random().nextInt() % 100);
            Toast.makeText(this, "" + progress, Toast.LENGTH_SHORT).show();
            binding.progress.setProgress(progress);
        });
        //设置0%进度值
        binding.btnSetProgress0.setOnClickListener(v -> binding.progress.setProgress(0));
        //设置100%进度值
        binding.btnSetProgress100.setOnClickListener(v -> binding.progress.setProgress(100));
    }
}

运行效果如下图所示:

在这里插入图片描述

七、源码

如果对你有所帮助的话,不妨 Star 或 Fork,山高水长,后会有期~

源码地址:EasyView

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/599121.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GLTF/GLB模型轻量化简明教程

GLB 文件格式很方便&#xff0c;因为它包含渲染所需的所有文件&#xff0c;包括纹理。 但是&#xff0c;根据用途&#xff0c;你可能希望简化文件&#xff0c;因为它有时非常详细。 在本文中&#xff0c;我将使用 gltf-transform 来执行简化&#xff0c;并且假设你使用的是 Wi…

0x23 Read Version Information Service

0x23 Read Version Information Service ReadMemoryByAddress服务允许客户端通过提供的起始地址和要读取的内存大小向服务器请求内存数据。 ReadMemoryByAddress请求消息用于请求由参数memoryAddress和memorySize标识的服务器的内存数据。 用于memoryAddress和memorySize参数的…

SAP-MM-原始接受订单

业务场景&#xff1a; 供应商是强势供应商&#xff0c;产品紧缺&#xff0c;订购货物需要自提&#xff0c;运损也归我们公司&#xff0c;而且立刻付款&#xff0c;那么就不能按以往操作&#xff0c;等供应商送货&#xff0c;再开票 我们在付款&#xff0c;那么SAP如何快速实现…

JS 排序算法

在前端工作中算法不常用&#xff0c;但是排序可能会经常会用&#xff0c;下面学习几种常用算法。 引用借鉴&#xff1a;js的五种排序方法_js排序_木可生森的博客-CSDN博客 JS 常见的排序算法_js排序算法_东风过境F的博客-CSDN博客 1.冒泡排序&#xff1a; 思路&#xff1a;逐次…

儿童节快乐,基于CSS3绘制一个游乐场动效界面

0️⃣写在前面 让代码创造童话&#xff0c;共建快乐世界。六一儿童节——这是属于孩子们的节日&#xff0c;也是属于我们大人的节日。让我们一起「致童真」&#xff0c;用代码&#xff08;HTMLCSSJS&#xff09;创造出一个游乐场&#xff0c;让这个世界多一份快乐和惊喜&#x…

如何把vue项目部署服务器(宝塔面板)上

一&#xff0c;vue项目打包 首先我们把准备好的vue项目进行打包&#xff1a; 输入命令&#xff1a;npm run build 生成dist文件 二、进入宝塔管理界面&#xff0c;点击网站&#xff0c;然后点击添加站点 三。按下面输入 点设置 四。 输入好点添加&#xff0c;注意&#x…

【Python开发】FastAPI 04:响应模型

响应模型是指在接口调用之后&#xff0c;服务器返回给客户端的数据模型。这个数据模型可以是一个简单的字符串&#xff0c;也可以是一个复杂的数据结构&#xff0c;如 JSON 或 XML 格式的数据。本篇文章将详细介绍 FastAPI 中的响应模型。 目录 1 响应模型 1.1 response_mode…

HTML--Java EE

目录 一、认识 HTML 标签 二、HTML 文件基本结构 三、开发者工具 四、HTML常见标签 1.注释标签 2.标题标签&#xff08;h1-h6&#xff09; 3.段落标签&#xff08;p&#xff09; 4.换行标签 5.格式化标签 6.图片标签&#xff08;img&#xff09; 6.1网络路径 6.2绝…

Mysql索引慢解决

索引慢解决 explain关键字 explainSQL id执行顺序&#xff0c;id相同从上到下&#xff1b;id不同&#xff0c;如果是子查询id序号会递增&#xff0c;id值越大优先级越高&#xff1b;id相同和不同都存在时&#xff0c;id相同理解为一组&#xff0c;从上往下顺序执行&#xff0…

最优化——几种重要的凸集

引言 这是中科大最优化理论的笔记&#xff0c;中科大凌青老师的凸优化课程&#xff0c;详尽易懂&#xff0c;基础扎实。不论是初学者还是从业多年的人&#xff0c;都值得系统地好好学一遍。 本文介绍种重要的凸集:超平面与半空间、球和椭球、多面体、单纯形。 超平面与半空间…

202314读书笔记|《孩子们的诗》——简单的语言,击中每个人心中的诗意

202314读书笔记|《孩子们的诗》——简单的语言&#xff0c;击中每个人心中的诗意 《孩子们的诗》作者果麦。这本书中的诗都是孩子们写的&#xff0c;虽然他们或许并不认为自己写的是诗。每个人都想一直做一个孩子&#xff0c;好奇、天真、单纯、善良、简单&#xff0c;多美美好…

【产品应用】一体化步进电机在全自动折页机的应用

随着科技的不断发展&#xff0c;机器人技术也越来越成熟&#xff0c;智能化和自动化已经成为了现代工业的发展趋势。在这个趋势下&#xff0c;全自动折页机作为一种高效、精确的印刷设备&#xff0c;被广泛应用于各种印刷品的生产中。而一体化步进电机作为全自动折页机的关键部…

SpringBoot+Vue 前后端分离在线学习平台-在线教育平台

SpringBootVue 前后端分离在线学习平台-在线教育平台 本项目是完整的前后端分离&#xff0c;在线学习系统。【有完整部署视频教程】 创新点&#xff1a; ①基于阿里云短信平台完成手机号变更【短信验证码】 ②基于支付宝沙箱支付完成在线购买课程【在线支付】 ③基于apach-echa…

【Python】函数 ② ( 函数参数定义和使用 | 形式参数和实际参数 )

文章目录 一、函数参数定义和使用二、形式参数和实际参数 一、函数参数定义和使用 Python 函数参数 作用 : 在 Python 函数 执行时 , 可以 接受 外部 函数调用者 提供的数据 ; 在 Python 函数中 , 可以接受零个或多个参数 , 这些参数可以传递到函数中进行操作 ; 函数参数也可…

Java并发(九)----线程join、interrupt

1、join 方法详解 1.1 为什么需要 join&#xff1f; 下面的代码执行&#xff0c;打印 r 是什么&#xff1f; static int r 0; public static void main(String[] args) throws InterruptedException {test1(); } private static void test1() throws InterruptedException …

用C++ 包装STM32 官方固件库 - 链式调用改写初始化结构体

拿C 在固件库上套娃一层有几点原因&#xff1a; 固件库都是用C 写的&#xff0c;而我平时都用C&#xff0c;虽然是兼容的&#xff0c;但C 的一些特性我不喜欢&#xff1b;我不喜欢官方库的函数命名风格&#xff1b;各个厂家的固件库大同小异&#xff0c;但是“小异”的那一部分…

路径规划算法:基于旗鱼优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于旗鱼优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于旗鱼优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法旗鱼…

Direct3D 12——几何——基础

在几何里可分为两种几何&#xff0c;一种是显式几何&#xff0c;另外一种是隐式几何。有不同的方式表示不同的几何 隐式几何 隐式实际上是说不会告诉具体的这些点点就在哪&#xff0c;只告诉你这些点满足的关系。表示一定的关系但并不会给实际的点 例子&#xff1a; 隐式几何…

Ubuntu开机桌面黑屏只有鼠标问题解决办法(搜狗输入法导致)

参考&#xff1a; Ubuntu开机桌面黑屏只有鼠标问题解决办法&#xff08;搜狗输入法导致&#xff09; 问题描述 笔者在安装完搜狗输入法重启电脑后&#xff0c;电脑开机黑屏&#xff0c;只有鼠标的光标可以移动。笔者一开始以为是系统问题&#xff0c;网上查阅资料才发现有大量…

华为OD机试之全量和已占用字符集(Java源码)

全量和已占用字符集 题目描述 给定两个字符集合&#xff0c;一个是全量字符集&#xff0c;一个是已占用字符集&#xff0c;已占用字符集中的字符不能再使用。 输入描述 输入一个字符串 一定包含&#xff0c;前为全量字符集 后的为已占用字符集已占用字符集中的字符一定是全量字…