Android自定义View app更新动画详解

为了做一个有温度的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

(0)

相关推荐

  • Android APP之WebView校验SSL证书的方法

    Android系统的碎片化很严重,并且手机日期不正确.手机根证书异常.com.google.android.webview BUG等各种原因,都会导致WebViewClient无法访问HTTPS站点.SSL错误的处理方式十分关键,如果处理不当,可能导致中间人攻击,黑客窃听数据,进而引发安全事故. 严谨地处理onReceivedSslError尤为重要.请参考以下代码,原理是:如果webview报告SSL错误,程序将会对服务器证书进行强校验,如果服务器传入证书的指纹(sha256)与记录值一致,说

  • Android App开发中使用RecyclerView替代ListView的实践

    RecyclerView是Android 5.0的新特性,可以直接代替ListView与GridView,并且能够实现瀑布流的布局,感觉RecyclerView使用的好处就是它不关心布局,只关心资源的回收与复用,正因为如此,RecyclerView中将ViewHolder进行了单独的编写,这也正是google所不断提倡的,另外,RecyclerView能够更简单的实现布局的转换. 新的视图类RecyclerView,它被用来代替ListView以及GridView,提供更为高效的回收复用机制,同

  • Android App中ViewPager所带来的滑动冲突问题解决方法

    叙述 滑动冲突可以说是日常开发中比较常见的一类问题,也是比较让人头疼的一类问题,尤其是在使用第三方框架的时候,两个原本完美的控件,组合在一起之后,忽然发现整个世界都不好了. 关于滑动冲突 滑动冲突分类: 滑动冲突,总的来说就是两类. 1.同方向滑动冲突 比如ScrollView嵌套ListView,或者是ScrollView嵌套自己 2.不同方向滑动冲突 比如ScrollView嵌套ViewPager,或者是ViewPager嵌套ScrollView,这种情况其实很典型.现在大部分应用最外层都是

  • 详解Android中ListView实现图文并列并且自定义分割线(完善仿微信APP)

    昨天的(今天凌晨)的博文<Android中Fragment和ViewPager那点事儿>中,我们通过使用Fragment和ViewPager模仿实现了微信的布局框架.今天我们来通过使用ListView实现其中联系人一栏的基本视图,效果如下: 要实现上图的效果,我们要用到两个知识点: 1.这里我们使用自定义适配实现图文列表(当然也可以用SimpleAdapter) 通过继承BaseAdapter(抽象类)自定义适配器可以实现更灵活更复杂的列表. 自定义适配器ListView的优化: (1)使用固

  • Android App中使用ViewPager实现滑动分页的要点解析

    以前如果要做 Tab 分页的话,必须要用一个很难用的 TabActivity,而且做出来的效果很差,弹性也很小 忘了从什么时候开始,Google release 了 ViewPager 这好东西取代了以前难用的 Gallery 元件,加上从 Honeycomb 导入的 Fragment 之后终于能够简单做出好看又好用的 Layout 了! 这里我们采用PagerTabStrip ,做出来的效果如下 特色就是使用简单,出来的效果则是目前显示的分页 Tab 的文字会自动置中,然后分别在左右显示上一个

  • Android用webView包装WebAPP方法

    前言 Android webView 兼容体验真的差到了极点!! 前一阵子,老板要将 WebAPP 放到 Android 和 iOS 里面,而我因为以前做过安卓,所以这方面就由我来打包,原理是很简单的,就是打开 APP 的时候用 webView 加载网站的网址,这样服务器一次更新,就能更新微信版, iOS 版和 Android 版; 首先我要说一句,如果你的 WebAPP 里面有文件上传,并且想要完全兼容,那么就别用原生的 WebAPP, 后面我会写一个关于 crossWalk 的博客,不过在此

  • Android App使用RecyclerView实现上拉和下拉刷新的方法

    关于RecyclerView RecyclerView在Android 5.0以来被引入,以前经常使用的ListView 继承的是AbsListView,而RecyclerView则直接继承 ViewGroup,并实现了ScrollingView 和 NestedScrollingChild接口,RecyclerView相比ListView,是一次彻底的改变. RecyclerView比listview更先进更灵活,对于很多的视图它就是一个容器,可以有效的重用和滚动.当数据动态变化的时候请使用它

  • android使用ViewPager组件实现app引导查看页面

    我们安装完app后第一次打开app,通常都会有一个翻页图片形式的app引导简介说明.下面我们来实现这个功能.ViewPager这个组件与ListView和RecyclerView在使用上有很高的相似处,所以你如果学习过以上两种列表滚动控件,可以基于它们帮助你理解ViewPager的使用. 实现步骤: 1.在左右滑动页面的界面中,通常在屏幕下方都会有一个小圆点,我们需要给这些小圆点的实现准备一些东西.在drawable目录下创建两个xml文件,一个画出未选中的圆点,一个画出选中后的圆点. 2.准备

  • Android中TabLayout+ViewPager 简单实现app底部Tab导航栏

    前言 在谷歌发布Android Design Support Library之前,app底部tab布局的实现方法就有很多种,其中有RadioGroup+FrameLayout.TabHost+Fragment.FragmentPagerAdapter+ViewPager等方法,虽然这些方法虽然能达到同样的效果,但我个人总觉得有些繁琐.然而,Google在2015的IO大会上,给开发者们带来了全新的Android Design Support Library,里面包含了许多新控件,这些新控件有许多

  • Android自定义View app更新动画详解

    为了做一个有温度的IT男,我决定在以后的文章中给大家分享一些看到的,听到的一些东西,如果你不喜欢请留言让我知道,如果你喜欢请点个赞.你也可留言写下自己想分享的东西,温暖你我他.这次分享的是一首歌,毛不易的<消愁>,分享这首歌主要是这首歌的歌词,借用薛之谦的评价:"我是研究歌词的人,我研究了十几年,但是你写到我想给你跪!",下面贴部分歌词供大家欣赏 一杯敬朝阳,一杯敬月光 唤醒我的向往,温柔了寒窗 于是可以不回头的逆风飞翔 不怕心头有雨,眼底有霜 一杯敬故乡,一杯敬远方 守着

  • Android自定义view实现侧滑栏详解

    目录 前言 需求 效果图 编写代码 主要问题 前言 上一篇文章学了下自定义View的onDraw函数及自定义属性,做出来的滚动选择控件还算不错,就是逻辑复杂了一些.这篇文章打算利用自定义view的知识,直接手撕一个安卓侧滑栏,涉及到自定义LayoutParams.带padding和margin的measure和layout.利用requestLayout实现动画效果等,有一定难度,但能重新学到很多知识! 需求 这里类似旧版QQ(我特别喜欢之前的侧滑栏),有两层页面,滑动不是最左侧才触发的,而是从

  • Android自定义View实现体重表盘详解流程

    目录 效果视频 分析 起始角度 圆弧 指针 代码 初始化属性 画布 绘制内圆弧 绘制外圆弧 绘制中间指针 绘制中间文字 绘制左右两边文字 动画 全部代码 下载链接 效果视频 分析 起始角度 如下图所示,起点角度为150,终点角度为240 圆弧 白色圆弧为整个圆弧范围,蓝色圆弧为根据数据变动而覆盖白色圆弧,蓝色圆弧比白色圆弧大一点,突出显示 InnerArcPaint.setStrokeWidth( Width * (float)0.1 ); OuterArcPaint.setStrokeWidt

  • Android自定义view渐变圆形动画

    本文实例为大家分享了Android自定义view渐变圆形动画的具体代码,供大家参考,具体内容如下 直接上效果图 自定义属性 attrs.xml文件 <resources> <declare-styleable name="ProgressRing"> <!--进度起始色--> <attr name="pr_progress_start_color" format="color" /> <!--

  • Android自定义View实现波浪动画

    本文实例为大家分享了Android自定义View实现波浪动画的具体代码,供大家参考,具体内容如下 效果演示 代码调用与实现效果 xml中调用 <developer.shivam.waveview.Wave android:layout_width="match_parent" android:layout_height="match_parent" app:amplitude="100" app:quadrant="0.5&quo

  • Android自定义view之围棋动画效果的实现

    前言 废话不多说直接开始 老规矩,文章最后有源码 完成效果图 棋子加渐变色 棋子不加渐变色 一.测量 1.获取宽高 @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; useWidth = mWidth; if (mWidth > mHeight) { useWidth =

  • Android自定义View实现loading动画加载效果

    项目开发中对Loading的处理是比较常见的,安卓系统提供的不太美观,引入第三发又太麻烦,这时候自己定义View来实现这个效果,并且进行封装抽取给项目提供统一的loading样式是最好的解决方式了. 先自定义一个View,继承自LinearLayout,在Layout中,添加布局控件 /** * Created by xiedong on 2017/3/7. */ public class Loading_view extends LinearLayout { private Context m

  • Android 自定义view和属性动画实现充电进度条效果

    近期项目中需要使用到一种类似手机电池充电进度的动画效果,以前没学属性动画的时候,是用图片+定时器的方式来完成的,最近一直在学习动画这一块,再加上复习一下自定义view的相关知识点,所以打算用属性动画和自定义view的方式来完成这个功能,将它开源出来,供有需要的人了解一下相关的内容. 本次实现的功能类似下面的效果: 接下来便详细解析一下如何完成这个功能,了解其中的原理,这样就能举一反三,实现其他类似的动画效果了. 详细代码请看大屏幕 https://github.com/crazyandcoder

  • Android 自定义返回按钮的实例详解

    Android 自定义返回按钮的实例详解 程序中我们有时候想让放回按钮按照自己的需求调整页面而不是单纯的按照系统返回上一级,这个问题很简单,重写 onKeyDown 方法即可. 下面方法,包含了 webview 中的返回上一页和普通 activity 的单击设置和双击退出程序. @Override public boolean onKeyDown(int keyCode, KeyEvent event) { //如果我们用的是webview页面,想返回网页的上一页设置这里就可以了 if (key

  • Android 使用View Binding的方法详解

    前言 Android Studio稳定版发布了3.6版本,带来了一些新变化:首先外观,启动页变了,logo改了,更显现代化:增加Multi Preview功能,能同时预览多个尺寸屏幕的显示效果:模拟器支持多屏:也终于支持全新的视图绑定组件View Binding:等. 之前我们与视图交互的方式有findViewById.kotlin中引入Android Kotlin Extensions后直接通过id进行访问.前者模板化严重,重复代码多:后者最为方便.现在有了新的选择–View Binding,

随机推荐