Android仿360市场下载按钮的实现方法

发布时间 - 2026-01-11 01:09:54    点击率:

首先来看看效果图:

无论多复杂的动画我们都是可以分割成小单元的,然后分步来实现。这个动画大概分为收缩,准备,加载,完成几个部分。为此定义一个枚举类来描述view的状态。

public enum Status { NORMAL, START, PRE, EXPAND, LOAD, END }

收缩动画

使用动画不断改变圆角矩形的宽度,触发重绘。代码如下:

private void initAnim() {
 Animation animation1 = new Animation() {
 @Override
 protected void applyTransformation(float interpolatedTime, Transformation t) {
 mCurrLength = mWidth * (1 - interpolatedTime);
 if (mCurrLength < mHeight) {
  mCurrLength = mHeight;
  clearAnimation();
  mAngleAnim.start();
 }
 invalidate();
 }
 };

 animation1.setDuration(mShrinkDuration);
 animation1.setInterpolator(new LinearInterpolator());
 animation1.setAnimationListener(new Animation.AnimationListener() {
 @Override
 public void onAnimationStart(Animation animation) {
 mStatus = Status.START;
 }

 @Override
 public void onAnimationEnd(Animation animation) {

 }

 @Override
 public void onAnimationRepeat(Animation animation) {

 }
 });
 mShrinkAnim = animation1;
 ...
}

onDraw中绘制:

if (mStatus == Status.START || mStatus == Status.NORMAL) {
 float left = (mWidth - mCurrLength) / 2f;
 float right = (mWidth + mCurrLength) / 2f;
 float r = mHeight / 2f;
 canvas.drawRoundRect(new RectF(left, 0, right, mHeight), r, r, mBgPaint);
 if (mStatus == Status.NORMAL) {
 Paint.FontMetrics fm = mTextPaint.getFontMetrics();
 float y = mHeight / 2 + (fm.descent - fm.ascent) / 2 - fm.descent;
 canvas.drawText("下载", mWidth / 2, y, mTextPaint);
 }
 }

准备动画

此时旋转动画,是通过canvas绘制背景圆和三个小圆,然后不断旋转画布来实现的,具体求圆心坐标和角度动画我们直接看代码:

ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
 @Override
 public void onAnimationUpdate(ValueAnimator animation) {
 mAngle += mPreAnimSpeed;
 invalidate();
 }
 });
 animator.addListener(new Animator.AnimatorListener() {
 @Override
 public void onAnimationStart(Animator animation) {
 mStatus = Status.PRE;
 }

 @Override
 public void onAnimationEnd(Animator animation) {
 mAngleAnim.cancel();
 startAnimation(mTranslateAnim);
 }

 @Override
 public void onAnimationCancel(Animator animation) {

 }

 @Override
 public void onAnimationRepeat(Animator animation) {

 }
 });

 animator.setDuration(mPreAnimDuration);
 animator.setInterpolator(new LinearInterpolator());
 mAngleAnim = animator;

onDraw中绘制代码:

if (mStatus == Status.PRE) {
 canvas.drawCircle(mWidth / 2f, mHeight / 2f, mHeight / 2f, mBgPaint);
 canvas.save();
 mTextPaint.setStyle(Paint.Style.FILL);
 canvas.rotate(mAngle, mWidth / 2, mHeight / 2);
 //大圆的圆心 半径
 float cX = mWidth / 2f;
 float cY = mHeight / 2f;
 float radius = mHeight / 2 / 3f;
 canvas.drawCircle(cX, cY, radius, mTextPaint);
 //上方小圆的参数
 float rr = radius / 2f;
 float cYY = mHeight / 2 - (radius + rr / 3);
 canvas.drawCircle(cX, cYY, rr, mTextPaint);
 //左下小圆参数
 float cXX = (float) (cX - Math.sqrt(2) / 2f * (radius + rr / 3f));
 cYY = (float) (mHeight / 2 + Math.sqrt(2) / 2f * (radius + rr / 3f));
 canvas.drawCircle(cXX, cYY, rr, mTextPaint);
 //右下小圆参数
 cXX = (float) (cX + Math.sqrt(2) / 2f * (radius + rr / 3f));
 canvas.drawCircle(cXX, cYY, rr, mTextPaint);
 canvas.restore();
 }

展开动画

展开动画也是不断改变view的宽度并重绘圆角矩形,同时需要对准备动画的状态进行向右位移。

Animation animator1 = new Animation() {
 @Override
 protected void applyTransformation(float interpolatedTime, Transformation t) {
 mCurrLength = mHeight + (mWidth - mHeight) * interpolatedTime;
 mTranslationX = (mWidth - mHeight) / 2 * interpolatedTime;
 invalidate();
 }
 };
 animator1.setAnimationListener(new Animation.AnimationListener() {
 @Override
 public void onAnimationStart(Animation animation) {
 mStatus = Status.EXPAND;
 }

 @Override
 public void onAnimationEnd(Animation animation) {
 clearAnimation();
 mLoadAngleAnim.start();
 mMovePointAnim.start();
 }

 @Override
 public void onAnimationRepeat(Animation animation) {

 }
 });
 animator1.setDuration(mExpandAnimDuration);
 animator1.setInterpolator(new LinearInterpolator());
 mTranslateAnim = animator1;

onDraw中绘制代码

if (mStatus == Status.EXPAND) {
 float left = (mWidth - mCurrLength) / 2f;
 float right = (mWidth + mCurrLength) / 2f;
 float r = mHeight / 2f;
 canvas.drawRoundRect(new RectF(left, 0, right, mHeight), r, r, mBgPaint);
 canvas.save();
 mTextPaint.setStyle(Paint.Style.FILL);
 canvas.translate(mTranslationX, 0);
 //大圆的圆心 半径
 float cX = mWidth / 2f;
 float cY = mHeight / 2f;
 float radius = mHeight / 2 / 3f;
 canvas.drawCircle(cX, cY, radius, mTextPaint);
 //上方小圆的参数
 float rr = radius / 2f;
 float cYY = mHeight / 2 - (radius + rr / 3);
 canvas.drawCircle(cX, cYY, rr, mTextPaint);
 //左下小圆参数
 float cXX = (float) (cX - Math.sqrt(2) / 2f * (radius + rr / 3f));
 cYY = (float) (mHeight / 2 + Math.sqrt(2) / 2f * (radius + rr / 3f));
 canvas.drawCircle(cXX, cYY, rr, mTextPaint);
 //右下小圆参数
 cXX = (float) (cX + Math.sqrt(2) / 2f * (radius + rr / 3f));
 canvas.drawCircle(cXX, cYY, rr, mTextPaint);
 canvas.restore();
 }

加载动画

加载动画分三部分,右侧的旋转动画,正弦轨迹运动的小球动画,进度更新的动画。正弦动画要求出正弦函数的周期,y轴偏移量,x轴偏移量。

ValueAnimator animator2 = ValueAnimator.ofFloat(0, 1);
 animator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
 @Override
 public void onAnimationUpdate(ValueAnimator animation) {
 mLoadAngle += mLoadRotateAnimSpeed;
 invalidate();
 }
 });
 animator2.addListener(new Animator.AnimatorListener() {
 @Override
 public void onAnimationStart(Animator animation) {
 mStatus = Status.LOAD;
 }

 @Override
 public void onAnimationEnd(Animator animation) {
 mLoadAngleAnim.cancel();
 }

 @Override
 public void onAnimationCancel(Animator animation) {

 }

 @Override
 public void onAnimationRepeat(Animator animation) {

 }
 });
 animator2.setDuration(Integer.MAX_VALUE);
 animator2.setInterpolator(new LinearInterpolator());
 mLoadAngleAnim = animator2;

onDraw中绘制代码:

if (mStatus == Status.LOAD || mStatus == Status.END) {
 float left = (mWidth - mCurrLength) / 2f;
 float right = (mWidth + mCurrLength) / 2f;
 float r = mHeight / 2f;
 mBgPaint.setColor(mProgressColor);
 canvas.drawRoundRect(new RectF(left, 0, right, mHeight), r, r, mBgPaint);
 if (mProgress != 100) {
 for (int i = 0; i < mFourMovePoints.length; i++) {
  if (mFourMovePoints[i].isDraw)
  canvas.drawCircle(mFourMovePoints[i].moveX, mFourMovePoints[i].moveY, mFourMovePoints[i].radius, mTextPaint);
 }
 }
 float progressRight = mProgress * mWidth / 100f;
 mBgPaint.setColor(mBgColor);
 canvas.save();
 canvas.clipRect(0, 0, progressRight, mHeight);
 canvas.drawRoundRect(new RectF(left, 0, right, mHeight), r, r, mBgPaint);
 canvas.restore();

 if (mProgress != 100) {
 canvas.drawCircle(mWidth - mHeight / 2, mHeight / 2, mHeight / 2, mBgPaint);
 canvas.save();
 mTextPaint.setStyle(Paint.Style.FILL);
 canvas.rotate(mLoadAngle, mWidth - mHeight / 2, mHeight / 2);
 canvas.drawCircle(mWidth - mHeight + 30, getCenterY(mWidth - mHeight + 30, 5), 5, mTextPaint);
 canvas.drawCircle(mWidth - mHeight + 45, getCenterY(mWidth - mHeight + 45, 8), 8, mTextPaint);
 canvas.drawCircle(mWidth - mHeight + 68, getCenterY(mWidth - mHeight + 68, 11), 11, mTextPaint);
 canvas.drawCircle(mWidth - mHeight + 98, getCenterY(mWidth - mHeight + 98, 14), 14, mTextPaint);
 canvas.restore();
 }

 Paint.FontMetrics fm = mTextPaint.getFontMetrics();
 float y = mHeight / 2 + (fm.descent - fm.ascent) / 2 - fm.descent;
 canvas.drawText(mProgress + "%", mWidth / 2, y, mTextPaint);
 }

项目主页:http://www.open-open.com/lib/view/home/1494985743108

本地下载:http://xiazai./201705/yuanma/Metal626-360DownLoadView().rar

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。


# android  # 按钮样式  # 开关按钮  # 圆角按钮  # Android按钮按下的时候改变颜色实现方法  # Android编程实现设置按钮背景透明与半透明及图片背景透明的方法  # 如何在Android中实现渐显按钮的左右滑动效果  # Android实现圆角Button按钮  # Android单选按钮对话框用法实例分析  # Android动画 实现开关按钮动画(属性动画之平移动画)实例代码  # Android自定义实现开关按钮代码  # Android利用悬浮按钮实现翻页效果  # Android学习系列一用按钮实现显示时间  # 安卓(android)仿电商app商品详情页按钮浮动效果  # 小圆  # 加载  # 来实现  # 都是  # 圆角  # 几个  # 偏移量  # 本地下载  # 来看看  # 要对  # 这篇文章  # 谢谢大家  # 时需  # 有疑问  # AnimationListener  # setAnimationListener  # LinearInterpolator  # onAnimationStart  # onAnimationEnd  # mStatus 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: HTML5空格和nbsp有啥关系_nbsp的作用及使用场景【说明】  Laravel如何连接多个数据库_Laravel多数据库连接配置与切换教程  Laravel Eloquent访问器与修改器是什么_Laravel Accessors & Mutators数据处理技巧  如何在IIS中新建站点并解决端口绑定冲突?  瓜子二手车官方网站在线入口 瓜子二手车网页版官网通道入口  在线制作视频的网站有哪些,电脑如何制作视频短片?  javascript事件捕获机制【深入分析IE和DOM中的事件模型】  网站制作大概要多少钱一个,做一个平台网站大概多少钱?  高端云建站费用究竟需要多少预算?  EditPlus中的正则表达式实战(6)  Android okhttputils现在进度显示实例代码  千库网官网入口推荐 千库网设计创意平台入口  Laravel如何发送系统通知?(Notification渠道示例)  Laravel怎么导出Excel文件_Laravel Excel插件使用教程  图片制作网站免费软件,有没有免费的网站或软件可以将图片批量转为A4大小的pdf?  如何安全更换建站之星模板并保留数据?  武汉网站设计制作公司,武汉有哪些比较大的同城网站或论坛,就是里面都是武汉人的?  PHP正则匹配日期和时间(时间戳转换)的实例代码  如何在浏览器中启用Flash_2025年继续使用Flash Player的方法【过时】  Laravel如何使用Guzzle调用外部接口_Laravel发起HTTP请求与JSON数据解析【详解】  Win11怎么恢复误删照片_Win11数据恢复工具使用【推荐】  如何挑选优质建站一级代理提升网站排名?  利用JavaScript实现拖拽改变元素大小  iOS UIView常见属性方法小结  Laravel怎么配置自定义表前缀_Laravel数据库迁移与Eloquent表名映射【步骤】  JavaScript常见的五种数组去重的方式  如何在Windows环境下新建FTP站点并设置权限?  安克发布新款氮化镓充电宝:体积缩小 30%,支持 200W 输出  制作公司内部网站有哪些,内网如何建网站?  如何在阿里云服务器自主搭建网站?  Laravel如何配置和使用队列处理异步任务_Laravel队列驱动与任务分发实例  北京的网站制作公司有哪些,哪个视频网站最好?  javascript读取文本节点方法小结  Laravel如何实现数据导出到CSV文件_Laravel原生流式输出大数据量CSV【方案】  今日头条微视频如何找选题 今日头条微视频找选题技巧【指南】  C++时间戳转换成日期时间的步骤和示例代码  如何挑选最适合建站的高性能VPS主机?  香港服务器建站指南:免备案优势与SEO优化技巧全解析  node.js报错:Cannot find module &#39;ejs&#39;的解决办法  Windows10电脑怎么设置虚拟光驱_Win10右键装载ISO镜像文件  laravel服务容器和依赖注入怎么理解_laravel服务容器与依赖注入解析  青岛网站建设如何选择本地服务器?  Linux虚拟化技术教程_KVMQEMU虚拟机安装与调优  Laravel如何生成API文档?(Swagger/OpenAPI教程)  如何将凡科建站内容保存为本地文件?  成都网站制作公司哪家好,四川省职工服务网是做什么用?  详解Huffman编码算法之Java实现  UC浏览器如何切换小说阅读源_UC浏览器阅读源切换【方法】  Laravel怎么使用artisan命令缓存配置和视图  Laravel怎么实现验证码功能_Laravel集成验证码库防止机器人注册