首先来看看效果图:
无论多复杂的动画我们都是可以分割成小单元的,然后分步来实现。这个动画大概分为收缩,准备,加载,完成几个部分。为此定义一个枚举类来描述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.jb51.net/201705/yuanma/Metal626-360DownLoadView(jb51.net).rar
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对小牛知识库的支持。
本文向大家介绍Python实现股市信息下载的方法,包括了Python实现股市信息下载的方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了Python实现股市信息下载的方法。分享给大家供大家参考。具体如下: 该程序下载来自yahoo财经的股市信息。 希望本文所述对大家的Python程序设计有所帮助。
本文向大家介绍Android开发仿bilibili刷新按钮的实现代码,包括了Android开发仿bilibili刷新按钮的实现代码的使用技巧和注意事项,需要的朋友参考一下 一、简述 最近跟小伙伴一起讨论了一下,决定一起仿一个BiliBili的app(包括android端和iOS端),我们并没有打算把这个项目完全做完,毕竟我们的重点是掌握一些新框架的使用,并在实战过程中发现并弥补自身的
本文向大家介绍android仿360加速球实现内存释放,包括了android仿360加速球实现内存释放的使用技巧和注意事项,需要的朋友参考一下 现在手机上的悬浮窗应用越来越多,对用户来说,最常见的悬浮窗应用就是安全软件的悬浮小控件,拿360卫士来说,当开启悬浮窗时,它是一个小球,小球可以拖动,当点击小球出现大窗体控件,可以进行进一步的操作如:释放手机内存等等。于是借着慕课网的视频,仿着实现了360
本文向大家介绍Android实现断点下载的方法,包括了Android实现断点下载的方法的使用技巧和注意事项,需要的朋友参考一下 最近做的项目中需要实现断点下载,即用户一次下载可以分多次进行,下载过程可以中断,在目前大多数的带离线缓存的软件都是需要实现这一功能。本文阐述了通过sqlite3简单实现了一个具有断点下载功能的demo。言归正传,开始正文。 设计 数据库表存储元数据 DBHelper.ja
本文向大家介绍js实现按钮控制图片360度翻转特效的方法,包括了js实现按钮控制图片360度翻转特效的方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了js实现按钮控制图片360度翻转特效的方法。分享给大家供大家参考。具体实现方法如下: 希望本文所述对大家的javascript程序设计有所帮助。
本文向大家介绍Android实现仿360桌面悬浮清理内存,包括了Android实现仿360桌面悬浮清理内存的使用技巧和注意事项,需要的朋友参考一下 今天闲来无事写了一个清内存的小东西,类似360,在桌面上悬浮,点击后清除后台无用程序,清除后台程序是通过调用ActivityManger.killBackgroundProcesses的方式来进行的,这样的方式有个不好的地方,就是重要性级别设置的高的应