Android动画(四):PathMeasure实现路径动画
文章概览
- 1 PathMeasure概述
- 2 实现路径加载动画
- 3 实现箭头加载动画
- 4 实现操作成功动画
本系列将介绍以下内容:
1 PathMeasure概述
PathMeasure是一个单独的类,其全部源码如下(请详细研读注释):
package android.graphics;public class PathMeasure {private Path mPath;public PathMeasure() {mPath = null;native_instance = native_create(0, false);}/*** @param forceClosed If true, then the path will be considered as "closed"* even if its contour was not explicitly closed.* 如果为 "true",则路径将被视为 "封闭" 即使其轮廓没有明确封闭。*/public PathMeasure(Path path, boolean forceClosed) {// The native implementation does not copy the path, prevent it from being GC'dmPath = path;native_instance = native_create(path != null ? path.readOnlyNI() : 0,forceClosed);}public void setPath(Path path, boolean forceClosed) {mPath = path;native_setPath(native_instance,path != null ? path.readOnlyNI() : 0,forceClosed);}/*** Return the total length of the current contour, or 0 if no path is* associated with this measure object.* 返回当前轮廓的总长度,如果此测量对象没有关联路径,则返回 0。*/public float getLength() {return native_getLength(native_instance);}public boolean getPosTan(float distance, float pos[], float tan[]) {if (pos != null && pos.length < 2 ||tan != null && tan.length < 2) {throw new ArrayIndexOutOfBoundsException();}return native_getPosTan(native_instance, distance, pos, tan);}public static final int POSITION_MATRIX_FLAG = 0x01; // must match flags in SkPathMeasure.hpublic static final int TANGENT_MATRIX_FLAG = 0x02; // must match flags in SkPathMeasure.hpublic boolean getMatrix(float distance, Matrix matrix, int flags) {return native_getMatrix(native_instance, distance, matrix.ni(), flags);}/*** @param dst 将截取的Path添加(不是替换)到dst中。* @param startWithMoveTo 起始点是否使用moveTo* * 注意:* 1、路径截取是以路径的左上角为起始点开始的。* 2、路径的截取方向与路径的生成方向相同。* 3、截取的Path片段是被添加到路径dst中,而不是替换dst中的内容。* 4、如果startWithMoveTo为true,则被截取出来的Path片段保持原状;如果为false,则会将截取出来的Path片段的起始点移动到dst的最后一个点,以保证dst路径的连续性。*/public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) {float length = getLength();if (startD < 0) {startD = 0;}if (stopD > length) {stopD = length;}if (startD >= stopD) {return false;}return native_getSegment(native_instance, startD, stopD, dst.mutateNI(), startWithMoveTo);}/*** Return true if the current contour is closed()* 如果当前轮廓封闭,则返回 true()*/public boolean isClosed() {return native_isClosed(native_instance);}/*** Move to the next contour in the path. Return true if one exists, or* false if we're done with the path.* 移动到路径中的下一个轮廓。如果存在下一个轮廓,则返回 true;* 如果已经完成路径的移动,则返回 false。* * 注意:通过该方法得到的曲线的顺序与Path中添加的顺序相同。*/public boolean nextContour() {return native_nextContour(native_instance);}protected void finalize() throws Throwable {native_destroy(native_instance);native_instance = 0; // Other finalizers can still call us.}private static native long native_create(long native_path, boolean forceClosed);private static native void native_setPath(long native_instance, long native_path, boolean forceClosed);private static native float native_getLength(long native_instance);private static native boolean native_getPosTan(long native_instance, float distance, float pos[], float tan[]);private static native boolean native_getMatrix(long native_instance, float distance, long native_matrix, int flags);private static native boolean native_getSegment(long native_instance, float startD, float stopD, long native_path, boolean startWithMoveTo);private static native boolean native_isClosed(long native_instance);private static native boolean native_nextContour(long native_instance);private static native void native_destroy(long native_instance);private long native_instance;
}
PathMeasure的初始化方法是
Path mCirclePath = new Path();PathMeasure mPathMeasure = new PathMeasure();
mPathMeasure.setPath(mCirclePath, true);
或
Path mCirclePath = new Path();
PathMeasure mPathMeasure = new PathMeasure(mCirclePath, false);
getLength()、getSegment()都只会针对其中第一条线段进行计算。它们针对的是当前的曲线,而不是整个Path,所以getLength()方法获取到的是当前曲线的长度,而不是整个Path的长度。
2 实现路径加载动画
主要使用PathMeasure的getSegment(x)方法实现动画效果。
直接在布局文件中引用:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><com.example.myapplication.GetSegmentViewandroid:layout_width="match_parent"android:layout_height="match_parent" /></LinearLayout>
自定义的GetSegmentView:
package com.example.myapplication;import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
import android.view.View;import androidx.annotation.NonNull;public class GetSegmentView extends View {private Paint mPaint;private Path mCirclePath, mDstPath;private PathMeasure mPathMeasure;private Float mCurAnimValue;public GetSegmentView(Context context, AttributeSet attrs) {super(context, attrs);setLayerType(LAYER_TYPE_SOFTWARE, null);mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeWidth(4);mPaint.setColor(Color.BLACK);mDstPath = new Path();mCirclePath = new Path();mCirclePath.addCircle(100, 100, 50, Path.Direction.CW);mPathMeasure = new PathMeasure(mCirclePath, true);ValueAnimator animator = ValueAnimator.ofFloat(0, 1);animator.setRepeatCount(ValueAnimator.INFINITE);animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(@NonNull ValueAnimator animation) {mCurAnimValue = (Float) animation.getAnimatedValue();invalidate();}});animator.setDuration(2000);animator.start();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);float length = mPathMeasure.getLength();float stop = length * mCurAnimValue;float start = (float) (stop - ((0.5 - Math.abs(mCurAnimValue - 0.5)) * length));// 清空之前生成的路径mDstPath.reset();canvas.drawColor(Color.WHITE);mPathMeasure.getSegment(0, stop, mDstPath, true);
// mPathMeasure.getSegment(start, stop, mDstPath, true);canvas.drawPath(mDstPath, mPaint);}}
效果图:
上述动画效果的起始位置是从0开始的,将onDraw(x)中的代码切换,改变动画的起始位置:
// mPathMeasure.getSegment(0, stop, mDstPath, true);mPathMeasure.getSegment(start, stop, mDstPath, true);
效果图:
3 实现箭头加载动画
利用PathMeasure的getPosTan(x)方法实现箭头加载动画。
箭头资源图片:
布局文件引用:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><com.example.myapplication.GetPosTanViewandroid:layout_width="match_parent"android:layout_height="match_parent" /></LinearLayout>
自定义的GetPosTanView:
package com.example.myapplication;import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;import androidx.annotation.NonNull;public class GetPosTanView extends View {private Paint mPaint;private Path mCirclePath, mDstPath;private PathMeasure mPathMeasure;private Float mCurAnimValue;private Bitmap mArrawBmp;private float[] pos = new float[2];private float[] tan = new float[2];public GetPosTanView(Context context, AttributeSet attrs) {super(context, attrs);setLayerType(LAYER_TYPE_SOFTWARE, null);// 缩小箭头图片BitmapFactory.Options options = new BitmapFactory.Options();options.inSampleSize = 6;mArrawBmp = BitmapFactory.decodeResource(getResources(), R.drawable.arraw, options);mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeWidth(4);mPaint.setColor(Color.BLACK);mDstPath = new Path();mCirclePath = new Path();mCirclePath.addCircle(200, 200, 50, Path.Direction.CW);mPathMeasure = new PathMeasure(mCirclePath, true);ValueAnimator animator = ValueAnimator.ofFloat(0, 1);animator.setRepeatCount(ValueAnimator.INFINITE);animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(@NonNull ValueAnimator animation) {mCurAnimValue = (Float) animation.getAnimatedValue();invalidate();}});animator.setDuration(2000);animator.start();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.drawColor(Color.WHITE);float length = mPathMeasure.getLength();float stop = length * mCurAnimValue;mDstPath.reset();mPathMeasure.getSegment(0, stop, mDstPath, true);canvas.drawPath(mDstPath, mPaint);// 箭头旋转、位移实现方式一,通过getPosTan(x)实现mPathMeasure.getPosTan(stop, pos, tan);float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);Matrix matrix = new Matrix();matrix.postRotate(degrees, mArrawBmp.getWidth() / 2, mArrawBmp.getHeight() / 2);matrix.postTranslate(pos[0] - mArrawBmp.getWidth() / 2, pos[1] - mArrawBmp.getHeight() / 2);// 箭头旋转、位移实现方式二,通过getMatrix(x)实现/*Matrix matrix = new Matrix();mPathMeasure.getMatrix(stop,matrix,PathMeasure.POSITION_MATRIX_FLAG | PathMeasure.TANGENT_MATRIX_FLAG);matrix.preTranslate(-mArrawBmp.getWidth() / 2, -mArrawBmp.getHeight() / 2);*/canvas.drawBitmap(mArrawBmp, matrix, mPaint);}}
效果图:
4 实现操作成功动画
需要用到PathMeasure的nextContour()方法。
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><com.example.myapplication.OperationViewandroid:layout_width="match_parent"android:layout_height="match_parent" /></LinearLayout>
自定义OperationView:
package com.example.myapplication;import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;import androidx.annotation.NonNull;public class OperationView extends View {private Paint mPaint;private Path mCirclePath, mDstPath;private PathMeasure mPathMeasure;private Float mCurAnimValue;private int mCentX = 200;private int mCentY = 200;private int mRadius = 50;boolean mNext = false;public OperationView(Context context, AttributeSet attrs) {super(context, attrs);setLayerType(LAYER_TYPE_SOFTWARE, null);mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeWidth(4);mPaint.setColor(Color.BLACK);mDstPath = new Path();mCirclePath = new Path();mCirclePath.addCircle(mCentX, mCentY, mRadius, Path.Direction.CW);mCirclePath.moveTo(mCentX - mRadius / 2, mCentY);mCirclePath.lineTo(mCentX, mCentY + mRadius / 2);mCirclePath.lineTo(mCentX + mRadius / 2, mCentY - mRadius / 3);mPathMeasure = new PathMeasure(mCirclePath, false);// 0~1之间画第一条路径,1~2之间画第二条路径ValueAnimator animator = ValueAnimator.ofFloat(0, 2);animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(@NonNull ValueAnimator animation) {mCurAnimValue = (Float) animation.getAnimatedValue();invalidate();}});animator.setDuration(4000);animator.start();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.drawColor(Color.WHITE);if (mCurAnimValue < 1) {float stop = mPathMeasure.getLength() * mCurAnimValue;mPathMeasure.getSegment(0, stop, mDstPath, true);} else {if (!mNext) {mNext = true;mPathMeasure.getSegment(0, mPathMeasure.getLength(), mDstPath, true);mPathMeasure.nextContour();}float stop = mPathMeasure.getLength() * (mCurAnimValue - 1);mPathMeasure.getSegment(0, stop, mDstPath, true);}canvas.drawPath(mDstPath, mPaint);}}
效果图:
参考文献:
[1] UML中的类图及类图之间的关系
[2] 启舰.Android自定义控件开发入门与实战[M].北京:电子工业出版社,2018
微信公众号:TechU
相关文章:

Android动画(四):PathMeasure实现路径动画
文章概览 1 PathMeasure概述2 实现路径加载动画3 实现箭头加载动画4 实现操作成功动画 本系列将介绍以下内容: Android动画 1 PathMeasure概述 PathMeasure是一个单独的类,其全部源码如下(请详细研读注释): package…...

HTTP 连接详解
概述 世界上几乎所有的 HTTP 通信都是由 TCP/IP 承载的,客户端可以打开一条TCP/IP连接,连接到任何地方的服务器。一旦连接建立,客户端和服务器之间交换的报文就永远不会丢失、受损或失序 TCP(Transmission Control Protocol&…...

练习题(2024/5/12)
1二分查找 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 示例 1: 输入: nums [-1,0,3,5,9,12], target 9 输出: 4…...
Day50代码随想录动态规划part12:309.最佳买卖股票时机含冷冻期、714.买卖股票的最佳时机含手续费
Day50 动态规划part12 股票问题 309.最佳买卖股票时机含冷冻期 leetcode题目链接:309. 买卖股票的最佳时机含冷冻期 - 力扣(LeetCode) 题意:给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。设计一个算…...
【软考】scrum的步骤
目录 1. 明确产品愿景和需求2. 制定计划和任务列表3. 进行迭代开发(Sprint)4. Sprint评审会议5. Sprint回顾会议6. 重复迭代 1. 明确产品愿景和需求 1.这个过程通常由项目所有者和利益相关者参与,目的是确保整个团队对项目的目标和方向有清晰…...

【C语言】编译与链接
✨✨欢迎大家来到Celia的博客✨✨ 🎉🎉创作不易,请点赞关注,多多支持哦🎉🎉 所属专栏:C语言 个人主页:Celias blog~ 目录 引言 一、翻译环境 1.1 编译 1.1.1 预处理 1.1.2 编译 …...
Consul 注册的服务地址变成了 127.0.1.1
问题 我们的服务一直用 Consul 作为注册中心,在 AWS 和 阿里云上使用的时候,没出现过问题。最近把一些服务迁到腾讯云的时候,遇到一个问题:注册的服务地址都是 127.0.1.1。 127.0.1.1 这个地址我们平时遇到的比较少,…...
数字水印 | 离散小波变换 DWT 的 Python 代码实现
🍍原文: 【图像处理】图像离散小波变换及 Python 代码实现 🍍写在前面: 本文在原文的基础上补全了代码。 1 环境准备 ① 安装 p y w t \mathsf{pywt} pywt 包: pip install PyWavelets说明: p y w t \…...
[框架] Unity 公共执行器
本篇我们通过使用单例模式来创建一个公共执行器,使得原本应该在Update()、FixedUpdate()中的指令都可以统一放在一个对象中执行,且可进行添加和移除操作。 1. 创建单例模式改造器:SingletonMono 我们先创建一个单例模式改造器,使…...

二进制转为HEX数组小工具
在使用RA8889时,JPG的解码只能从FLASH的DMA通道获取,那么如果要从远端、或者SD卡等处读取JPG图片出来显示怎么办? RA8889支持JPG图片硬解码,但数据流是从FLASH进行DMA读取的,然后再进行解码。因此这种情况下ÿ…...

数据结构-二叉树-红黑树
一、红黑树的概念 红黑树是一种二叉搜索树,但在每个节点上增加一个存储位表示节点的颜色,可以是Red或者BLACK,通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,…...
C++11 新特性 decltype 说明符
一、typeof与typeid 1.1、typeof 在C11标准之前,GCC已经提供了一个类似功能的运算符 typeof对类型进行推导,但是这毕竟是编译器的实现,不是标准。 int a 0; typeof(a) b 5;1.2、typeid C标准提供了 typeid 运算符,获取的类型…...

java线程局部变量使用方式
线程局部变量是Java中用于存储线程本地信息的变量。这种变量仅在线程的生命周期内存在,并且每个线程都有自己的一份拷贝。换句话说,线程局部变量是线程私有的,其他线程无法访问。 使用场景主要包括: 1. 存储线程状态信息ÿ…...
【隧道篇 / WAN优化】(7.4) ❀ 01. 启动WAN优化 ❀ FortiGate 防火墙
【简介】几乎所有的人都知道,防火墙自带的硬盘是用来保存日志,以方便在出现问题时能找到原因。但是很少的人知道,防火墙自带的硬盘其实还有另一个功能,那就是用于WAN优化。 防火墙自带的硬盘 在FortiGate防火墙A、B、C、D系列&…...

2024数维杯数学建模B题生物质和煤共热解问题的研究原创论文分享
大家好,从昨天肝到现在,终于完成了2024数维杯数学建模挑战赛B题的完整论文啦。 实在精力有限,具体的讲解大家可以去讲解视频: 2024数维杯数学建模B题煤共热解每一问高质量完整代码讲解!_哔哩哔哩_bilibili 2024数维杯…...
中国电子学会(CEIT)2022年12月真题C语言软件编程等级考试三级(含详细解析答案)
中国电子学会(CEIT)考评中心历届真题(含解析答案) C语言软件编程等级考试一级 2022年12月 编程题五道 总分:100分一、鸡兔同笼(20分) 一个笼子里面关了鸡和兔子(鸡有2只脚,兔子有4只脚,没有例外)。已经知道了笼子里面脚的总数a,问笼子里面至少有多少只动物,至…...

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 5月12日,星期日
每天一分钟,知晓天下事! 2024年5月12日 星期日 农历四月初五 1、 全国多地已推“一次挂号管三天”,部分医院专家门诊适用。 2、 在梅大高速塌方事故中拦车、救援,黄曼秋等5人拟确认为见义勇为。 3、 深圳新能源车指标申请条件调…...

微服务思想以及实现
文章目录 前言一、什么时候需要拆分微服务1. 创业型项目2. 大型项目 二、怎么拆1. 拆分目标2. 拆分方式 三、微服务之间远程调用1. 实现方式2. 手动发送Http请求(RestTemplate)3. 服务注册中心3.1 原理3.2 Nacos注册中心3.3 服务注册3.4 服务发现(Discov…...
C语法:格式符号%f和%lf引发的错误
今天编程时有如下代码: #include"stdio.h"int main(void) {double profit;double bonus;printf("请输入本月利润\n");scanf("%f",&profit);//错误:此行profit是double类型,格式符为%f,当输入8时࿰…...

Java基础入门day48
day48 JDBC调用关系 tomcat 简介 tomcat是Apache下的一个核心项目,免费开源,支持servlet和jsp。 tomcat技术先进,性能稳定,目前比较流行的web应用服务器 安装 官网: Apache Tomcat - Welcome! 下载 tomcat8.5 解压&a…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...

【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...