Android动画之小球拟合动画实例
发布时间 - 2026-01-11 02:09:32 点击率:次Android动画之小球拟合动画实例

实现效果:
动画组成:
1.通过三阶贝塞尔曲线来拟合圆,拟合系数的由来,以及怎么选控制点.
2.利用画布canvas.translate,以及scale,rotate的方法,来渐变绘制的过程.
3.熟悉拟合过程.
4.不熟悉的话,先绘制辅助点的移动路线,对理解两个圆的分裂的拟合过程有好处.
package com.example.administrator.animationworkdemo.views;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;
import java.util.concurrent.CyclicBarrier;
/**
* 这个例子中,大家可以发现作者的拟合做的并不是很好,连接的地方比较生硬,大家可以思考下如何改善
* 贝塞尔曲线绘制比较复杂,大家在学习过程中,可以仿照示例中的,将辅助点和线绘制出来,这样会看的更清楚一点
*/
public class BallShapeChangeView extends View {
// 使用贝塞尔曲线来拟合圆的magic number
//C 是三阶贝塞尔曲线拟合 圆的 误差最小 获得控制点的参数.
private static final float C = 0.551915024494f;
private Paint mPaint;
private int mRadiusBig = 120, mRadiusSmall = (int) (mRadiusBig / 2f), mWidth, mHeight, mMimWidth = (int) (mRadiusSmall * 2 * 3)/*fill view mim width*/;
private float mFraction = 0, mFractionDegree = 0, /*degree*/
mLength, mDistanceBezier;
private Path mPathCircle, mPathBezier;
private ValueAnimator mValueAnimator;
private float[] mPointData = new float[8];// 4个数据点 顺时针排序,从左边开始
private float[] mPointCtrl = new float[16];// 8个控制点
private float[] mPos = new float[2];
private PathMeasure mPathMeasure;
private Path mPathBezier2;
public BallShapeChangeView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(0xFF7C191E);
mPaint.setAntiAlias(true);
mPathCircle = new Path();
mPathBezier = new Path();
mPathBezier2 = new Path();
mPathMeasure = new PathMeasure();
mValueAnimator = ValueAnimator.ofFloat(0, 1, 0);
mValueAnimator.setDuration(3000);
mValueAnimator.setRepeatCount(Integer.MAX_VALUE);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mFraction = (float) animation.getAnimatedValue();
mFractionDegree = animation.getAnimatedFraction();
invalidate();
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 为了能够更好的控制绘制的大小和位置,当然,初学者写死也是可以的
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode != MeasureSpec.AT_MOST && heightMode != MeasureSpec.AT_MOST) {
if (mWidth < mMimWidth)
mWidth = mMimWidth;
if (mHeight < mMimWidth)
mHeight = mMimWidth;
} else if (widthMeasureSpec != MeasureSpec.AT_MOST) {
if (mWidth < mMimWidth)
mWidth = mMimWidth;
} else if (heightMeasureSpec != MeasureSpec.AT_MOST) {
if (mHeight < mMimWidth)
mHeight = mMimWidth;
}
setMeasuredDimension(mWidth, mHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 通过mFraction来控制绘图的过程,这是常用的一种方式
canvas.translate(mWidth / 2, mHeight / 2);
canvas.scale(1, -1);
canvas.rotate(-360 * mFractionDegree);
setDoubleCirClePath();
canvas.drawPath(mPathCircle, mPaint);
if (mFraction < (1 / 3f)) {// 缩小大圆
setCirclePath();
canvas.drawPath(mPathCircle, mPaint);
} else if (mFraction < 3 / 4f) {// 画贝塞尔曲线
setBezierPath2();
canvas.drawPath(mPathBezier, mPaint);
canvas.drawPath(mPathBezier2, mPaint);
} else {// 画分离
//setLastBezierPath();
//canvas.drawPath(mPathBezier, mPaint);
}
}
private void setDoubleCirClePath() {
mPathCircle.reset();
if (mFraction < (1 / 3f)) {
mPathCircle.addCircle(-mRadiusSmall / 2f * mFraction * 3, 0, mRadiusSmall, Path.Direction.CW);
mPathCircle.addCircle(mRadiusSmall / 2f * mFraction * 3, 0, mRadiusSmall, Path.Direction.CW);
} else {
float distance = (mFraction - 1 / 3f) / (2 / 3f) * (mRadiusSmall * 2 + mRadiusSmall / 2f);
mPathCircle.addCircle(-mRadiusSmall / 2f - distance, 0, mRadiusSmall, Path.Direction.CW);
mPathCircle.addCircle(mRadiusSmall / 2f + distance, 0, mRadiusSmall, Path.Direction.CW);
}
}
// mFraction 0 ~ 1/3
private void setCirclePath() {
mPointData[0] = -mRadiusBig + mRadiusSmall / 2f * mFraction * 3f;
mPointData[1] = 0;
mPointData[2] = 0;
mPointData[3] = mRadiusBig - mRadiusBig / 2f * mFraction * 3f;//0到1 的三分之一 用来给大圆做效果;
mPointData[4] = mRadiusBig - mRadiusSmall / 2f * mFraction * 3f;
mPointData[5] = 0;
mPointData[6] = mPointData[2];
mPointData[7] = -mPointData[3];
mPointCtrl[0] = mPointData[0];// x轴一样
mPointCtrl[1] = mRadiusBig * C;// y轴向下的
mPointCtrl[2] = mPointData[2] - mRadiusBig * C;
mPointCtrl[3] = mPointData[3];// y轴一样
mPointCtrl[4] = mPointData[2] + mRadiusBig * C;
mPointCtrl[5] = mPointData[3];
mPointCtrl[6] = mPointData[4];
mPointCtrl[7] = mPointCtrl[1];
mPointCtrl[8] = mPointData[4];
mPointCtrl[9] = -mPointCtrl[1];
mPointCtrl[10] = mPointCtrl[4];
mPointCtrl[11] = mPointData[7];
mPointCtrl[12] = mPointCtrl[2];
mPointCtrl[13] = mPointData[7];
mPointCtrl[14] = mPointData[0];
mPointCtrl[15] = -mPointCtrl[1];
mPathCircle.reset();
mPathCircle.moveTo(mPointData[0], mPointData[1]);
mPathCircle.cubicTo(mPointCtrl[0], mPointCtrl[1], mPointCtrl[2], mPointCtrl[3], mPointData[2], mPointData[3]);
mPathCircle.cubicTo(mPointCtrl[4], mPointCtrl[5], mPointCtrl[6], mPointCtrl[7], mPointData[4], mPointData[5]);
mPathCircle.cubicTo(mPointCtrl[8], mPointCtrl[9], mPointCtrl[10], mPointCtrl[11], mPointData[6], mPointData[7]);
mPathCircle.cubicTo(mPointCtrl[12], mPointCtrl[13], mPointCtrl[14], mPointCtrl[15], mPointData[0], mPointData[1]);
}
// mFraction 1/3 ~ 3/4
private void setBezierPath2() {
mPointData[0] = -mRadiusSmall / 2 - (mFraction - 1 / 3f) * mRadiusBig * 2f;
if (mFraction < 2 / 3f) {
mPointData[1] = -mRadiusSmall;
} else {
mPointData[1] = -mRadiusSmall + (mFraction - 2 / 3f) * 3 * mRadiusSmall;
}
if (mFraction < 3 / 4f) {
mPointData[2] = 0;
} else {
//当分裂超过一定程度让结束点的位置变远
mPointData[2] = (mFraction - 3 / 4f) * 16 * mPointData[0];
}
//当动画执行进度大于2/3时,此时该点接近于0
mPointData[3] = -mRadiusBig + mFraction * mRadiusBig * 1.5f < -0.01f * mRadiusBig ? -mRadiusBig + mFraction * mRadiusBig * 1.5f : 0.01f * -mRadiusBig;
mPointData[4] = mPointData[2];
mPointData[5] = -mPointData[3];
mPointData[6] = mPointData[0];
mPointData[7] = -mPointData[1];
mPointCtrl[0] = mPointData[0] + mRadiusSmall;
mPointCtrl[1] = mPointData[3];
mPointCtrl[2] = mPointData[0] + mRadiusSmall;
mPointCtrl[3] = -mPointData[3];
mPathBezier.reset();
mPathBezier.moveTo(mPointData[0], mPointData[1]);
mPathBezier.quadTo(mPointCtrl[0], mPointCtrl[1], mPointData[2], mPointData[3]);
mPathBezier.lineTo(mPointData[4], mPointData[5]);
mPathBezier.quadTo(mPointCtrl[2], mPointCtrl[3], mPointData[6], mPointData[7]);
mPathBezier2.reset();
mPathBezier2.moveTo(-mPointData[0], mPointData[1]);
mPathBezier2.quadTo(-mPointCtrl[0], mPointCtrl[1], -mPointData[2], mPointData[3]);
mPathBezier2.lineTo(-mPointData[4], mPointData[5]);
mPathBezier2.quadTo(-mPointCtrl[2], mPointCtrl[3], -mPointData[6], mPointData[7]);
}
// mFraction 1/3 ~ 3/4
private void setBezierPath() {
mPathBezier.reset();
float distance = (2 * mRadiusSmall + mRadiusSmall / 2f) * mFraction;
//float topY = mRadiusSmall * (1 - 0.6f * mFraction);
float topY = mRadiusSmall - mRadiusSmall * (mFraction - 1 / 3f);
float distanceBezier = topY - distance * C * (0.5f + 0.5f * mFraction);
if (mDistanceBezier != 0 && distanceBezier < (mDistanceBezier)) {
distanceBezier = mDistanceBezier;
}
mPathBezier.moveTo(-distance, topY);
mPathBezier.cubicTo(-distance, distanceBezier, distance, distanceBezier, distance, topY);
if (mDistanceBezier == 0) {
mPathMeasure.setPath(mPathBezier, false);
mLength = mPathMeasure.getLength();
mPathMeasure.getPosTan(mLength / 2, mPos, null);
if (mPos[1] <= 8) {
mDistanceBezier = distanceBezier;
mPathBezier.reset();
mPathBezier.moveTo(-distance, topY);
mPathBezier.cubicTo(-distance, mDistanceBezier, distance, mDistanceBezier, distance, topY);
mPathBezier.lineTo(distance, -topY);
mPathBezier.cubicTo(distance, -mDistanceBezier, -distance, -mDistanceBezier, -distance, -topY);
mPathBezier.close();
return;
}
}
mPathBezier.lineTo(distance, -topY);
mPathBezier.cubicTo(distance, -distanceBezier, -distance, -distanceBezier, -distance, -topY);
mPathBezier.close();
}
// mFraction 3/4 ~ 1
private void setLastBezierPath() {
float x = -mRadiusSmall / 2f - (mFraction - 1 / 3f) / (2 / 3f) * (mRadiusSmall * 2 + mRadiusSmall / 2f);
mPathBezier.reset();
mPathBezier.moveTo(x, mRadiusSmall);
mPathBezier.quadTo(x, 0, x + mRadiusSmall + mRadiusSmall * (4 - mFraction * 4), 0);
mPathBezier.quadTo(x, 0, x, -mRadiusSmall);
mPathBezier.lineTo(x, mRadiusSmall);
mPathBezier.moveTo(-x, mRadiusSmall);
mPathBezier.quadTo(-x, 0, -x - mRadiusSmall - mRadiusSmall * (4 - mFraction * 4), 0);
mPathBezier.quadTo(-x, 0, -x, -mRadiusSmall);
mPathBezier.lineTo(-x, mRadiusSmall);
mPathBezier.close();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (!mValueAnimator.isRunning())
mValueAnimator.start();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mValueAnimator.isRunning())
mValueAnimator.cancel();
}
}
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
# Android
# 动画
# 实现小球拟合动画
# Android自定义控件实现随手指移动的小球
# Android实现移动小球和CircularReveal页面切换动画实例代码
# 塞尔
# 这是
# 很好
# 希望能
# 谢谢大家
# 不熟悉
# 过程中
# 顺时针
# 轴向
# float
# static
# final
# mRadiusBig
# mPaint
# int
# BallShapeChangeView
# extends
# public
# magic
# mMimWidth
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Laravel怎么配置S3云存储驱动_Laravel集成阿里云OSS或AWS S3存储桶【教程】
laravel怎么通过契约(Contracts)编程_laravel契约(Contracts)编程方法
Laravel如何使用Seeder填充数据_Laravel模型工厂Factory批量生成测试数据【方法】
Laravel如何实现多语言支持_Laravel本地化与国际化(i18n)配置教程
QQ浏览器网页版登录入口 个人中心在线进入
如何快速搭建高效可靠的建站解决方案?
谷歌浏览器下载文件时中断怎么办 Google Chrome下载管理修复
Laravel如何使用缓存系统提升性能_Laravel缓存驱动和应用优化方案
*服务器网站为何频现安全漏洞?
米侠浏览器网页图片不显示怎么办 米侠图片加载修复
Laravel distinct去重查询_Laravel Eloquent去重方法
国美网站制作流程,国美电器蒸汽鍋怎么用官方网站?
重庆市网站制作公司,重庆招聘网站哪个好?
Laravel如何实现模型的全局作用域?(Global Scope示例)
php做exe能调用系统命令吗_执行cmd指令实现方式【详解】
焦点电影公司作品,电影焦点结局是什么?
Bootstrap整体框架之CSS12栅格系统
香港服务器租用每月最低只需15元?
文字头像制作网站推荐软件,醒图能自动配文字吗?
HTML透明颜色代码在Angular里怎么设置_Angular透明颜色使用指南【详解】
Windows Hello人脸识别突然无法使用
如何获取免费开源的自助建站系统源码?
Laravel用户密码怎么加密_Laravel Hash门面使用教程
js代码实现下拉菜单【推荐】
如何快速搭建虚拟主机网站?新手必看指南
Laravel如何实现事件和监听器?(Event & Listener实战)
Win11摄像头无法使用怎么办_Win11相机隐私权限开启教程【详解】
电商网站制作多少钱一个,电子商务公司的网站制作费用计入什么科目?
详解一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)
谷歌Google入口永久地址_Google搜索引擎官网首页永久入口
高防服务器租用指南:配置选择与快速部署攻略
魔方云NAT建站如何实现端口转发?
如何在服务器上配置二级域名建站?
ChatGPT常用指令模板大全 新手快速上手的万能Prompt合集
微信小程序 require机制详解及实例代码
Laravel的辅助函数有哪些_Laravel常用Helpers函数提高开发效率
Android中Textview和图片同行显示(文字超出用省略号,图片自动靠右边)
Laravel如何实现登录错误次数限制_Laravel自带LoginThrottles限流配置【方法】
专业商城网站制作公司有哪些,pi商城官网是哪个?
Laravel怎么返回JSON格式数据_Laravel API资源Response响应格式化【技巧】
头像制作网站在线观看,除了站酷,还有哪些比较好的设计网站?
通义万相免费版怎么用_通义万相免费版使用方法详细指南【教程】
如何用wdcp快速搭建高效网站?
iOS发送验证码倒计时应用
移动端手机网站制作软件,掌上时代,移动端网站的谷歌SEO该如何做?
Laravel如何实现全文搜索_Laravel Scout集成Algolia或Meilisearch教程
手机怎么制作网站教程步骤,手机怎么做自己的网页链接?
如何快速搭建自助建站会员专属系统?
深圳网站制作的公司有哪些,dido官方网站?
宙斯浏览器视频悬浮窗怎么开启 边看视频边操作其他应用教程

