Android自定义View app更新动画详解
作者:MrAllRight_Liu
为了做一个有温度的IT男,我决定在以后的文章中给大家分享一些看到的,听到的一些东西,如果你不喜欢请留言让我知道,如果你喜欢请点个赞。你也可留言写下自己想分享的东西,温暖你我他。这次分享的是一首歌,毛不易的《消愁》,分享这首歌主要是这首歌的歌词,借用薛之谦的评价:“我是研究歌词的人,我研究了十几年,但是你写到我想给你跪!”,下面贴部分歌词供大家欣赏
一杯敬朝阳,一杯敬月光
唤醒我的向往,温柔了寒窗
于是可以不回头的逆风飞翔
不怕心头有雨,眼底有霜
一杯敬故乡,一杯敬远方
守着我的善良,催着我成长
所以南北的路从此不再漫长
灵魂不再无处安放
好了,言归正传,本篇文章是实现项目中的更新功能,效果如下
观察动画,可以分为几个阶段:
- 初始化阶段 显示立即升级按钮,在点击立即升级按钮后,执行放大再缩小至消失动画
- 准备阶段 进度条背景从中间向两端扩散,然后进度提示图片显示,进度提示文字显示0%
- 更新阶段 进度更新时,进度提示图片和文字旋转向前移动,如果一定时间内进度没更新的话,进度提示图片和文字要置回水平状态
- 成功阶段,进度提示图片缩放消失,进度条背景从两端向中间缩小至消失
- 安装阶段 马上安装图片放大显示
1.首选看初始化阶段,我们要判断用户是否点击了立即升级按钮,我们通过监听onTouchEvent事件判断手指是否落在可点击区域
//如果点击生效,执行动画
if (rectClickRange.contains(event.getX(), event.getY()))
startBtnDisappear();//立即更新按钮消失动画
其中rectClickRange是我们定义的可点击区域,也就是立即更新图片的显示区域
rectClickRange = new RectF(getWidth() / 2 - startDrawable.getWidth() / 2, getHeight() / 2 - startDrawable.getHeight() / 2, getWidth() / 2 + startDrawable.getWidth() / 2, getHeight() / 2 + startDrawable.getHeight() / 2);//startDrawable是立即更新图片
点击生效后我们执行立即更新按钮消失动画,代码如下
/** * 点击立即升级的时候,立即升级按钮执行消失动画 * 动画效果是按钮放大一点之后缩小至消失 * 根据效果选择插值器AnticipateInterpolator(开始的时候向后然后向前甩) * 将bitmapscale设置到立即升级图片上 * 动画结束后状态更新为准备状态 */ private void startBtnDisappear() { initStateData(); ValueAnimator va = ValueAnimator.ofInt(0, 1); va.setInterpolator(new AnticipateInterpolator()); va.setDuration(800); va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { bitmapScale = 1 - animation.getAnimatedFraction(); invalidate(); } }); va.addListener(new MyAnimationListener() { @Override public void onAnimationEnd(Animator animation) { cancleValueAnimator(va_List); state = PREPARE; toPrepare(); } }); va.start(); va_List.add(va); }
然后在onDraw里面绘制立即升级按钮动画,代码如下: matrix.reset(); matrix.setScale(bitmapScale, bitmapScale);//缩放图片 matrix.preTranslate(0, 0); matrix.postTranslate(width / 2 - startDrawable.getWidth() / 2 * bitmapScale, height / 2 - startDrawable.getHeight() / 2 * bitmapScale);//不断的改变缩放的中心点 canvas.drawBitmap(startDrawable, matrix, bitmapPaint);
2.接着我们看一下准备阶段,我们通过画path,并不断的改变path的起点和终点达到所需要的动画效果,代码如下:
/** * PREPARE状态 * 进度条从中间向两端扩散 * 具体做法是不断改变path的起点和终点坐标 * 动画结束的时候开始下载更新 */ private void toPrepare() { final ValueAnimator va = ValueAnimator.ofFloat(0, width / 2 - pbPaint.getStrokeWidth() * 3 - pbProgerssDrawable.getWidth()); va.setInterpolator(new LinearInterpolator()); va.setDuration(200); va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float value = (Float) animation.getAnimatedValue(); startX = (int) (width / 2 - value); endX = (int) (width / 2 + value); if (animation.getAnimatedFraction() == 1) prepareDone = true; invalidate(); } }); va.addListener(new MyAnimationListener() { @Override public void onAnimationEnd(Animator animation) { if (startDownLoadListener != null && !isSetListener) { isSetListener = true; postDelayed(new Runnable() { @Override public void run() { state = UPDATEING; startDownLoadListener.downLoad();//动画结束,通知界面开始下载apk text = progress * 100 / max + "%"; } }, 200); } } }); va.start(); va_List.add(va); }
具体的绘制代码如下 pbPath.reset(); pbPath.moveTo(startX, height / 2); pbPath.lineTo(endX, height / 2); canvas.drawPath(pbPath, pbPaint);//绘制path //进度条完全显示后,画进度提示图片和文字 if (prepareDone) { canvas.drawBitmap(pbProgerssDrawable, startX - pbProgerssDrawable.getWidth() / 2, height / 2 - pbProgerssDrawable.getHeight() - pbPaint.getStrokeWidth(), bitmapPaint); String text = "0%"; textPaint.getTextBounds(text.toCharArray(), 0, text.toCharArray().length, textRect); canvas.drawText(text, startX - textRect.right / 2, height / 2 - pbProgerssDrawable.getHeight() / 2 - pbPaint.getStrokeWidth() + textRect.bottom, textPaint); }
3.这个时候界面就开始下载apk(代码不贴了),然后通知view来更新进度,更新的动画是图片和文字旋转向前移动(我们的做法是将画布旋转),如果一定时间进度没有变化,更新的图片和文字置回正常状态(我们通过启动线程不断的将画布旋转回来并更新view,如果这个阶段进度有更新的话,我们把线程remove掉),绘制代码如下
pbPath.reset(); pbPath.moveTo(startX, height / 2); pbPath.lineTo(endX, height / 2); pm.setPath(pbPath, false); //不断截取进度到pbPathSec并绘制 if (progressOffsetX >= pm.getLength()) { pm.getSegment(0, pm.getLength(), pbPathSec, true); pm.getPosTan(pm.getLength(), POS, null); } else { pm.getSegment(0, progressOffsetX, pbPathSec, true); pm.getPosTan(progressOffsetX, POS, null); } matrix.reset(); matrix.setTranslate(POS[0] - pbProgerssDrawable.getWidth() / 2, POS[1] - pbProgerssDrawable.getHeight() - pbPaint.getStrokeWidth()); canvas.drawPath(pbPath, pbPaint); canvas.drawPath(pbPathSec, pbUpdatePaint); canvas.save(); //如果进度没有到达100%,并且进度在更新的时候,画布旋转,然后画进度提示图片和文字 if (progressOffsetX < pm.getLength() && !isRotate) { canvas.rotate(-15, POS[0], POS[1] - pbPaint.getStrokeWidth() / 2); } canvas.drawBitmap(pbProgerssDrawable, matrix, bitmapPaint); if (progressOffsetX >= pm.getLength()) progressOffsetX = pm.getLength(); text = (int) (progressOffsetX * 100 / pm.getLength()) + "%"; textPaint.getTextBounds(text.toCharArray(), 0, text.toCharArray().length, textRect); canvas.drawText(text, progressOffsetX + startX - textRect.right / 2, height / 2 - pbProgerssDrawable.getHeight() / 2 - pbPaint.getStrokeWidth() + textRect.bottom, textPaint); //我们启动一个线程,如果300毫秒进度没有更新,将画布旋转回来画进度提示图片和文字 if (progressOffsetX < pm.getLength()) postDelayed(rotateRunnable, 300); else toSucc(); canvas.restore();
其中rotateRunnable的代码如下 //每隔一段时间刷新界面,如果进度没有更新,将画布旋转回来 private Runnable rotateRunnable = new Runnable() { @Override public void run() { isRotate = true; invalidate(); } };
4.当进度达到100%的时候,我们将进度提示图片缩放至消失,并且进度背景执行两端向中间缩小动画,也是改变path的起始点,代码如下
//下载进度达到100时,进度提示图片进行缩放 private void toSuccBitmapScale() { cancleValueAnimator(va_List); ValueAnimator va = ValueAnimator.ofFloat(1, 0); va.setInterpolator(new AccelerateInterpolator()); va.setDuration(100); va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { bitmapScale = (Float) animation.getAnimatedValue(); state = SUCCESS; invalidate(); } }); va.start(); va.addListener(new MyAnimationListener() { @Override public void onAnimationEnd(Animator animation) { toSuccPathAnim(); } }); va_List.add(va); } //成功后进度条缩放动画 private void toSuccPathAnim() { cancleValueAnimator(va_List); ValueAnimator va = ValueAnimator.ofInt(0, (endX - startX) / 2); va.setInterpolator(new AccelerateInterpolator()); va.setDuration(300); va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { transx = (int) animation.getAnimatedValue(); state = SUCCESS; invalidate(); } }); va.start(); va.addListener(new MyAnimationListener() { @Override public void onAnimationEnd(Animator animation) { toInstall(); } }); va_List.add(va); }
绘制代码如下
pbPath.reset(); pbPath.moveTo(startX + transx, height / 2);//不断的改变起点 pbPath.lineTo(endX - transx, height / 2);//改变终点 pm.setPath(pbPath, false); pm.getSegment(0, (endX - startX), pbPathSec, true); pm.getPosTan(endX - startX, POS, null); matrix.reset(); matrix.preTranslate(POS[0] - pbProgerssDrawable.getWidth() / 2, POS[1] - pbProgerssDrawable.getHeight() - pbPaint.getStrokeWidth()); matrix.postScale(bitmapScale, bitmapScale, POS[0], POS[1] - pbPaint.getStrokeWidth()); canvas.drawPath(pbPath, pbUpdatePaint);//path缩放动画 canvas.drawBitmap(pbProgerssDrawable, matrix, bitmapPaint);//bitmap缩放动画
5.最后就是显示马上安装图片动画了,一个简单的缩放
//显示马上安装图片动画 private void toInstall() { cancleValueAnimator(va_List); ValueAnimator va = ValueAnimator.ofInt(0, 1); va.setInterpolator(new LinearInterpolator()); va.setDuration(400); va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { bitmapScale = animation.getAnimatedFraction(); state = INSTALL; invalidate(); } }); va.start(); va_List.add(va); } //绘制代码如下 matrix.reset(); matrix.setScale(bitmapScale, bitmapScale); matrix.preTranslate(0, 0); matrix.postTranslate(width / 2 - succDrawable.getWidth() / 2 * bitmapScale, height / 2 - succDrawable.getHeight() / 2 * bitmapScale); canvas.drawBitmap(succDrawable, matrix, bitmapPaint);
回过头来看看,其实当我们把动画不断的分解之后,发现其实每个动画并没有那么难,我们这里用到的有path绘制及截取,getPosTan(获取路径上某点的坐标及其切线的坐标),利用Matrix做动画,使用属性动画ValueAnimator。本篇还有好多功能没有实现,比如下载失败动画,失败后恢复至初始化动画,不过任何轮子都不一定能完全适合你,学习到知识之后自己造一个适合自己的才是最重要。
github地址:https://github.com/MrAllRight/BezierView
您可能感兴趣的文章:
- android使用ViewPager组件实现app引导查看页面
- Android用webView包装WebAPP方法
- Android APP之WebView校验SSL证书的方法
- Android中TabLayout+ViewPager 简单实现app底部Tab导航栏
- 详解Android中ListView实现图文并列并且自定义分割线(完善仿微信APP)
- Android App使用RecyclerView实现上拉和下拉刷新的方法
- Android App开发中使用RecyclerView替代ListView的实践
- Android App中使用ViewPager实现滑动分页的要点解析
- Android App中ViewPager所带来的滑动冲突问题解决方法