Android原生动画概述:

对于APP开发中涉及到的一些动画基本上都可以用Android提供的各种原生动画类来实现,所以在学习自定义动画之前首先来对原生动画进行一个基本的了解,这里不详细对每一个原生动画进行深入学习,因为重点是学会如何自定义动画,其Android支持的原生动画主要有以下三类:

①、补间动画【View Animation】:

  • 平移动画:TranslateAnimation
  • 旋转动画:RotateAnimation
  • 缩放动画:ScaleAnimation
  • 渐变动画:AlphaAnimation

②、属性动画【Property Animation】:

这个动画在实际中用得比较多的是这两个类:ObjectAnimator和ValueAnimator,而ObjectAnimator是继承自ValueAnimator,如下:

其中ValueAnimator在之前的学习中也已经用过了,它可以对值进行变化。

对于上面两种动画其实是有一个比较大的区别的,下面来做一个小实验来直观的感受一下两者的区别:

新建一个工程,先准备布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.animationdemo.test.MainActivity"> <Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="traslate1"
android:text="点我平移补间动画" /> <Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="traslate2"
android:text="点我平移属性动画" />
</LinearLayout>

先看一下补间动画的效果:

public class MainActivity extends AppCompatActivity {

    private boolean isTranslate;

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} //补间动画平移
public void traslate1(View view) {
if (!isTranslate) {
TranslateAnimation translateAnimation = new TranslateAnimation(TranslateAnimation.ABSOLUTE, 0, TranslateAnimation.ABSOLUTE, 100, TranslateAnimation.ABSOLUTE, 0, TranslateAnimation.ABSOLUTE, 0);
translateAnimation.setDuration(1000);
translateAnimation.setFillAfter(true);
view.startAnimation(translateAnimation);
isTranslate = true;
} else {
Toast.makeText(this, "点我了", Toast.LENGTH_SHORT).show();
}
} //属性动画平移
public void traslate2(View view) { }
}

主要是看一下事件的点击效果,编译运行:

从运行的结果来看,补间动画的平移并非真正的将View给移动了,也就是本尊未动,只是它的影子动了,接着再来看一下属性动画对于同样效果的实现,如下:

public class MainActivity extends AppCompatActivity {

    private boolean isTranslate;
private boolean isTranslate2; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} //补间动画平移
public void traslate1(View view) {
if (!isTranslate) {
TranslateAnimation translateAnimation = new TranslateAnimation(TranslateAnimation.ABSOLUTE, 0, TranslateAnimation.ABSOLUTE, 100, TranslateAnimation.ABSOLUTE, 0, TranslateAnimation.ABSOLUTE, 0);
translateAnimation.setDuration(1000);
translateAnimation.setFillAfter(true);
view.startAnimation(translateAnimation);
isTranslate = true;
} else {
Toast.makeText(this, "点我了", Toast.LENGTH_SHORT).show();
}
} //属性动画平移
@SuppressLint("ObjectAnimatorBinding")
public void traslate2(View view) {
if (!isTranslate2) {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "translationX", 0, 100);
objectAnimator.setDuration(1000);
objectAnimator.start();
isTranslate2 = true;
} else {
Toast.makeText(this, "点我了2", Toast.LENGTH_SHORT).show();
}
}
}

编译运行:

很明显这次本尊和影子都一起动了,也就是真正的移动,这也就是两者之间的一个很明显的区别。

③、帧动画【Drawable Animation】:

这个就比较简单了,其实也就是动画是由系列的图片按一定的速度进行切换而实现滴,而在Android中叫AnimationDrawable。

自定义动画引入:

在上面已经介绍了Android提供的三种原生动画,这个在之后还会具体去应用到的,但是现实应用中可能会碰到原生动画满足不需求的情况,所以此时自定义动画就派上用场了,先来看一下如下系列动画效果:

它是来自于https://github.com/81813780/AVLoadingIndicatorView这个开源项目,接下来会挑几个效果进行学习,会搭配的Android的原生动画最终实现特定的效果,首先咱们要实现一个WIFI效果,如下:

而这个是通过单纯的canvas的绘制功能来实现动画效果而并未采用Android的原生动画,所以下面开始来一步步实现这个WIFI动画效果吧!

WIFI思路整理:

在正式编码实现之前,先来对效果进行一个思路整理, 先拿一个静态图来分析:

很明显该动画是由一个扇形和三个弧形组成,而且共用一个圆心,如下:

而且该圆心并非是自定义的中心点,另外需要控制绘制格数,当绘制满格信号的时候,又得回到一格信号,所以这里存在一上计数的逻辑控制,所以大概了解了之后接下来咱们就可以具体来动手将它一一实现啦。

WIFI图形绘制:

在加入动画之前先来在咱们自定义的View上绘制出一个WIFI的静态图形,所以先新建一个自定义View:

首先先画一个一格信号的扇形,而怎么绘制扇形在之前已经学习过了【http://www.cnblogs.com/webor2006/p/7341697.html】,可以用canvas.drawArc()进行,而扇形绘制需要一个外切矩形,外切矩形的大小决定了扇形的大小,如下:

而总共有四格信号,每格信号其实就都一个弧形,所以需要对应四个外切矩形,到底对应几个可以将其定义成一个常量,如下:

接着问题的焦点就回到了如何来确定每个RectF中的left、top、right、bottom的值了,那怎么确定呢?先来分析一下效果图:

再详细一点说:

所以说,咱们首先得要计算出这个总的半径长度,而这个长度应该是依赖于整个View的长宽,直径应该是取View宽高的较小值,所以:

有了直径之后,半径不就除以2嘛,这就是最大的半径,最后还得将其均分,所以:

所以每循环一次,则将这个基本半径进行成倍增加,如下:

这样就可以来确定外切距形的左、上、右、下的值啦,如下:

关于这四个参数为啥这样传,先不用过脑想,等绘出来之后再来理解,所以下面将这些矩形绘制出来看一下效果:

为了看到效果,在布局中定义这个View,如下:

编译运行:

呃~~乌黑一片~~什么鬼~~原因是得设置一下画笔的样式,如下:

编译运行:

嗯~~为了更进一步理解这个外切矩形,咱们拆解一个个来绘制,如下:

运行:

运行:

运行:

编译运行:

理解了这个外切矩形之后,下面再将这个rectF传给画布的drawArc方法中进行弧形的绘制,如下:

那为啥startAngle是-135,sweepAngle是90呢? 其中startAngle表示弧的起始角度,而我们想要绘制的起始角度应该是在坐标系中的如下位置:

而sweepAngle表示弧的弧度,那很明显咱们要绘制的弧度的结束位置应该在坐标系的如下位置:

所以这两个值为啥这样写的原因已经说明,接下来则开始运行看一下效果:

呃~未啥是长这个样子呢?其实是因为画笔样式的原因,下面修改如下:

编译运行:

嗯~~样子有了,但是弧条不够粗,所以改下画笔的粗度呗:

编译运行:

嗯~~够粗了,但是!!目前内容显示贴着顶边的,不太好看,此时应该将其往下移动一点,具体如下:

编译运行:

但是!!关于WIFI图形的绘制这块还差最后一个细节:第一格的应该是一个扇形,其它格数都是弧,而扇形与弧的决定是由这个参数来决定的:

所以此时需要加入条件判断来着情处理,如下:

编译运行:

WIFI动画实现:

有了静态图之后,接下来就是想办法将它动起来了,这里不采用任何Android提供的原生动画来弄,而是通过纯代码,所以需要解决两个问题:

1、不断的让界面重绘,那很简单,直接用invalidate();

2、得按一定的规律一格格的往上绘制,达到最大格数之后则需要又回到第一格,很显然需要用一个变量来控制当前绘制的格数,如下:

而每次绘制都是有一个for循环,所以需要通过一定的算法来控制咱们的绘制,具体如何做呢?下面直接给出:

另外每次绘制还得更改一下shouldExistSigalSize的大小,所以:

接下来得用一个定时器不断去让View重绘,有很多方法,这里直接用Handler来弄,可以这样做,如下:

此时编译运行:

嗯~~貌似不错~~但是还是存在一个小BUG的,第一次进来就绘制了两格,所以解决它很解决,只要改变初始化的格数既可,如下:

为何要将其改成4既可呢?因为:

所以,此时再编译运行就木问题了:

嗯~~不过差完美还有一步,资源的回收,此时如果将Activity退出,其刷新线程还在不断进行,如下:

所以接下来对资源进行回收,比较简单:

/**
* WIFI动画:采用自定义View的方式来实现,而非用Android提供的原生动画
*/
public class WiFi extends View { //constants
/* 总共有几格信号 */
private static final float SIGNAL_SIZE = 4F; //variables
private Paint paint;
/* 绘制的基准长度:取屏幕宽高的较少值 */
private int baseLength;
/* 当前信号存在的数量,默认从1格信号开始 */
private float shouldExistSinalSize = 4;
private Handler handler = new Handler();
private boolean isExit; public WiFi(Context context) {
this(context, null);
} public WiFi(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, -1);
} public WiFi(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
} private void init() {
paint = new Paint();
paint.setColor(Color.BLACK);
paint.setAntiAlias(true);
paint.setStrokeWidth(4);
//不断的进行绘制
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (isExit)
return;
Log.e("cexo", "handler run()...");
invalidate();
handler.postDelayed(this, 1000);
}
}, 1000);
} @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//绘制应该是在屏幕较小的长度来进行,所以先取出最小值,在绘制时需要参考该值
baseLength = Math.min(w, h);
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
shouldExistSinalSize++;
if (shouldExistSinalSize > 4) {
shouldExistSinalSize = 1;//如果已经绘制四格信号了,那么下一次则又回到一格信号状态
}
canvas.save();
canvas.translate(0, baseLength / SIGNAL_SIZE);//将画布往下移动一点,以防绘制太贴边
//根据信号的格数来进行相应的绘制
RectF rectF;
//计算出一个基准圆半径
float baseRadius = baseLength / 2 / SIGNAL_SIZE;//算出平均的半径
for (int i = 0; i < SIGNAL_SIZE; i++) {
if (i >= SIGNAL_SIZE - shouldExistSinalSize) {
float radius = baseRadius * i;
rectF = new RectF(radius, radius, baseLength - radius, baseLength - radius);
if (i < SIGNAL_SIZE - 1) {//此时需绘制一个弧形
paint.setStyle(Paint.Style.STROKE);//设置画笔样式为空心样式
canvas.drawArc(rectF, -135, 90, false, paint);
} else {//最下面则需要绘制一个扇形
paint.setStyle(Paint.Style.FILL);//设置画笔样式为实心样式
canvas.drawArc(rectF, -135, 90, true, paint);
}
}
}
canvas.restore();
} public void onDestroy() {
isExit = true;
}
}
public class MainActivity extends AppCompatActivity {

    private WiFi wifi;

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); wifi = findViewById(R.id.wifi);
} @Override
protected void onDestroy() {
super.onDestroy();
wifi.onDestroy();
}
}

至此wifi信号的效果就完美实现,看似简单的效果其实现并非简单,可见也花的篇幅也不小。

最新文章

  1. 自己实现一个简易web服务器
  2. arcgis server之路网服务发布
  3. phpize建立php扩展 Cannot find config.m4
  4. 移动端使用localResizeIMG4压缩图片
  5. HDU 2955(0-1背包问题)
  6. codeforces 422A A. Borya and Hanabi(暴力)
  7. html 绑定
  8. Markdown使用记录
  9. ViewHolder的作用和用法
  10. 基本排序算法:Python实现
  11. 【Loadrunner】初学Loadrunner——安装
  12. sql server统计字段的值在某些范围内中的个数
  13. Nginx基本配置、性能优化指南
  14. koa2+webSocket 聊天室
  15. JS开发引用HTML DOM的location和document对象
  16. Linux 小知识翻译 - 「内核(kernel)」
  17. Gift for GS5
  18. [转载][QT][SQL]sq]学习记录1_模糊搜索
  19. Python学习:6.python内置函数
  20. PHP扩展模块Pecl、Pear以及Perl的区别

热门文章

  1. IDEA工具上传项目报:Push rejected: Push to origin/master was rejected
  2. C基础知识(4):指针--p=&amp;a和*p=a的区别详解
  3. 【D3D12学习手记】CPU/GPU Synchronization
  4. Adobe Acrobat XI 中文版激活方法
  5. VirtualBox下Centos6.8网络配置
  6. [ZJOI2007]捉迷藏(动态点分治/(括号序列)(线段树))
  7. java 利用辗除法求两个整数的最大公约数和最小公倍数
  8. Go语言中的map(十一)
  9. 【Tomcat】热部署的遗留配置导致服务器无法启动
  10. WPF中Brush类型