Android 圆环带刻度条进度动画效果实现

news2024/12/25 13:17:10

效果图
圆环带刻度进度

需求是根据传感器做一个重力球效果,先实现了动画后续加上跟传感器联动.
又是摆烂的一天, 尚能呼吸,未来可期啊

View源码

package com.android.circlescalebar.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.View;
import com.android.circlescalebar.R;
import com.android.circlescalebar.utils.ChartUtils;
import com.android.circlescalebar.utils.DensityUtils;

public class CircleGearView extends View {

    private Context mContext;
    private Paint mPaint; // 画笔对象的引用
    private PointF mProgressPoint;
    private float mRoundWidth = DensityUtils.dp2px(4); // 圆环的宽度
    private int centerX, centerY;
    private int radius, roundRadius;
    private int paddingOuterThumb;//外边距
    private int minValidateTouchArcRadius; // 最小有效点击半径
    private int maxValidateTouchArcRadius; // 最大有效点击半径
    private int mMainColor; //主题颜色
    private int mInnerRoundColor; //内圆 宽度 、颜色
    private float mInnerRoundWidth;
    private int mTxtProgress = 1; // 显示进度
    private int max = 200; // 最大进度 -- 总共200个刻度 所以这样定义
    private float progress = 1;
    private double mOuterRoundProgress = 0f;//外圈进度
    private boolean mOuterSences = true; //true 正向----false方向

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

    public CircleGearView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleGearView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        initView(attrs);
    }


    private void initView(AttributeSet attrs){
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);  // 关闭硬件加速
        this.setWillNotDraw(false);                    // 调用此方法后,才会执行 onDraw(Canvas) 方法
        mPaint = new Paint();

        //获取自定义属性和默认值
        TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.CGViewStyleable);

        mRoundWidth = typedArray.getDimension(R.styleable.CGViewStyleable_round_width, DensityUtils.dp2px(7));
        mMainColor = typedArray.getColor(R.styleable.CGViewStyleable_round_color, getResources().getColor(R.color.green));

        mInnerRoundWidth = typedArray.getDimension(R.styleable.CGViewStyleable_inner_round_width, DensityUtils.dp2px(2));
        mInnerRoundColor = typedArray.getColor(R.styleable.CGViewStyleable_inner_round_color, getResources().getColor(R.color.white33));

        paddingOuterThumb = DensityUtils.dp2px(20);
    }

    @Override
    protected void onSizeChanged(int width, int height, int oldw, int oldh) {
        centerX = width / 2;
        centerY = height / 2;
        int minCenter = Math.min(centerX, centerY);

        radius = (int) (minCenter - mRoundWidth / 2 - paddingOuterThumb); //圆环的半径
        roundRadius = radius - (int)(3 * mRoundWidth);
        minValidateTouchArcRadius = (int) (radius - paddingOuterThumb * 1.5f);
        maxValidateTouchArcRadius = (int) (radius + paddingOuterThumb * 1.5f);
        super.onSizeChanged(width, height, oldw, oldh);
    }

    @Override
    public void onDraw(Canvas canvas) {
      //  setLayerType(LAYER_TYPE_SOFTWARE, null);//对单独的View在运行时阶段禁用硬件加速
        initOnDraw(canvas);
    }

    /** start circle -*/
    private void initOnDraw(Canvas canvas) {
        /** 画刻度-200份- 还分正反切换---start */
        mPaint.setStrokeWidth(DensityUtils.dp2px(1));
        for (int i = 0; i < 200; i++){
            //radius:模糊半径,radius越大越模糊,越小越清晰,但是如果radius设置为0,则阴影消失不见
            //dx:阴影的横向偏移距离,正值向右偏移,负值向左偏移
            //dy:阴影的纵向偏移距离,正值向下偏移,负值向上偏移
            //color: 绘制阴影的画笔颜色,即阴影的颜色(对图片阴影无效)

            if (i < mOuterRoundProgress) {
                if (mOuterSences) {
//                    mPaint.setShadowLayer(30, 0, 0, mMainColor);
                    mPaint.setColor(getResources().getColor(R.color.green));
                } else
                    mPaint.setColor(getResources().getColor(R.color.white33));
            } else {
                if (mOuterSences)
                    mPaint.setColor(getResources().getColor(R.color.white33));
                else {
//                    mPaint.setShadowLayer(30, 0, 0, mMainColor);
                    mPaint.setColor(getResources().getColor(R.color.green));
                }
            }
            float mProgress = (i)* 1.0f/ 200 * max;
            PointF mProgressPoint = ChartUtils.calcArcEndPointXY(centerX, centerY, radius, 360 * mProgress / max, 90);
            //圆上到圆心
            float scale1 = radius * 1.0F / mRoundWidth;
            float scale2 = radius * 1.0F / (radius - mRoundWidth);
            //计算内圆上的点
            float disX = (scale1*mProgressPoint.x + scale2*centerX)/(scale1+ scale2);
            float disY =  (scale1*mProgressPoint.y + scale2*centerY)/(scale1+ scale2);
            //计算外圆上的点
            float disX2 = mProgressPoint.x*2 - disX;
            float disY2 =  mProgressPoint.y*2 - disY;
//            if (mProgress%6 == 0){
//                //直线3/4高度
//                canvas.drawLine(disX2 ,disY2,disX,disY, mPaint);
//            }else{
                //直线1/2高度
                float disX3 = (disX*1 + disX2)/2;
                float disY3 =  (disY*1 + disY2)/2;
                canvas.drawLine(disX3 ,disY3,disX,disY, mPaint);
//            }
        }
        /** 画刻度-200份- 还分正反切换---end */

        // 移动圆点
        mProgressPoint = ChartUtils.calcArcEndPointXY(centerX, centerY, radius - 55, 360 *
                progress / max, (float)90);
//        //直接用画笔画
        mPaint.setColor(getResources().getColor(R.color.green));  //设置进度的颜色
        // 设置渐变
//        Shader shader = new RadialGradient(
//                0, 0, 50, // 圆的中心坐标和半径
//                mMainColor, mInnerRoundColor, // 渐变的起止颜色
//                Shader.TileMode.CLAMP // 渐变模式
//        );
//        mPaint.setShader(shader);
        canvas.drawCircle(mProgressPoint.x, mProgressPoint.y,30 ,mPaint);
        canvas.restore();
        canvas.save();

    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
    }

    /**
     * 设置进度,此为线程安全控件,由于考虑多线的问题,需要同步
     * 刷新界面调用postInvalidate()能在非UI线程刷新
     *
     * @param progress
     */
    public synchronized void setProgress(float progress) {
        if (progress < 0) {
            mTxtProgress = 1;
            progress = 0;
        }
        mTxtProgress =  Math.round(progress);
        float ss = progress * 200 / 100;
        progress = (int) ss;
        if (progress < 0) {
            throw new IllegalArgumentException("progress not less than 0");
        }
        if (progress > max) {
            progress = max;
            mOuterRoundProgress = progress + 1;
        }
        if (progress <= max) {
            this.progress = progress;
            mOuterRoundProgress = progress + 1;
            postInvalidate();
        }
    }
}

工具类

package com.android.circlescalebar.utils;

import android.graphics.PointF;

public class ChartUtils {

    /**
     * 依圆心坐标,半径,扇形角度,计算出扇形终射线与圆弧交叉点的xy坐标
     *
     * @param cirX     圆centerX
     * @param cirY     圆centerY
     * @param radius   圆半径
     * @param cirAngle 当前弧角度
     * @return 扇形终射线与圆弧交叉点的xy坐标
     */
    public static PointF calcArcEndPointXY(float cirX, float cirY, float radius, float
            cirAngle) {
        float posX = 0.0f;
        float posY = 0.0f;
        //将角度转换为弧度
        float arcAngle = (float) (Math.PI * cirAngle / 180.0);
        if (cirAngle < 90) {
            posX = cirX + (float) (Math.cos(arcAngle)) * radius;
            posY = cirY + (float) (Math.sin(arcAngle)) * radius;
        } else if (cirAngle == 90) {
            posX = cirX;
            posY = cirY + radius;
        } else if (cirAngle > 90 && cirAngle < 180) {
            arcAngle = (float) (Math.PI * (180 - cirAngle) / 180.0);
            posX = cirX - (float) (Math.cos(arcAngle)) * radius;
            posY = cirY + (float) (Math.sin(arcAngle)) * radius;
        } else if (cirAngle == 180) {
            posX = cirX - radius;
            posY = cirY;
        } else if (cirAngle > 180 && cirAngle < 270) {
            arcAngle = (float) (Math.PI * (cirAngle - 180) / 180.0);
            posX = cirX - (float) (Math.cos(arcAngle)) * radius;
            posY = cirY - (float) (Math.sin(arcAngle)) * radius;
        } else if (cirAngle == 270) {
            posX = cirX;
            posY = cirY - radius;
        } else {
            arcAngle = (float) (Math.PI * (360 - cirAngle) / 180.0);
            posX = cirX + (float) (Math.cos(arcAngle)) * radius;
            posY = cirY - (float) (Math.sin(arcAngle)) * radius;
        }
        return new PointF(posX, posY);
    }

    /**
     * 依圆心坐标,半径,扇形角度,计算出扇形终射线与圆弧交叉点的xy坐标
     *
     * @param cirX       圆centerX
     * @param cirY       圆centerY
     * @param radius     圆半径
     * @param cirAngle   当前弧角度
     * @param orginAngle 起点弧角度
     * @return 扇形终射线与圆弧交叉点的xy坐标
     */
    public static PointF calcArcEndPointXY(float cirX, float cirY, float radius, float
            cirAngle, float orginAngle) {
        cirAngle = (orginAngle + cirAngle) % 360;
        return calcArcEndPointXY(cirX, cirY, radius, cirAngle);
    }
}
package com.android.circlescalebar.utils;

import android.content.res.Resources;

public class DensityUtils {
    public float density;

    public DensityUtils() {
        density = Resources.getSystem().getDisplayMetrics().density;
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     * @param dpValue 虚拟像素
     * @return 像素
     */
    public static int dp2px(float dpValue) {
        return (int) (0.5f + dpValue * Resources.getSystem().getDisplayMetrics().density);
    }

    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     * @param pxValue 像素
     * @return 虚拟像素
     */
    public static float px2dp(int pxValue) {
        return (pxValue / Resources.getSystem().getDisplayMetrics().density);
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     * @param dpValue 虚拟像素
     * @return 像素
     */
    public int dip2px(float dpValue) {
        return (int) (0.5f + dpValue * density);
    }

    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     * @param pxValue 像素
     * @return 虚拟像素
     */
    public float px2dip(int pxValue) {
        return (pxValue / density);
    }
}

调用实现

    private int count = 0;  
    private Handler handler = new Handler();  
    private Runnable updateTextRunnable;  

    private CircleGearView circleGearView;
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
  
        circleGearView = findViewById(R.id.circleGearView); 
  
        updateTextRunnable = new Runnable() {  
            @Override  
            public void run() {  
                circleGearView.setProgress(count);
                count++;  
                if (count > 100) {  
                    // 停止循环  
                    handler.removeCallbacks(this);  
                } else {  
                    // 继续循环  
                    handler.postDelayed(this, 1000); // 每秒更新一次  
                }  
            }  
        };  
  
        // 开始循环  
        handler.post(updateTextRunnable);  
    }  
  
    @Override  
    protected void onDestroy() {  
        super.onDestroy();  
        // 确保在Activity销毁时移除所有回调和消息,防止内存泄漏  
        handler.removeCallbacks(updateTextRunnable);  
    }  

布局

    <com.android.circlescalebar.view.CircleGearView
        android:id="@+id/circleGearView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:inner_round_color="@color/white33"
        app:inner_round_width="2dp"
        app:round_color="@color/green"
        app:round_width="7dp" />  

attrs

    <declare-styleable name="CGViewStyleable">
        <!-- 圆的宽度 -->
        <attr name="round_width" format="dimension"/>
        <attr name="round_color" format="color"/>
        <attr name="inner_round_width" format="dimension"/>
        <attr name="inner_round_color" format="color"/>
    </declare-styleable>

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

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

相关文章

IIC通信驱动EEPROM,AT24C02硬件存储器编程(2)

接着上一篇博客文章讲解了IIC协议的原理及编程思路&#xff0c;本篇博客文章将以IIC为基础&#xff0c;从芯片手册入手&#xff0c;梳理讲解如何对AT24C02进行驱动编程&#xff0c;实现数据的读写操作。IIC通信驱动硬件编程 (1)-CSDN博客https://blog.csdn.net/weixin_49337111…

flannel网络拓扑

测试环境创建 在k8s中部署flannel网络插件 https://blog.csdn.net/weixin_64124795/article/details/128894411 参考文章部署k8s集群和flannel网络插件 我的k8s集群物理环境 我的集群中只有两个节点master和node1节点 [rootmaster sjs]# kubectl get node NAME STATU…

智慧安防/视频监控汇聚平台EasyCVR如何通过接口调用获取设备录像回看的流地址?

视频云存储/视频融合/安防监控EasyCVR视频汇聚系统可兼容各品牌的IPC、NVR、移动单兵、智能手持终端、移动执法仪、无人机、布控球等设备的接入&#xff0c;支持的接入协议包括&#xff1a;国标GB28181、RTSP/Onvif、RTMP&#xff0c;以及厂家的私有协议与SDK&#xff0c;如&am…

【DDD】学习笔记-领域事件

作为一种领域分析建模方法&#xff0c;事件风暴将事件视为一种建模的手段&#xff0c;将不同的团队角色统一到一个共同的业务场景下&#xff0c;同时又利用了事件的因果关系驱动我们把握业务的整体流程。在这个过程中&#xff0c;领域事件在事件风暴中起到了核心的驱动作用&…

JSON.stringify() 第三个参数的使用

语法 JSON.stringify(value[, replacer[, space]]) 参数说明&#xff1a; value: 必需&#xff0c; 要转换的 JavaScript 值&#xff08;通常为对象或数组&#xff09;。 replacer: 可选。用于转换结果的函数或数组。 如果 replacer 为函数&#xff0c;则 JSON.stringify …

C++从入门到精通 第十四章(STL容器)【上】

写在前面&#xff1a; 本系列专栏主要介绍C的相关知识&#xff0c;思路以下面的参考链接教程为主&#xff0c;大部分笔记也出自该教程&#xff0c;笔者的原创部分主要在示例代码的注释部分。除了参考下面的链接教程以外&#xff0c;笔者还参考了其它的一些C教材&#xff08;比…

分享58个NodeJs爬虫源码总有一个是你想要的

分享58个NodeJs爬虫源码总有一个是你想要的 学习知识费力气&#xff0c;收集整理更不易。 知识付费甚欢喜&#xff0c;为咱码农谋福利。 链接&#xff1a;https://pan.baidu.com/s/1_Im6ituI4izxP05oyA2z3Q?pwd8888 提取码&#xff1a;8888 项目名称 anyproxy 、nodejs …

【算法 - 动态规划】最长回文子序列

上篇文章中&#xff0c;我们学习一个新的模型&#xff1a; 样本对应模型&#xff0c;该模型的套路就是&#xff1a;以结尾位置为出发点&#xff0c;思考两个样本的结尾都会产生哪些可能性 。 而前篇文章中的 纸牌博弈问题 属于 [L , R]上范围尝试模型。该模型给定一个范围&…

爬虫基本库的使用(requests库的详细解析)

注&#xff1a;本文一共4万多字&#xff0c;希望读者能耐心读完&#xff01;&#xff01;&#xff01; 前面,我们了解了urllib库的基本用法&#xff08;爬虫基本库的使用(urllib库的详细解析)-CSDN博客&#xff09;。其中&#xff0c;确实又不方便的地方。例如处理网页验证…

P2670 [NOIP2015 普及组] 扫雷游戏 ---- 洛谷

题目描述 扫雷游戏是一款十分经典的单机小游戏。在 n 行 m 列的雷区中有一些格子含有地雷&#xff08;称之为地雷格&#xff09;&#xff0c;其他格子不含地雷&#xff08;称之为非地雷格&#xff09;。玩家翻开一个非地雷格时&#xff0c;该格将会出现一个数字——提示周围格子…

【前端素材】推荐优质后台管理系统Xoric平台模板(附源码)

一、需求分析 当我们从多个层次来详细分析后台管理系统时&#xff0c;可以将其功能和定义进一步细分&#xff0c;以便更好地理解其在不同方面的作用和实际运作。 1. 功能层次 a. 用户管理功能&#xff1a; 用户注册和登录&#xff1a;管理用户账户的注册和登录过程。权限管…

C#上位机与三菱PLC的通信08---开发自己的通讯库(A-1E版)

1、A-1E报文回顾 具体细节请看&#xff1a; C#上位机与三菱PLC的通信03--MC协议之A-1E报文解析 C#上位机与三菱PLC的通信04--MC协议之A-1E报文测试 2、为何要开发自己的通讯库 前面使用了第3方的通讯库实现了与三菱PLC的通讯&#xff0c;实现了数据的读写&#xff0c;对于通…

熬夜整理的考研考公学习资料,祝愿大家成功上岸

现如今大学生毕业大都在考研、考公这两条道路上选一个。今天给这些朋友同学分享一下考研、考公的学习资料。希望能够帮助到部分努力的同学&#xff01; 以下就是考研、考公的学习资料 学习资料获取地址 点击获取学习资料 就拿考研来说这里不仅包含了深入浅出的复习笔记&#x…

steam搬砖项目还能不能做,新手小白月入过万真的假的

steam搬砖项目还能不能做&#xff1f;今天&#xff0c;我们将通过实际数据来告诉你&#xff0c;当前市场上存在着大量没有实操过Steam搬砖项目的人&#xff0c;他们也开始参与其中&#xff0c;导致市场格局混乱。这些人可能是第一次接触该项目&#xff0c;但却毫不犹豫地发表自…

Facebook Horizon:探索虚拟现实中的社交空间

随着科技的不断进步&#xff0c;虚拟现实&#xff08;VR&#xff09;技术正成为社交互动和娱乐体验的新前沿。在这个数字时代&#xff0c;Facebook作为全球最大的社交媒体平台之一&#xff0c;正在引领虚拟社交的新时代&#xff0c;其推出的虚拟社交平台Facebook Horizon成为了…

如何使用Docker部署MongoDB并结合内网穿透实现远程访问本地数据库

文章目录 前言1. 安装Docker2. 使用Docker拉取MongoDB镜像3. 创建并启动MongoDB容器4. 本地连接测试5. 公网远程访问本地MongoDB容器5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定TCP地址远程访问 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 …

Fiddler工具 — 19.Fiddler抓包HTTPS请求(二)

5、查看证书是否安装成功 方式一&#xff1a; 点击Tools菜单 —> Options... —> HTTPS —> Actions 选择第三项&#xff1a;Open Windows Certificate Manager打开Windows证书管理器。 打开Windows证书管理器&#xff0c;选择操作—>查看证书&#xff0c;在搜索…

【webrtc】m77 PacedSender

mediasoup是m77的代码,m77的代码并没有paced controller ,而且与paced sender 的逻辑混在了一起。结合大神们的代码分析,对照m77 进行 理解。m77 有ProbeController。给pacersender 更新飞行数据:PacedSender::InsertPacket(size_t bytes) 对应的是 PacingController::OnPa…

微信小程序错误----config is not defined

微信小程序出错 请求头发生错误 修改 options.header {// 为请求头对象添加 token 验证的 Authorization 字段Access-Token: token,platform: MP-WEIXIN,// 保留原有的 header...options.header,}

运维SRE-11 备份服务及备份项目

1.第一个服务-rsync备份服务-守护进程模式 1.1概述 守护进程&#xff1a;持续运行的进程&#xff0c;也可以叫作服务服务一般分为&#xff1a;服务端与客户端服务端&#xff1a;linux服务器上运行的各种服务软件客户端&#xff1a;linux中的客户端可能是一个命令&#xff0c;…