Android中PathMeasure仿支付宝支付动画
发布时间 - 2026-01-11 02:40:30 点击率:次前言

在 Android 自定义 View 中,Path 可能用的比较多,PathMeasure 可能用的比较少,就我而言,以前也没有使用过 PathMeasure 这个 api,看到别人用 PathMeasure 和 ValueAnimator 结合在一起完成了很好的动画效果,于是我也学习下 PathMeasure ,此处记录下。
PathMeasure
构造器:
forceClosed 含义:
// 创建一个 Path 对象 path = new Path(); path.moveTo(20, 20); path.lineTo(200, 20); path.lineTo(200, 400);
在onDraw(Canvas canvas) 中绘制 path
@Override
protected void onDraw(Canvas canvas) {
destPath.reset();
destPath.lineTo(0, 0);
pathMeasure.setPath(path, true);
Log.e("debug", "PathMeasure.getLength() = " + pathMeasure.getLength());
pathMeasure.getSegment(0, pathMeasure.getLength() * curValue, destPath, true);
canvas.drawPath(destPath, paint); // 绘制线段路径
}
当 pathMeasure.setPath(path,false) 时:
当 pathMeasure.setPath(path,true) 时:
可以看到:当 forceClosed = true 时, path 进行了闭合,相应的 path 长度也变长了,即 算上了斜边的长度。
仿支付宝支付动画 View - LoadingView
效果:
思路:
绘制对号,叉号,主要是通过 ValueAnimator 结合 getSegment() 不断绘制新的弧形段,其中,叉号由两个 path 组成,在第一个 path 绘制完成时,需要调用 pathMeasure.nextContour() 跳转到另一个 path。
getSegment() 将获取的片段填充到 destPath 中,在 Android 4.4 及以下版本中,不能绘制,需要调用 destPath.reset(),destPath.line(0,0)
LoadingView 完整代码:
public class LoadingView extends View {
private final int DEFAULT_COLOR = Color.BLACK; // 默认圆弧颜色
private final int DEFAULT_STROKE_WIDTH = dp2Px(2); // 默认圆弧宽度
private final boolean DEFAULT_IS_SHOW_RESULT = false; // 默认不显示加载结果
private final int DEFAULT_VIEW_WIDTH = dp2Px(50); // 控件默认宽度
private final int DEFAULT_VIEW_HEIGHT = dp2Px(50); // 控件默认高度
private int color; // 圆弧颜色
private int strokeWidth; // 圆弧宽度
private boolean isShowResult; // 是否显示加载结果状态
private Paint paint; // 画笔
private int mWidth; // 控件宽度
private int mHeight; // 控件高度
private int radius; // 圆弧所在圆的半径
private int halfStrokeWidth; // 画笔宽度的一半
private int rotateDelta = 4;
private int curAngle = 0;
private int minAngle = -90;
private int startAngle = -90; // 上方顶点
private int endAngle = 0;
private RectF rectF;
private StateEnum stateEnum = StateEnum.LOADING;
private Path successPath;
private Path rightFailPath;
private Path leftFailPath;
private ValueAnimator successAnimator;
private ValueAnimator rightFailAnimator;
private ValueAnimator leftFailAnimator;
private PathMeasure pathMeasure;
private float successValue;
private float rightFailValue;
private float leftFailValue;
private Path destPath;
private AnimatorSet animatorSet;
public LoadingView(Context context) {
this(context, null);
}
public LoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray typedArray = null;
try {
typedArray = context.obtainStyledAttributes(attrs, R.styleable.LoadingView);
color = typedArray.getColor(R.styleable.LoadingView_color, DEFAULT_COLOR);
strokeWidth = (int) typedArray.getDimension(R.styleable.LoadingView_storkeWidth, DEFAULT_STROKE_WIDTH);
isShowResult = typedArray.getBoolean(R.styleable.LoadingView_isShowResult, DEFAULT_IS_SHOW_RESULT);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (typedArray != null) {
typedArray.recycle();
}
}
paint = createPaint(color, strokeWidth, Paint.Style.STROKE);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
Log.i("debug", "getMeasureWidth() = " + getMeasuredWidth());
Log.i("debug", "getMeasureHeight() = " + getMeasuredHeight());
radius = Math.min(mWidth, mHeight) / 2;
halfStrokeWidth = strokeWidth / 2;
rectF = new RectF(halfStrokeWidth - radius, halfStrokeWidth - radius,
radius - halfStrokeWidth, radius - halfStrokeWidth);
// success path
successPath = new Path();
successPath.moveTo(-radius * 2 / 3f, 0f);
successPath.lineTo(-radius / 8f, radius / 2f);
successPath.lineTo(radius / 2, -radius / 3);
// fail path ,right top to left bottom
rightFailPath = new Path();
rightFailPath.moveTo(radius / 3f, -radius / 3f);
rightFailPath.lineTo(-radius / 3f, radius / 3f);
// fail path, left top to right bottom
leftFailPath = new Path();
leftFailPath.moveTo(-radius / 3f, -radius / 3f);
leftFailPath.lineTo(radius / 3f, radius / 3f);
pathMeasure = new PathMeasure();
destPath = new Path();
initSuccessAnimator();
initFailAnimator();
}
private void initSuccessAnimator() {
// pathMeasure.setPath(successPath, false);
successAnimator = ValueAnimator.ofFloat(0, 1f);
successAnimator.setDuration(1000);
successAnimator.setInterpolator(new LinearInterpolator());
successAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
successValue = (float) animation.getAnimatedValue();
invalidate();
}
});
}
private void initFailAnimator() {
// pathMeasure.setPath(rightFailPath, false);
rightFailAnimator = ValueAnimator.ofFloat(0, 1f);
rightFailAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
rightFailValue = (float) animation.getAnimatedValue();
invalidate();
}
});
// pathMeasure.setPath(leftFailPath, false);
leftFailAnimator = ValueAnimator.ofFloat(0, 1f);
leftFailAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
leftFailValue = (float) animation.getAnimatedValue();
invalidate();
}
});
animatorSet = new AnimatorSet();
animatorSet.play(leftFailAnimator).after(rightFailAnimator);
animatorSet.setDuration(500);
animatorSet.setInterpolator(new LinearInterpolator());
}
/**
* 测量控件的宽高,当测量模式不是精确模式时,设置默认宽高
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_VIEW_WIDTH, MeasureSpec.EXACTLY);
}
if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_VIEW_HEIGHT, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.translate(mWidth / 2, mHeight / 2);
destPath.reset();
destPath.lineTo(0, 0); // destPath
if (stateEnum == StateEnum.LOADING) {
if (endAngle >= 300 || startAngle > minAngle) {
startAngle += 6;
if (endAngle > 20) {
endAngle -= 6;
}
}
if (startAngle > minAngle + 300) {
minAngle = startAngle;
endAngle = 20;
}
canvas.rotate(curAngle += rotateDelta, 0, 0);//旋转rotateDelta=4的弧长
canvas.drawArc(rectF, startAngle, endAngle, false, paint);
// endAngle += 6 放在 drawArc()后面,是防止刚进入时,突兀的显示了一段圆弧
if (startAngle == minAngle) {
endAngle += 6;
}
invalidate();
}
if (isShowResult) {
if (stateEnum == StateEnum.LOAD_SUCCESS) {
pathMeasure.setPath(successPath, false);
canvas.drawCircle(0, 0, radius - halfStrokeWidth, paint);
pathMeasure.getSegment(0, successValue * pathMeasure.getLength(), destPath, true);
canvas.drawPath(destPath, paint);
} else if (stateEnum == StateEnum.LOAD_FAILED) {
canvas.drawCircle(0, 0, radius - halfStrokeWidth, paint);
pathMeasure.setPath(rightFailPath, false);
pathMeasure.getSegment(0, rightFailValue * pathMeasure.getLength(), destPath, true);
if (rightFailValue == 1) {
pathMeasure.setPath(leftFailPath, false);
pathMeasure.nextContour();
pathMeasure.getSegment(0, leftFailValue * pathMeasure.getLength(), destPath, true);
}
canvas.drawPath(destPath, paint);
}
}
canvas.restore();
}
public void updateState(StateEnum stateEnum) {
this.stateEnum = stateEnum;
if (stateEnum == StateEnum.LOAD_SUCCESS) {
successAnimator.start();
} else if (stateEnum == StateEnum.LOAD_FAILED) {
animatorSet.start();
}
}
public enum StateEnum {
LOADING, // 正在加载
LOAD_SUCCESS, // 加载成功,显示对号
LOAD_FAILED // 加载失败,显示叉号
}
/**
* 创建画笔
*
* @param color 画笔颜色
* @param strokeWidth 画笔宽度
* @param style 画笔样式
* @return
*/
private Paint createPaint(int color, int strokeWidth, Paint.Style style) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setColor(color);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(style);
return paint;
}
/**
* dp 转换成 px
*
* @param dpValue
* @return
*/
private int dp2Px(int dpValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics());
}
}
github : https://github.com/xing16/LoadingView
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# Android
# PathMeasure
# 支付宝
# Android中RecyclerView布局代替GridView实现类似支付宝的界面
# Android波纹扩散效果之仿支付宝咻一咻功能实现波纹扩散特效
# Android app第三方支付宝支付接入教程
# Android支付宝和微信支付集成
# Android仿支付宝支付从底部弹窗效果
# Android支付宝支付封装代码
# 新版Android studio导入微信支付和支付宝官方Demo问题解决大全
# Android开发之实现GridView支付宝九宫格
# Android自定义View仿支付宝输入六位密码功能
# android仿微信支付宝的支付密码输入框示例
# 加载
# 我也
# 很好
# 放在
# 第一个
# 上了
# 可以看到
# 自定义
# 比较多
# 转换成
# 长了
# 使用过
# 创建一个
# 正在加载
# 就我
# 比较少
# 大家多多
# 进行了
# 刚进
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
如何在Windows虚拟主机上快速搭建网站?
Claude怎样写约束型提示词_Claude约束提示词写法【教程】
php json中文编码为null的解决办法
如何用wdcp快速搭建高效网站?
头像制作网站在线观看,除了站酷,还有哪些比较好的设计网站?
如何选择PHP开源工具快速搭建网站?
Linux安全能力提升路径_长期防护思维说明【指导】
Laravel怎么定时执行任务_Laravel任务调度器Schedule配置与Cron设置【教程】
php中::能调用final静态方法吗_final修饰静态方法调用规则【解答】
悟空识字如何进行跟读录音_悟空识字开启麦克风权限与录音
北京的网站制作公司有哪些,哪个视频网站最好?
SQL查询语句优化的实用方法总结
Laravel如何发送系统通知_Laravel Notifications实现多渠道消息通知
如何快速建站并高效导出源代码?
Laravel项目如何进行性能优化_Laravel应用性能分析与优化技巧大全
详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)
小视频制作网站有哪些,有什么看国内小视频的网站,求推荐?
宙斯浏览器视频悬浮窗怎么开启 边看视频边操作其他应用教程
Android自定义listview布局实现上拉加载下拉刷新功能
如何生成腾讯云建站专用兑换码?
长沙企业网站制作哪家好,长沙水业集团官方网站?
Python3.6正式版新特性预览
Laravel Octane如何提升性能_使用Laravel Octane加速你的应用
浅谈Javascript中的Label语句
,网页ppt怎么弄成自己的ppt?
如何快速搭建高效香港服务器网站?
Laravel怎么返回JSON格式数据_Laravel API资源Response响应格式化【技巧】
个人网站制作流程图片大全,个人网站如何注销?
怎么用AI帮你设计一套个性化的手机App图标?
移动端脚本框架Hammer.js
潮流网站制作头像软件下载,适合母子的网名有哪些?
敲碗10年!Mac系列传将迎来「触控与联网」双革新
网站建设要注意的标准 促进网站用户好感度!
Windows10如何更改计算机工作组_Win10系统属性修改Workgroup
网站制作壁纸教程视频,电脑壁纸网站?
1688铺货到淘宝怎么操作 1688一键铺货到自己店铺详细步骤
Swift中swift中的switch 语句
Python结构化数据采集_字段抽取解析【教程】
Laravel请求验证怎么写_Laravel Validator自定义表单验证规则教程
网站制作软件有哪些,制图软件有哪些?
如何在阿里云ECS服务器部署织梦CMS网站?
Python文件异常处理策略_健壮性说明【指导】
如何在宝塔面板创建新站点?
iOS正则表达式验证手机号、邮箱、身份证号等
如何用5美元大硬盘VPS安全高效搭建个人网站?
Laravel如何配置和使用队列处理异步任务_Laravel队列驱动与任务分发实例
Laravel如何实现数据导出到CSV文件_Laravel原生流式输出大数据量CSV【方案】
Laravel Docker环境搭建教程_Laravel Sail使用指南
如何在VPS电脑上快速搭建网站?
Laravel如何使用Passport实现OAuth2?(完整配置步骤)

