一、FrameAnimation(帧动画)
依赖于完整的UI资源,按顺序依次播放图片集合,存在一定局限性,资源文件占用太大。
- 在drawable目录下创建资源文件animation_list.xml。
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/a_0"
android:duration="100" />
<item
android:drawable="@drawable/a_1"
android:duration="100" />
<item
android:drawable="@drawable/a_2"
android:duration="100" />
</animation-list>
ImageView img= (ImageView) findViewById(R.id.animation1);
img.setImageResource(R.drawable.frame_anim);
AnimationDrawable animationDrawable = (AnimationDrawable) img.getDrawable();
animationDrawable.start();
二、TweenedAnimation(补间动画)
- 补间动画又可以分为四种形式,分别是 alpha(淡入淡出),translate(位移),scale(缩放大小),rotate(旋转)。
xml形式实现:
- 淡入淡出动画
- 在anim目录下创建alpha_anim.xml。
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromAlpha="1.0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:toAlpha="0.0" />
- 要实现的java代码
Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.alpha_anim);
ImageView img = (ImageView) findViewById(R.id.img);
img.startAnimation(animation);
其它三种实现方式类似,具体属性如下:
Rotate属性详解
XML属性 | java代码 | 解释 |
---|---|---|
android:fromDegrees | RotateAnimation(float fromDegrees, , , ) | 旋转开始角度,正代表顺时针度数,负代表逆时针度数 |
android:toDegrees | RotateAnimation(, float toDegrees, ,) | 旋转结束角度,正代表顺时针度数,负代表逆时针度数 |
android:pivotX | RotateAnimation(, ,float pivotX, ) | 缩放起点X坐标(数值、百分数、百分数p)如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点 |
android:pivotY | RotateAnimation(, , , float pivotY) | 缩放起点Y坐标,同上规律 |
Scale属性详解
XML属性 | java代码 | 解释 |
---|---|---|
android:fromXScale | ScaleAnimation(float fromX, …) | 初始X轴缩放比例,1.0表示无变化 |
android:toXScale | ScaleAnimation(…, float toX, …) | 结束X轴缩放比例 |
android:fromYScale | ScaleAnimation(…, float fromY, …) | 初始Y轴缩放比例 |
android:toYScale | ScaleAnimation(…, float toY, …) | 结束Y轴缩放比例 |
android:pivotX | ScaleAnimation(…, float pivotX, …) | 缩放起点X坐标(数值、百分数、百分数p)如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点 |
android:pivotY | ScaleAnimation(…, float pivotY) | 缩放起点Y |
Animation属性详解
XML属性 | java代码 | 解释 |
---|---|---|
android:detachWallpaper | setDetachWallpaper(boolean) | 是否在壁纸上运行 |
android:duration | setDuration(long) | 动画持续时间,毫秒为单位 |
android:fillAfter | setFillAfter(boolean) | 控件动画结束时是否保持动画最后的状态 |
android:fillBefore | setFillBefore(boolean) | 控件动画结束时是否还原到开始动画前的状态 |
android:fillEnabled | setFillEnabled(boolean) | 与android:fillBefore效果相同 |
android:interpolator | setInterpolator(Interpolator) | 设定插值器(指定的动画效果,譬如回弹等) |
android:repeatCount | setRepeatCount(int) | 重复次数 |
android:repeatMode | setRepeatMode(int) | 重复类型有两个值,reverse表示倒序回放,restart表示从头播放 |
android:startOffset | etStartOffset(long) | 调用start函数之后等待开始运行的时间,单位为毫秒 |
android:zAdjustment | setZAdjustment(int) | 表示被设置动画的内容运行时在Z轴上的位置(top/bottom/normal),默认为normal |
插值器
- AccelerateDecelerateInterpolator 变化率开始和结束缓慢但在中间加速。
- AccelerateInterpolator 变化率开始缓慢然后加速。
- AnticipateInterpolator 变化开始向后然后向前。
- AnticipateOvershootInterpolator 变化开始向后,向前晃动并超过目标值,然后最终返回到最终值。
- BounceInterpolator 变化在结束时反弹。
- CycleInterpolator 动画重复指定的周期数。
- DecelerateInterpolator 变化率快速开始然后减速。
- LinearInterpolator 变化率恒定。
- OvershootInterpolator 变化向前晃动并超过一个值,然后返回。
- TimeInterpolator 一个允许您实现自己的插补器的接口。
- PathInterpolator 定义路径坐标后按照路径坐标来跑。
##三、属性动画
ValueAnimator使用简介
-
ValueAnimator.ofInt(int values)使用
(1)在res/animator的文件夹里创建相应的xml文件。
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0" // 初始值
android:valueTo="100" // 结束值
android:valueType="intType" // 变化值类型 :floatType & intType
android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果
android:startOffset ="1000" // 动画延迟开始时间(ms)
android:fillBefore = “true” // 动画播放完后,视图是否会停留在动画开始的状态,默认为true
android:fillAfter = “false” // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
android:fillEnabled= “true” // 是否应用fillBefore值,对fillAfter值无影响,默认为true
android:repeatMode= “restart” // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart|
android:repeatCount = “0” // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
android:interpolator = @[package:]anim/interpolator_resource // 插值器,即影响动画的播放速度,下面会详细讲
/>
在Java代码中启动动画
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);
// 设置动画对象
animator.setTarget(view);
animator.start();
(2)java代码设置(实际开发中建议使用…)
// 步骤1:设置动画属性的初始值 & 结束值
ValueAnimator anim = ValueAnimator.ofInt(0, 3);
anim.setDuration(500);
// 设置动画运行的时长
anim.setStartDelay(500);
// 设置动画延迟播放时间
anim.setRepeatCount(0);
// 设置动画重复播放次数 = 重放次数+1,动画播放次数 = infinite时,动画无限重复
anim.setRepeatMode(ValueAnimator.RESTART);
// 设置重复播放动画模式,ValueAnimator.RESTART(默认):正序重放,ValueAnimator.REVERSE:倒序回放
// 设置 值的更新监听器
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentValue = (Integer) animation.getAnimatedValue();
// 获得改变后的值
View.setproperty(currentValue);
//刷新视图,即重新绘制,从而实现动画效果
View.requestLayout();
}
});
anim.start();
}
-
ValueAnimator.oFloat(float values)使用与ValueAnimator.ofInt(int values)类似,不再做阐述!
-
ValueAnimator.ofObject()使用
-
估值器(TypeEvaluator) 简介
-
ValueAnimator.ofFloat()实现了将初始值以浮点型的形式过渡到结束值的逻辑,FloatEvaluator的代码实现:
public class FloatEvaluator implements TypeEvaluator {
// FloatEvaluator实现了TypeEvaluator接口
public Object evaluate(float fraction, Object startValue, Object endValue) {
// fraction:表示动画完成度(根据它来计算当前动画的值)
// startValue、endValue:动画的初始值和结束值
float startFloat = ((Number) startValue).floatValue();
return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
// 初始值 过渡 到结束值 的算法是:
// 1. 用结束值减去初始值,算出它们之间的差值
// 2. 用上述差值乘以fraction系数
// 3. 再加上初始值,就得到当前动画的值
}
}
- 对于ValueAnimator.ofObject(),我们需自定义估值器(TypeEvaluator)来告知系统如何进行从初始对象过渡到结束对象的逻辑:
- 实现的动画效果:一个圆从一个点 移动到另外一个点 。
步骤1:定义对象类
Point.java
public class Point {
private float x;
private float y;
public Point(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
}
步骤2:实现TypeEvaluator接口
PointEvaluator.java
// 实现TypeEvaluator接口
public class PointEvaluator implements TypeEvaluator {
// 在evaluate()里写入对象动画过渡的逻辑
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 将动画初始值startValue 和 动画结束值endValue 强制类型转换成Point对象
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
// 根据fraction来计算当前动画的x和y的值
float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
// 将计算后的坐标封装到一个新的Point对象中并返回
Point point = new Point(x, y);
return point;
}
}
步骤3:将属性动画作用到自定义View当中
MyView.java
public class MyView extends View {
public static final float RADIUS = 70f;// 圆的半径 = 70
private Point currentPoint;// 当前点坐标
private Paint mPaint;// 绘图画笔
// 构造方法(初始化画笔)
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
// 初始化画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
// 绘制逻辑:先在初始点画圆,通过监听当前坐标值(currentPoint)的变化,每次变化都调用onDraw()重新绘制圆,从而实现圆的平移动画效果
@Override
protected void onDraw(Canvas canvas) {
// 如果当前点坐标为空(即第一次)
if (currentPoint == null) {
currentPoint = new Point(RADIUS, RADIUS);
// 在该点画一个圆:圆心 = (70,70),半径 = 70
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, RADIUS, mPaint);
Point startPoint = new Point(RADIUS, RADIUS);
// 初始点为圆心(70,70)
Point endPoint = new Point(700, 1000);
// 结束点为(700,1000)
// 步骤2:创建动画对象 & 设置初始值 和 结束值
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
anim.setDuration(5000);
// 即每当坐标值(Point对象)更新一次,该方法就会被调用一次
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
// 将每次变化后的坐标值(估值器PointEvaluator中evaluate()返回的Piont对象值)到当前坐标值对象(currentPoint)
// 每次赋值后就重新绘制,从而实现动画效果
invalidate();
// 调用invalidate()后,就会刷新View,即才能看到重新绘制的界面,即onDraw()会被重新调用一次
}
});
anim.start();
} else {
// 如果坐标值不为0,则画圆
// 所以坐标值每改变一次,就会调用onDraw()一次,就会画一次圆,从而实现动画效果
// 在该点画一个圆:圆心 = (30,30),半径 = 30
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, RADIUS, mPaint);
}
}
}
步骤4:在布局文件加入自定义View空间,到此就能显示出一个圆从一个点 移动到另外一个点。
ObjectAnimator类使用简介
- 1. 透明度
ObjectAnimator animator = new ObjectAnimator(view,"alpha",1f,0f,1f);
//延迟播放时间
animator.setStartDelay(500);
//设置重复次数
animator.setRepeatCount(0);
//设置重复模式
animator.setRepeatMode(ValueAnimator.RESTART);
//设置动画时间
animator.setDuration(1000);
animator.start();
动画效果:正常 - 全透明 - 不正常。
- 2. 旋转
ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "rotation", 0f, 360f);
animator.setDuration(5000);
animator.start();
动画效果是:0 - 360。
- 3. 平移
float curTranslationX = mButton.getTranslationX();
// 获得当前按钮的位置
ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "translationX", curTranslationX, 300,curTranslationX);
animator.setDuration(5000);
animator.start();
动画效果是:从当前位置平移到 x=1500 再平移到初始位置。
- 4. 缩放
ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "scaleX", 1f, 3f, 1f);
animator.setDuration(5000);
animator.start();
动画效果是:放大到3倍,再缩小到初始大小。
如果需要采用ObjectAnimator 类实现动画效果,那么需要操作的对象就必须有该属性的set()和 get()。
- 实现的动画效果:一个圆的颜色渐变
步骤1:自定义圆MyVIew
MyView.java
public class MyView extends View {
// 圆的半径 = 100
public static final float RADIUS = 100f;
private Paint mPaint;
// 设置背景颜色属性
private String color;
// 设置背景颜色的get()方法
public String getColor() {
return color;
}
// 设置背景颜色的set()方法
public void setColor(String color) {
this.color = color;
mPaint.setColor(Color.parseColor(color));
// 调用invalidate(),通过onDraw()重新绘制刷新视图
invalidate();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
// 初始化画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle(500, 500, RADIUS, mPaint);
}
}
步骤2:在布局文件加入自定义View控件
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">
<com.qin.animator.MyView
android:id="@+id/myview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</RelativeLayout>
步骤3:实现TypeEvaluator接口
ColorEvaluator.java
public class ColorEvaluator implements TypeEvaluator {
// 实现TypeEvaluator接口
private int mCurrentRed;
private int mCurrentGreen ;
private int mCurrentBlue ;
// 复写evaluate()
// 在evaluate()里写入对象动画过渡的逻辑:此处是写颜色过渡的逻辑
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 获取到颜色的初始值和结束值
String startColor = (String) startValue;
String endColor = (String) endValue;
// 通过字符串截取的方式将初始化颜色分为RGB三个部分,并将RGB的值转换成十进制数字
// 那么每个颜色的取值范围就是0-255
int startRed = Integer.parseInt(startColor.substring(1, 3), 16);
int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);
int startBlue = Integer.parseInt(startColor.substring(5, 7), 16);
int endRed = Integer.parseInt(endColor.substring(1, 3), 16);
int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);
int endBlue = Integer.parseInt(endColor.substring(5, 7), 16);
// 将初始化颜色的值定义为当前需要操作的颜色值
mCurrentRed = startRed;
mCurrentGreen = startGreen;
mCurrentBlue = startBlue;
// 计算初始颜色和结束颜色之间的差值
// 该差值决定着颜色变化的快慢:初始颜色值和结束颜色值很相近,那么颜色变化就会比较缓慢;否则,变化则很快
// 具体如何根据差值来决定颜色变化快慢的逻辑写在getCurrentColor()
int redDiff = Math.abs(startRed - endRed);
int greenDiff = Math.abs(startGreen - endGreen);
int blueDiff = Math.abs(startBlue - endBlue);
int colorDiff = redDiff + greenDiff + blueDiff;
if (mCurrentRed != endRed) {
mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,
fraction);
} else if (mCurrentGreen != endGreen) {
mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,
redDiff, fraction);
} else if (mCurrentBlue != endBlue) {
mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,
redDiff + greenDiff, fraction);
}
String currentColor = "#" + getHexString(mCurrentRed) + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);
// 最终将RGB颜色拼装起来,并作为最终的结果返回
return currentColor;
}
// 具体是根据fraction值来计算当前的颜色。
private int getCurrentColor(int startColor, int endColor, int colorDiff,
int offset, float fraction) {
int currentColor;
if (startColor > endColor) {
currentColor = (int) (startColor - (fraction * colorDiff - offset));
if (currentColor < endColor) {
currentColor = endColor;
}
} else {
currentColor = (int) (startColor + (fraction * colorDiff - offset));
if (currentColor > endColor) {
currentColor = endColor;
}
}
return currentColor;
}
// 将10进制颜色值转换成16进制。
private String getHexString(int value) {
String hexString = Integer.toHexString(value);
if (hexString.length() == 1) {
hexString = "0" + hexString;
}
return hexString;
}
}
步骤4:调用ObjectAnimator.ofObject()方法
MainActivity.java
public class MainActivity extends AppCompatActivity {
MyView2 myView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myView = (MyView2) findViewById(R.id.myview );
ObjectAnimator anim = ObjectAnimator.ofObject(myView2, "color", new ColorEvaluator(),"#0000FF", "#FF0000");
anim.setDuration(8000);
anim.start();
}
}
通过包装原始动画对象,间接给对象加上该属性的 get()和set()
public class MainActivity extends AppCompatActivity {
Button mButton;
ViewWrapper wrapper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.Button);
// 创建动画作用对象
wrapper = new ViewWrapper(mButton);
// 创建包装类,并传入动画作用的对象
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(3000).start();
}
});
}
// 提供ViewWrapper类,用于包装View对象
private static class ViewWrapper {
private View mTarget;
// 传入需要包装的对象
public ViewWrapper(View target) {
mTarget = target;
}
// 为宽度设置get()和 set()
public int getWidth() {
return mTarget.getLayoutParams().width;
}
public void setWidth(int width) {
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
}
AnimatorSet 类
方法 | 简介 |
---|---|
AnimatorSet.play(Animator anim) | 播放当前动画 |
AnimatorSet.after(long delay) | 将现有动画延迟delay毫秒后执行 |
AnimatorSet.with(Animator anim) | 将现有动画和传入的动画同时执行 |
AnimatorSet.after(Animator anim) | 将现有动画插入到传入的动画之后执行 |
AnimatorSet.before(Animator anim) | 将现有动画插入到传入的动画之前执行 |
在 res/animator的文件目录下创建动画.xml文件
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially" >
// ordering的属性值:sequentially & together
// sequentially:表示set中的动画,按照先后顺序逐步进行(a 完成之后进行 b )
// together:表示set中的动画,在同一时间同时进行,为默认值
<set android:ordering="together" >
<objectAnimator
android:duration="2000"
android:propertyName="translationX"
android:valueFrom="0"
android:valueTo="300"
android:valueType="floatType" >
</objectAnimator>
<objectAnimator
android:duration="3000"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360"
android:valueType="floatType" >
</objectAnimator>
</set>
<set android:ordering="sequentially" >
// 下面的动画按序进行
<objectAnimator
android:duration="1500"
android:propertyName="alpha"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType" >
</objectAnimator>
<objectAnimator
android:duration="1500"
android:propertyName="alpha"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" >
</objectAnimator>
</set>
</set>
在Java代码中启动动画
AnimatorSet animator = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.set_animation);
// 创建组合动画对象 & 加载XML动画
animator.setTarget(mButton);
// 设置动画作用对象
animator.start();
// 启动动画
动画另外一种用法(ViewPropertyAnimator)
mButton.animate().alpha(0f);
// 单个动画设置:将按钮变成透明状态
mButton.animate().alpha(0f).setDuration(5000).setInterpolator(new BounceInterpolator());
// 单个动画效果设置,参数设置
mButton.animate().alpha(0f).x(500).y(500);
// 组合动画:将按钮变成透明状态再移动到(500,500)
// 动画自动启动,无需调用start()方法.
四、FlingAnimation 与SpringAnimation
-
简介
-
基于Fling的动画使用与物体速度成比例的摩擦力。使用它来设置对象属性的动画,并希望逐渐结束动画。
-
触摸力决定了动画的加速和减速。
-
在每一帧中动画值和速度都会更新。
-
当受力达到平衡时动画停止。
-
添加支持库
dependencies {
implementation 'com.android.support:support-dynamic-animation:27.1.1'
}
- FlingAnimation 使用
ImageView img = (ImageView)findViewById(R.id.img);
//DynamicAnimation.X表示动画的类型
FlingAnimation fling = new FlingAnimation(img, DynamicAnimation.X);
//设置大于0的初速度,否则动画不会动
fling.setStartVelocity(500f);
//设置摩擦系数,默认0,摩擦系数越大越容易停止
fling.setFriction(0.5f);
//开启动画
fling.start();
- SpringAnimation 使用
//DynamicAnimation.X表示动画的类型
SpringAnimation springAnimation = new SpringAnimation(img, DynamicAnimation.X);
//设置大于0的初速度,否则动画不会动
springAnimation.setStartVelocity(2000);
SpringForce springForce = new SpringForce();
//设置弹性阻尼,值越大,反弹次数越小
springForce.setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY);
//设置生硬度,恢复成未拉伸状态所需的时间
springForce.setStiffness(SpringForce.STIFFNESS_LOW);
//设置最后静止时的位置
springForce.setFinalPosition(img.getX());
springAnimation.setSpring(springForce);
springAnimation.start();
- setDampingRatio()方法中参数共有4种可选项:
public static final float DAMPING_RATIO_HIGH_BOUNCY = 0.2F;
public static final float DAMPING_RATIO_MEDIUM_BOUNCY = 0.5F;
public static final float DAMPING_RATIO_LOW_BOUNCY = 0.75F;
public static final float DAMPING_RATIO_NO_BOUNCY = 1.0F;
- setStiffness()方法中共有4种可选项:
public static final float STIFFNESS_HIGH = 10000.0F;
public static final float STIFFNESS_MEDIUM = 1500.0F;
public static final float STIFFNESS_LOW = 200.0F;
public static final float STIFFNESS_VERY_LOW = 50.0F;
-
创建自定义动画属性
SpringAnimation和FlingAnimation的构造函数只能接收一个可动画属性参数,如ALPHA、ROTATION、SCALE等,如果要同时为多个属性生成动画,可以创建一个新的属性,改属性封装了我们想改变的其他动画属性值。
FloatPropertyCompat<View> scale = new FloatPropertyCompat<View>("scale") {
@Override
public float getValue(View view) {
// return the value of any one property
return view.getScaleX();
}
@Override
public void setValue(View view, float value) {
// Apply the same value to two properties
view.setScaleX(value);
view.setScaleY(value);
}
};
SpringAnimation stretchAnimation = new SpringAnimation(img, scale);
//传递一个有意义的值,以确保动画不会消耗太多的CPU性能
stretchAnimation.setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);
......
-
动画监听
DynamicAnimation提供了两个动画监听器OnAnimationUpdateListener和OnAnimationEndListener,前者监听动画值改变,后者监听动画结束状态。添加动画变化监听需要调用addUpdateListener()方法,重写onAnimationEnd()方法执行具体操作;如果移除动画监听,则需要调用removwUpdateListener()和removeEndListener()。
SpringAnimation springAnimation = new SpringAnimation(img, DynamicAnimation.X);
SpringForce springForce = new SpringForce();
springForce.setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY);
springForce.setStiffness(SpringForce.STIFFNESS_LOW);
springForce.setFinalPosition(img.getX());
springAnimation.setSpring(springForce);
springAnimation.setStartVelocity(2000);
springAnimation.start();
img.setImageResource(R.drawable.pic1);
springAnimation.addEndListener(new DynamicAnimation.OnAnimationEndListener() {
@Override
public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, float velocity) {
img.setImageResource(R.drawable.pic2);
}
});
FlingAnimation 与SpringAnimation示例
五、animateLayoutChanges属性使用
步骤1、编写activity_main.xml布局
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView 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"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
步骤2、编写layout.xml布局
layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn"
android:text="删除"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
步骤3、在res/menu中编写menu.xml布局
menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/add"
android:title="添加"
app:showAsAction="always" />
</menu>
步骤4、MainActivity布局
MainActivity.java
package com.qin.myapplication;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private LinearLayout mLl;
private int count = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLl = findViewById(R.id.container);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu,menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.add:
addItem();
}
return super.onOptionsItemSelected(item);
}
private void addItem() {
final View views = getLayoutInflater().inflate(R.layout.layout, mLl, false);
Button btn = views.findViewById(R.id.btn);
TextView tv = views.findViewById(R.id.tv);
mLl.addView(views);
tv.setText((count++)+"");
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mLl.getChildCount()!=1){
mLl.removeView(views);
count--;
}else{
Log.i("tag","没有更多子view!");
}
}
});
}
}
六、layoutAnimation用法
动画只在ListView或GradView子View第一次进入时有效,如果后面动态给listView添加子View时,此动画效果无效。
- 只需在ListView或GradView布局文件中使用即可,如:
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
<!-- 设置在这里 -->
android:layoutAnimation="@anim/layout_animation"
/>
- layout_animation.xml自定义动画
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@anim/left_into"
android:animationOrder="normal"
android:delay="0.5"
/>
- 通过代码方式设置:
//通过加载XML动画设置文件来创建一个Animation对象,子View进入的动画
Animation animation=AnimationUtils.loadAnimation(this, R.anim.left_into);
//得到一个LayoutAnimationController对象;
LayoutAnimationController lac=new LayoutAnimationController(animation);
//设置控件显示的顺序;
lac.setOrder(LayoutAnimationController.ORDER_REVERSE);
//设置控件显示间隔时间;
lac.setDelay(0.5f);
//为ListView设置LayoutAnimationController属性;
listView.setLayoutAnimation(lac);
七、LayoutTransition 使用
- LayoutTransition类用于当前布局容器中需要View添加,删除,隐藏,显示时设置布局容器子View的过渡动画。可以通过setLayoutTransition()方法为布局容器ViewGroup设置LayoutTransition对象,代码如下:
//初始化容器动画
LayoutTransition mTransitioner = new LayoutTransition();
container.setLayoutTransition(mTransitioner);
- LayoutTransition 常用函数:
函数名 | 简介 |
---|---|
setAnimator(int transitionType, Animator animator) | 设置不同状态下的动画过渡,transitionType取值为, APPEARING、DISAPPEARING、CHANGE_APPEARING、CHANGE_DISAPPEARING 、CHANGING |
setDuration(long duration) | 设置所有动画完成所需要的时长 |
setDuration(int transitionType, long duration) | 设置特定type类型动画时长,transitionType取值为, APPEARING、DISAPPEARING、CHANGE_APPEARING、CHANGE_DISAPPEARING 、CHANGING |
setStagger(int transitionType, long duration) | 设置特定type类型动画的每个子item动画的时间间隔 ,transitionType取值为: APPEARING、DISAPPEARING、CHANGE_APPEARING、CHANGE_DISAPPEARING、CHANGING |
setInterpolator(int transitionType, TimeInterpolator interpolator) | 设置特定type类型动画的插值器, transitionType取值为: APPEARING、DISAPPEARING、CHANGE_APPEARING、CHANGE_DISAPPEARING、CHANGING |
setStartDelay(int transitionType, long delay) | 设置特定type类型动画的动画延时 transitionType取值为, APPEARING、DISAPPEARING、CHANGE_APPEARING、CHANGE_DISAPPEARING 、CHANGING |
addTransitionListener(TransitionListener listener) | 设置监听器TransitionListener |
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private int i = 0;
private LinearLayout container;
private LayoutTransition mTransitioner;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
container = (LinearLayout) findViewById(R.id.container);
//构建LayoutTransition
mTransitioner = new LayoutTransition();
//设置给ViewGroup容器
container.setLayoutTransition(mTransitioner);
setTransition();
}
private void setTransition() {
//添加View时过渡动画效果
ObjectAnimator addAnimator = ObjectAnimator.ofFloat(null, "rotationY", 0, 90,0).setDuration(mTransitioner.getDuration(LayoutTransition.APPEARING));
mTransitioner.setAnimator(LayoutTransition.APPEARING, addAnimator);
// 移除View时过渡动画效果
ObjectAnimator removeAnimator = ObjectAnimator.ofFloat(null, "rotationX", 0, -90, 0).
setDuration(mTransitioner.getDuration(LayoutTransition.DISAPPEARING));
mTransitioner.setAnimator(LayoutTransition.DISAPPEARING, removeAnimator);
//view 动画改变时,布局中的每个子view动画的时间间隔
mTransitioner.setStagger(LayoutTransition.CHANGE_APPEARING, 30);
mTransitioner.setStagger(LayoutTransition.CHANGE_DISAPPEARING, 30);
/**
*LayoutTransition.CHANGE_APPEARING和LayoutTransition.CHANGE_DISAPPEARING的过渡动画效果
* 必须使用PropertyValuesHolder所构造的动画才会有效果,不然无效!使用ObjectAnimator是行不通的,
* 时,”left”、”top”、”bottom”、”right”属性的变动是必须设置的,至少设置两个,不然动画无效,问题是我们即使这些属性不想变动!!!也得设置!!!
* 这时只要传递的可变参数都一样就行如下面的(0,0)也可以是(100,100)即可。
*/
PropertyValuesHolder pvhLeft =
PropertyValuesHolder.ofInt("left", 0, 0);
PropertyValuesHolder pvhTop =
PropertyValuesHolder.ofInt("top", 0, 0);
PropertyValuesHolder pvhRight =
PropertyValuesHolder.ofInt("right", 0, 0);
PropertyValuesHolder pvhBottom =
PropertyValuesHolder.ofInt("bottom", 0, 0);
//view被添加时,其他子View的过渡动画效果
PropertyValuesHolder animator = PropertyValuesHolder.ofFloat("scaleX", 1, 1.5f, 1);
final ObjectAnimator changeIn = ObjectAnimator.ofPropertyValuesHolder(this, pvhLeft, pvhBottom, animator).setDuration(mTransitioner.getDuration(LayoutTransition.CHANGE_APPEARING));
//设置过渡动画
mTransitioner.setAnimator(LayoutTransition.CHANGE_APPEARING, changeIn);
//view移除时,其他子View的过渡动画
PropertyValuesHolder pvhRotation =
PropertyValuesHolder.ofFloat("scaleX", 1, 1.5f, 1);
final ObjectAnimator changeOut = ObjectAnimator.ofPropertyValuesHolder(
this, pvhLeft, pvhBottom, pvhRotation).
setDuration(mTransitioner.getDuration(LayoutTransition.CHANGE_DISAPPEARING));
mTransitioner.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, changeOut);
}
public void addView(View view) {
i++;
Button button = new Button(this);
button.setText(""+ i);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
container.addView(button, Math.min(1, container.getChildCount()), params);
}
public void removeView(View view) {
if (i > 0)
container.removeViewAt(0);
}
}
- 还有点需要注意的是在使用的ofInt,ofFloat中的可变参数值时,第一个值和最后一个值必须相同,不然此属性将不会有动画效果,比如下面首位相同是有效的
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top",100,0,100);
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top",0,100,0);
后续将会对Android Transition(Android过渡动画)做一个使用总结
参考:
https://developer.android.google.cn/training/animation/overview 官方解析
https://blog.csdn.net/carson_ho/article/details/72909894 属性动画
https://blog.csdn.net/javazejian/article/details/52334098 动画详解