Android动画可以说是老生常谈了,一个Android相机的UI想要炫酷,肯定离不开动画,现在也有很多现成的动画库,如果想要看懂这些库是如何实现的,或者说想自己不借助其它库,给自己的APP添加点动画,那就不得不懂Android最本质的两种动画了,这两种动画就是视图动画和属性动画,本文通过详细的示例代码对这两种动画资源进行讲解。最后顺带一下LayoutTransition类如何实现布局动画。
官方概述地址
视图动画包括补间动画和帧动画两种,如名称一样,视图动画只是对View进行视觉上的移动,旋转,缩放,透明度的效果,如果一个带点击事件的View,通过视图动画从A位置移动到B位置,那么你点击B位置是无法触发View的事件的,因为你只有点击View最初的位置即A位置,才能真正点的到这个View。
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"/>
ImageView imageView=findViewById(R.id.testImg);
AlphaAnimation alphaAnimation = (AlphaAnimation) AnimationUtils.loadAnimation(this, R.anim.alpha);
imageView.setAnimation(alphaAnimation);
private AlphaAnimation alphaAnimation()
{
//构造函数传入透明度开始值和结束值
AlphaAnimation alphaAnimation = new AlphaAnimation(0.0f, 1.0f);
//设置动画时常
alphaAnimation.setDuration(2000);
//设置动画的插值算法
alphaAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
return alphaAnimation;
}
//应用动画
imageView.setAnimation(alphaAnimation());
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="45"
android:pivotX="50%"
android:pivotY="50%"
android:duration="2000"/>
ImageView imageView=findViewById(R.id.testImg);
RotateAnimation rotateAnimation = (RotateAnimation) AnimationUtils.loadAnimation(this, R.anim.rotate);
imageView.setAnimation(rotateAnimation);
private RotateAnimation rotateAnimation()
{
/**
* public RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue,
* int pivotYType, float pivotYValue)
* fromDegrees 旋转前的角度
* toDegrees 旋转后的角度
* pivotXType 中心点类型:是相对于View本身(Animation.RELATIVE_TO_SELF)还是相对于View的父控件(Animation.RELATIVE_TO_PARENT)
* pivotXValue 旋转中心点的相对位置x
* pivotYType 中心点类型
* pivotYValue 旋转中心点的相对位置y
*/
RotateAnimation rotateAnimation = new RotateAnimation(0.0f, 45.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
//动画时长
rotateAnimation.setDuration(2000);
//是否将View保留在动画旋转后的状态
rotateAnimation.setFillAfter(true);
//设置插值方法
rotateAnimation.setInterpolator(new AccelerateInterpolator());
return rotateAnimation;
}
//应用动画
imageView.setAnimation(rotateAnimation());
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:toXScale="2.0"
android:toYScale="0.5"
android:pivotY="50%"
android:pivotX="50%"
android:duration="2000"/>
ImageView imageView=findViewById(R.id.testImg);
ScaleAnimation scaleAnimation = (ScaleAnimation) AnimationUtils.loadAnimation(this, R.anim.scale);
imageView.setAnimation(scaleAnimation);
private ScaleAnimation scaleAnimation()
{
/**
* ScaleAnimation(float fromX, float toX, float fromY, float toY,
* int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)
* fromX 对应xml的fromXScale
* toX 对应xml的toXScale
* fromY 对应xml的fromYScale
* toY 对应xml的toYScale
* pivotXType 与上面rotate的意义一样
* pivotXValue 与上面rotate的意义一样
* pivotYType 与上面rotate的意义一样
* pivotYValue 与上面rotate的意义一样
*/
ScaleAnimation scaleAnimation = new ScaleAnimation(1.0f, 2.0f, 1.0f, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(2000);
scaleAnimation.setFillAfter(true);
scaleAnimation.setInterpolator(new DecelerateInterpolator());
return scaleAnimation;
}
//应用动画
imageView.setAnimation(scaleAnimation());
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="92%p"
android:toYDelta="0"
android:duration="2000"/>
ImageView imageView=findViewById(R.id.testImg);
TranslateAnimation translateAnimation = (TranslateAnimation) AnimationUtils.loadAnimation(this, R.anim.translate);
imageView.setAnimation(translateAnimation);
private TranslateAnimation translateAnimation()
{
/**
* public TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,
* int fromYType, float fromYValue, int toYType, float toYValue)
* fromXType 移动前X的相对位置类型(同scale和rotate的pivotXType),Animation.RELATIVE_TO_PARENT代表相对于View的父布局,Animation.RELATIVE_TO_SELF代表相对于View本身
* fromXValue 移动前X的相对位置
* toXType 与fromXType含义类似
* toXValue 移动后X的相对位置
* fromYType 与fromXType含义类似
* fromYValue 移动前Y的相对位置
* toYType 与fromXType含义类似
* toYValue 移动后Y的相对位置
*/
TranslateAnimation translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, 0.f, Animation.RELATIVE_TO_PARENT, 0.96f, Animation.RELATIVE_TO_PARENT, 0f, Animation.RELATIVE_TO_PARENT, 0.f);
//设置动画时常
translateAnimation.setDuration(2000);
//设置是否将View的位置保留在动画之后的状态
translateAnimation.setFillAfter(true);
//设置插值算法
translateAnimation.setInterpolator(new DecelerateInterpolator());
return translateAnimation;
}
//应用动画
imageView.setAnimation(translateAnimation());
上面我们讲解了4种动画效果(透明度,选择,缩放,移动),但上面的方法只能对控件应用一个动画,如果想要控件同时具备好几种动画,就可以采用AnimationSet,它就是把上面4种动画融合起来一起使用。
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:shareInterpolator="false">
<scale
android:duration="2000"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:toXScale="2.0"
android:toYScale="0.5"
android:pivotX="50%"
android:pivotY="50%"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"/>
<set
android:duration="1000"
android:startOffset="1000"
android:interpolator="@android:anim/accelerate_interpolator">
<scale
android:fromXScale="2.0"
android:fromYScale="0.5"
android:toXScale="0.0"
android:toYScale="0.0"
android:pivotY="50%" android:pivotX="50%"/>
<rotate
android:fromDegrees="0"
android:toDegrees="-45"
android:pivotX="50%"
android:pivotY="50%"/>
set>
set>
ImageView imageView=findViewById(R.id.testImg);
AnimationSet animation = (AnimationSet) AnimationUtils.loadAnimation(this, R.anim.set);
imageView.setAnimation(animation);
//用Java代码实现xml中的配置
private AnimationSet animationSet()
{
//创建动画集set
//public AnimationSet(boolean shareInterpolator)
//shareInterpolator:因为set中包含很多种动画,因此需要判断当前set的插值算法是否应用到set中所有动画上
AnimationSet animationSet = new AnimationSet(false);
animationSet.setFillAfter(true);
//创建缩放动画
ScaleAnimation scaleAnimation = new ScaleAnimation(1.0f, 2.0f, 1.0f, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
scaleAnimation.setDuration(2000);
//将缩放动画添加到动画集中
animationSet.addAnimation(scaleAnimation);
//创建子动画集set
AnimationSet animationSubSet = new AnimationSet(true);
animationSubSet.setStartOffset(1000);
animationSubSet.setDuration(1000);
animationSubSet.setInterpolator(new AccelerateInterpolator());
//创建缩放动画
ScaleAnimation scaleAnimation1 = new ScaleAnimation(2.0f, 0.0f, 0.5f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
//添加到子动画集中
animationSubSet.addAnimation(scaleAnimation1);
//创建选择动画
RotateAnimation rotateAnimation = new RotateAnimation(0.0f, -45.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
//添加到子动画集中
animationSubSet.addAnimation(rotateAnimation);
//将子动画集添加到动画集中
animationSet.addAnimation(animationSubSet);
return animationSet;
}
//应用动画
imageView.setAnimation(animationSet());
上一节的补间动画是通过插值的方法将两个要素中间的部分插值出数据。本节讲解的帧动画是不用插值出数据,而是每一帧图片都由我们自己指定。
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/pic1" android:duration="100"/>
<item android:drawable="@drawable/pic2" android:duration="100"/>
<item android:drawable="@drawable/pic3" android:duration="100"/>
<item android:drawable="@drawable/pic4" android:duration="100"/>
<item android:drawable="@drawable/pic5" android:duration="100"/>
<item android:drawable="@drawable/pic6" android:duration="100"/>
<item android:drawable="@drawable/pic7" android:duration="100"/>
<item android:drawable="@drawable/pic8" android:duration="100"/>
<item android:drawable="@drawable/pic9" android:duration="100"/>
<item android:drawable="@drawable/pic10" android:duration="100"/>
animation-list>
ImageView img = findViewById(R.id.testFrame);
img.setBackgroundResource(R.drawable.frame);
AnimationDrawable background = (AnimationDrawable) img.getBackground();
background.start();
private AnimationDrawable animationDrawable()
{
AnimationDrawable animationDrawable = new AnimationDrawable();
try {
//添加帧图片,同时设置每帧播放时间
for (int i = 1; i <= 10; i++) {
Field background = R.drawable.class.getField("pic"+i);
int res_id = background.getInt(background.getName());//通过反射获取资源id
animationDrawable.addFrame(ContextCompat.getDrawable(this,res_id),100);//添加帧数据
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return animationDrawable;
}
//给view应用帧动画
AnimationDrawable animationDrawable = animationDrawable();
img.setBackground(animationDrawable);
animationDrawable.start();
属性动画比上面介绍的视图动画(补间动画,帧动画)更加灵活,适用范围更广,最重要的是视图动画只是视觉上一个动画,本质位置没有发生变化,比如一个View通过补间动画移动到了某个位置,你点击当前位置是无法触发它的点击事件的,而通过属性动画可以触发。
ValueAnimator继承自Animator,通过ValueAnimator可以计算给定的起点值和终点值在一段时间内各个时间点的变化值,通过将各个时间点的变化值设置到View的透明度,缩放,旋转,位移,背景颜色,背景图片等等属性上,达到View动画的效果。
ValueAnimator使用时只需要选择求解器(继承自TypeEvaluator)并指定起点值和终点值,但常用的ofFloat和ofInt内部都有默认的求解器,因此不需要自己指定,当然我们也可以自定义求解器。
ValueAnimator alphaAnimator = ValueAnimator.ofFloat(1.0f, 0.5f);
alphaAnimator.setDuration(2000);
alphaAnimator.start();
alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(@NonNull ValueAnimator animation) {
//动态计算一段时间内值的变化
Log.d(TAG, "onAnimationUpdate Float: "+animation.getAnimatedValue());
//将该值设置给View属性
imageView.setAlpha((Float) animation.getAnimatedValue());
}
});
ValueAnimator colorAnimator = ValueAnimator.ofArgb(0xffff00ff, 0xffffff00, 0xffff00ff);
colorAnimator.setDuration(2000);
colorAnimator.start();
colorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(@NonNull ValueAnimator animation) {
Log.d(TAG, "onAnimationUpdate Argb: "+animation.getAnimatedValue());
imageView.setBackgroundColor((Integer) animation.getAnimatedValue());
}
});
ValueAnimator objectAnimator = ValueAnimator.ofObject(new MyEvaluator(), new AlphaScaleObject(1, 1), new AlphaScaleObject(0, 2));
objectAnimator.setDuration(3000);
objectAnimator.start();
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(@NonNull ValueAnimator animation) {
AlphaScaleObject animatedValue = (AlphaScaleObject) animation.getAnimatedValue();
Log.d(TAG, "onAnimationUpdate: Object"+animatedValue.toString());
imageView.setAlpha(animatedValue.alphaValue);
imageView.setScaleType(ImageView.ScaleType.CENTER);
imageView.setScaleX(animatedValue.scaleValue);
imageView.setScaleY(animatedValue.scaleValue);
}
});
//自定义求值器
class MyEvaluator implements TypeEvaluator<AlphaScaleObject>
{
// 属性动画封装了一个因子fraction,我们设置动画时需要setDuration(xxxx),例如时间为1000ms,那么当到达100ms时,fraction就为0.1
// fraction也就是当前时间占总时间的百分比,startValue和endValue就是我们传入的初始值和结束值
@Override
public AlphaScaleObject evaluate(float fraction, AlphaScaleObject startValue, AlphaScaleObject endValue) {
//计算某个时刻的alpha值和scale值
float currAlphaValue= startValue.alphaValue+(endValue.alphaValue-startValue.alphaValue)*fraction;
float currScaleValue= startValue.scaleValue+(endValue.scaleValue-startValue.scaleValue)*fraction;
return new AlphaScaleObject(currAlphaValue,currScaleValue);
}
}
class AlphaScaleObject
{
float alphaValue;//透明度变化值
float scaleValue;//缩放变化值
public AlphaScaleObject(float alphaValue, float scaleValue) {
this.alphaValue = alphaValue;
this.scaleValue = scaleValue;
}
@Override
public String toString() {
return "AlphaScaleObject{" +
"alphaValue=" + alphaValue +
", scaleValue=" + scaleValue +
'}';
}
}
ObjectAnimator继承自ValueAnimator,因此它本质还是ValueAnimator,只是ValueAnimator的使用比较麻烦,因为ValueAnimator每次都要在监听函数中监听每个时刻的数据,然后设置给View动画属性,这个过程比较麻烦,代码也不简洁,因此ObjectAnimator对其进行了封装,方便开发者编写更简要的代码。
/**
* public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)
* target: 需要作用动画的View
* propertyName:通过反射的方式执行view中propertyName的set方法。
* 如想要在X轴上移动View倒霉指定位置,需要执行View中的setTranslationX方法,
* 因此propertyName设置为translationX即可。
*/
ObjectAnimator translationX = ObjectAnimator.ofFloat(imageView, "translationX", 0, 300, -100, 200, -50, 0);
translationX.setDuration(2000);
translationX.setRepeatCount(10);
translationX.start();
ObjectAnimator scaleX = ObjectAnimator.ofFloat(imageView, "scaleX", 0f, 1.5f, 2f, 1.5f, 0f, 0.5f, 0.2f, 1f);
imageView.setScaleType(ImageView.ScaleType.CENTER);
scaleX.setDuration(2000);
scaleX.start();
ObjectAnimator alpha = ObjectAnimator.ofFloat(imageView, "alpha", 0f, 0.5f, 1.0f);
alpha.setDuration(2000);
alpha.start();
ObjectAnimator rotation = ObjectAnimator.ofFloat(imageView, "rotation", 0, 180, 0, -180, 0);
rotation.setDuration(2000);
rotation.start();
//贝塞尔曲线动画
Path path = new Path();
path.quadTo(800, 200, 800, 800);
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "x", "y", path);
animator.setDuration(4000);
animator.start();
//改变颜色动画
ObjectAnimator backgroundColor = ObjectAnimator.ofArgb(imageView, "BackgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
backgroundColor.setDuration(4000);
backgroundColor.start();
//多个动画一起执行(AnimatorSet也能实现该功能)
PropertyValuesHolder rotationHolder = PropertyValuesHolder.ofFloat("Rotation", 90, -90, 45, -45, 60, -60);
PropertyValuesHolder colorHolder = PropertyValuesHolder.ofInt("BackgroundColor", 0xff55aa11, 0xff115633, 0xff123344, 0xffaabbcc);
PropertyValuesHolder scaleXHolder = PropertyValuesHolder.ofFloat("ScaleX", 1f, 1.1f, 1.2f, 1.5f, 1.8f, 1.5f, 1.2f, 1.1f, 1);
PropertyValuesHolder scaleYHolder = PropertyValuesHolder.ofFloat("ScaleY", 1f, 1.1f, 1.2f, 1.5f, 1.8f, 1.5f, 1.2f, 1.1f, 1);
ObjectAnimator objectAnimator1 = ObjectAnimator.ofPropertyValuesHolder(imageView, rotationHolder, colorHolder, scaleXHolder, scaleYHolder);
objectAnimator1.setDuration(3000);
objectAnimator1.setInterpolator(new AccelerateInterpolator());
objectAnimator1.start();
AnimatorSet继承自Animator,通过AnimatorSet您可以根据一个动画开始或结束的时间来播放另一个动画,可以指定是同时播放动画、按顺序播放还是在指定的延迟时间后播放。其实就是对上面两种属性动画的操作入口,比较简单,看下API就知道怎么使用了。
AnimatorSet接口文档
搞懂了上面讲解的属性动画和视图动画,再看LayoutTransition不要太简单,只是对属性动画和视图动画再次调用而已。
LayoutTransition可以在ViewGroup对象的布局变化时启用自动动画。为了使得一个ViewGroup具备LayoutTransition的功能,需要调用ViewGroup#setLayoutTransition(LayoutTransition)。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/shadow_activity"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<Button
android:text="Add View"
android:onClick="onAddViewClick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:orientation="vertical"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
LinearLayout>
LinearLayout>
package com.xiaomi.androidanimationtest;
import androidx.appcompat.app.AppCompatActivity;
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private int mCount=1;
private LinearLayout mLinearLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLinearLayout=findViewById(R.id.linearLayout);
}
public void onAddViewClick(View view) {
//创建TextView并设置属性
TextView textView = new TextView(this);
textView.setText("newItem:"+mCount++);
ViewGroup.MarginLayoutParams params = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 130);
params.bottomMargin=10;
textView.setLayoutParams(params);
textView.setBackgroundColor(getResources().getColor(com.google.android.material.R.color.design_default_color_primary));
textView.setTextColor(Color.WHITE);
textView.setGravity(Gravity.CENTER|Gravity.LEFT);
//创建LayoutTransition
LayoutTransition layoutTransition = new LayoutTransition();
//创建动画
ObjectAnimator animator = ObjectAnimator.ofFloat(textView, "translationX", 200, 0);
//设置动画
layoutTransition.setAnimator(LayoutTransition.APPEARING,animator);
//设置动画时常
layoutTransition.setDuration(500);
//ViewGroup添加LayoutTransition
mLinearLayout.setLayoutTransition(layoutTransition);
mLinearLayout.addView(textView);
}
}
好久好久好久没有更博客了,因为毕业后工作的原因,实在是没有多余精力写文章,但是不输出又怎么会进步呢?后续我会坚持输出文章(只是频率比较低了。。。),也希望输出的文章能够帮助到博友,一起进步。