参考资料
LayoutAnimationController实战案例
郭霖- Android属性动画完全解析
目录
- 1)视图动画
- 2)属性动画
- 3)对任意属性做动画
- 4)布局动画
- 5)Interpolators(插值器)&TypeEvaluator(估值器)
- 6)Android5.X SVG矢量动画
- 7) 动画特效
- 8)Activity过渡动画
- 9)帧动画
- 10)Lottie动画
1)视图动画
缺陷 | 优点 |
---|---|
不具备交互性 | 效率高&方便 |
图中动画结束后,位移立即回原位了,可以使用setFillAfter(true)来保留位移位置,但是它还是不会具备交互性
- xml方式 ~res/anim/xxx.xml
android:shareInterpolator=["true|"false""]>
Animation animation = AnimationUtils.loadAnimation(this,R.anim.xxx);
view.startAnimation(animation);
- 代码方式
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private AnimationSet as;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView txt = (TextView) findViewById(R.id.txt);
//透明度动画
AlphaAnimation aa = new AlphaAnimation(0, 1);
aa.setDuration(2000);
//旋转动画
//fromDegrees和toDegrees:这两个分别是旋转的起始角度和结束角度
//pivotX和pivotY:是旋转的中心点的X,Y坐标
//pivotXType和pivotYType:X,Y轴的伸缩模式,定义了pivotXValue和pivotYValue怎么被使用
//pivotXValue和pivotYValue:在X,Y方向的位置,但是会受pivotXType和pivotYType的影响
RotateAnimation ra = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5F, Animation.RELATIVE_TO_SELF, 0.5F);
ra.setDuration(2000);
//位移动画
TranslateAnimation ta = new TranslateAnimation(0, 0, 0, 200);
ta.setDuration(2000);
//缩放动画
ScaleAnimation sa = new ScaleAnimation(0,1,0,1,Animation.RELATIVE_TO_SELF,0.5F,Animation.RELATIVE_TO_SELF,0.5f);
sa.setDuration(2000);
//动画集合
AnimationSet as = new AnimationSet(true);
as.addAnimation(aa);
as.addAnimation(ra);
as.addAnimation(ta);
as.addAnimation(sa);
as.setFillAfter(true); //保留动画后的状态,设置true则位移后停留在原位!!
txt.startAnimation(as);
as.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
Log.i(TAG, "onAnimationStart: ");
}
@Override
public void onAnimationEnd(Animation animation) {
Log.i(TAG, "onAnimationEnd: ");
}
@Override
public void onAnimationRepeat(Animation animation) {
Log.i(TAG, "onAnimationRepeat: ");
}
});
}
}
2)属性动画
属性动画通过调用属性的get set方法,动态的改变对象的属性从而达到动画效果。
属性 | 说明 |
---|---|
translationX,translationY | 偏移 |
rotation | 2D旋转 |
rotationX,rotationY | 3D旋转 |
scaleX,scaleY | 围绕支点进行2D缩放 |
pivotX,pivotY | view的支点位置,默认为view对象中心点 |
x,y | view动画后的最终位置 |
alpha | 透明度,1-不透明(默认),0-完全透明 |
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(this);
//属性动画
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(btn, "rotation", 180);
// btn.setPivotX(300); //控制着view的支点位置,围绕着该支点做rotation和scale
// btn.setPivotY(100); //控制着view的支点位置,围绕着该支点做rotation和scale
objectAnimator.setDuration(1000);
objectAnimator.start();
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.i(TAG, "onAnimationUpdate: " + animation.getAnimatedValue());
//TODO
}
});
objectAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
//动画开始时调用
Log.i(TAG, "onAnimationStart: ");
}
@Override
public void onAnimationEnd(Animator animation) {
//动画结束时调用
Log.i(TAG, "onAnimationEnd: ");
}
@Override
public void onAnimationCancel(Animator animation) {
//动画被取消时调用
Log.i(TAG, "onAnimationCancel: ");
}
@Override
public void onAnimationRepeat(Animator animation) {
//动画循环播放时调用
Log.i(TAG, "onAnimationRepeat: ");
}
});
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn:
ObjectAnimator oa1 = ObjectAnimator.ofFloat(btn,"translationY",100);
ObjectAnimator oa2 = ObjectAnimator.ofFloat(btn,"rotationY",180);
ObjectAnimator oa3 = ObjectAnimator.ofFloat(btn,"scaleX",1.5F);
ObjectAnimator oa4 = ObjectAnimator.ofFloat(btn,"alpha",0.5F);
// 注意与视图动画的 AnimationSet 的区别
AnimatorSet as = new AnimatorSet();
as.setDuration(1000);
as.play(oa1).with(oa2).with(oa3).with(oa4); //一起执行 相当于as.playTogether(oa1,oa2);
// as.play(oa1).after(oa2); //顺序执行 as.play(oa1).before(oa2); 相当于as.playSequentially(oa1,oa2);
as.start();
//另一种写法 ========
// PropertyValuesHolder 就相当于视图动画的AnimationSet
// PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationY", 100);
// PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("rotationY", 180); //3D Y轴旋转180
// PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleX", 1.5F); //放大1.5
// ObjectAnimator.ofPropertyValuesHolder(btn, pvh1, pvh2, pvh3).setDuration(2000).start();
break;
}
}
}
- 在XML中定义属性动画
新建 ~/res/animator/xxx.xml (注意:属性动画定义在animator目录下)
Animator animator = AnimatorInflater.loadAnimator(this,R.animator.object_animator);
animator.setTarget(btn2);
animator.start();
- View的animate方法
View的animate()相当于属性动画的简写形式
btn2.animate()
.alpha(0.5f)
.x(300)
.setDuration(1000)
.withStartAction(new Runnable() {
@Override
public void run() {
}
})
.withEndAction(new Runnable() {
@Override
public void run() {
}
})
.start();
3)对任意属性做动画
如果想实现给Button一个动画,使其宽度从当前增加到500px。
属性动画要求动画作用的对象提供该属性的get和set方法。
官方对于没有定义属性提供三种解决方案:
- 给你的对象加上get和set方法
- 用一个类包装原始对象,间接提供get和set方法
//通过此代码给一个width属性包装了一层,并提供了get set方法
private static class ViewWrapper{
private View mTarget;
public ViewWrapper(View target){
mTarget = target;
}
public int getWidth(){
return mTarget.getLayoutParams().width;
}
public void setWidth(int width){
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
//使用时只需要操作包装类就可以间接调用了
ViewWrapper wrapper = new ViewWrapper(mButton);
ObjectAnimator.ofInt(wrapper,"width",500).setDuration(1000).start();
- 使用ValueAnimator,监听动画过程,实现属性改变
@override
public void onClick(View v){
if(v == mButton){
handleAnimate(mButton, mButton.getWidth(), 500);
}
}
private void handleAnimate(final View target, final int start, final int end){
ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
//持有一个持有一个Int对象,方便下面估值的时候使用
private IntEvaluator intEvaluator = new IntEvaluator();
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//获得当前动画进度值,整型 0~100
int currentValue = (int) animation.getAnimatedValue();
Log.i(TAG, "currentValue: "+currentValue);
//获得当前进度占整个动画过程比例, 0~1
float fraction = animation.getAnimatedFraction();
//直接调用估值器,通过比例计算出宽度,然后再设给Button
target.getLayoutParams().width = intEvaluator.evaluate(fraction,start,end);
target.requestLayout();
}
});
valueAnimator.setDuration(5000).start();
}
4)布局动画
指在ViewGroup上,给viewgroup增加view时添加一个动画过渡效果。
- 在viewgroup的xml中,使用系统自身的android:animateLayoutChanges="true"
- 使用LayoutAnimationController自定义view过渡效果
注:LayoutAnimationController方式 可通过代码,也可以使用xml配置即
并在viewgroup的xml中使用android:layoutAnimation="@anim/list_anim_layout"
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private LinearLayout content;
private int i=0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn1 = (Button) findViewById(R.id.btn1);
btn1.setOnClickListener(this);
Button btn2 = (Button) findViewById(R.id.btn2);
btn2.setOnClickListener(this);
content = (LinearLayout) findViewById(R.id.content);
AlphaAnimation aa=new AlphaAnimation(0, 1);
aa.setDuration(1000);
ScaleAnimation sa = new ScaleAnimation(0,1,0,1, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
sa.setDuration(1000);
AnimationSet as = new AnimationSet(true);
as.addAnimation(aa);
as.addAnimation(sa);
LayoutAnimationController lac=new LayoutAnimationController(as);
//ORDER_NORMAL-顺序 ORDER_RANDOM-随机, ORDER_REVERSE-反序
lac.setOrder(LayoutAnimationController.ORDER_NORMAL);
lac.setDelay(0.5f);
content.setLayoutAnimation(lac);
//演示LayoutAnimationController自定义view过渡效果
for (int k=0;i<5;i++){
Button button = new Button(this);
button.setText("Button "+i);
content.addView(button);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
//演示系统自带的 android:animateLayoutChanges="true" 过渡效果
case R.id.btn1:
i++;
Button button = new Button(this);
button.setText("Button "+i);
content.addView(button);
break;
case R.id.btn2:
if (i>0){
content.removeViewAt(0);
i--;
}
}
}
}
5)Interpolators(插值器)&TypeEvaluator(估值器)
- 插值器,即随时间流逝的百分比来计算当前属性改变的百分比。系统提供了多种插值器。如AccelerateDecelerateInterpolator就是一个线加速再减速的Interpolator,而BounceInterpolator就是一种可以模拟物理规律,实现反复弹起效果的Interpolator
-
估值器,即根据属性改变的百分比来计算改变后的属性值。
animator.setInterpolator(new BounceInterpolator());
animator.start();
- 可以自定义的插值器,实现TimeInterpolator的getInterpolation方法,对于输入进行不同的输出处理
public class DecelerateAccelerateInterpolator implements TimeInterpolator{
@Override
public float getInterpolation(float input) {
float result;
if (input <= 0.5) {
result = (float) (Math.sin(Math.PI * input)) / 2;
} else {
result = (float) (2 - Math.sin(Math.PI * input)) / 2;
}
return result;
}
}
6)Android5.X SVG矢量动画
Bitmap通过在每个像素点上存储色彩信息来表达图像,而SVG是一个绘图标准。SVG最大的优点是放大不会失真,不需要为不同分辨率设计多套图标。
path标签 | 说明 |
---|---|
M=moveto(M X,Y) | 将画笔移到指定位置,但未绘制 |
L=lineto(L X,Y) | 画直线到指定坐标位置 |
H=horizontal lineto(H X) | 画水平线到指定X坐标位置 |
V=vertical lineto(V Y) | 画垂直线到指定Y坐标位置 |
C=curveto(C X1,Y1,X2,Y2,ENDX,ENDY) | 三阶贝塞尔曲线 |
S=smooth curveto(S X2,Y2,ENDX,ENDY) | 三阶贝塞尔曲线 |
Q=quadratic belzier curve(Q X,Y,ENDX,ENDY) | 二阶贝塞尔曲线 |
T=smooth quadratic belzier curveto(T ENDX,ENDY) | 映射前面路径后的终点 |
A=elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y) | 弧线 |
Z=closepath() | 关闭路径 |
PS:指令大小写均可,大写绝对定位(参照全局坐标系),小写相对定位(参照父坐标系)
- Android中的SVG
Andrid5.X中提供了下面2个新API来帮助支持SVG
1)VectorDrawable
2)AnimatedVectorDrawable(类似胶水,将静态的VectorDrawable和动态的objectAnimator粘合起来) -
示例1
VectorDrawable
~/res/drawable/svg_vector1.xml
objectAnimator
~/res/anim/svg_objanimator1_path1.xml
~/res/anim/svg_objanimator1_path2.xml
AnimatedVectorDrawable
~res/drawable/svg_animted_vector1.xml
Activity
final ImageView img = (ImageView) findViewById(R.id.img);
img.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Drawable drawable = img.getDrawable();
((Animatable)drawable).start();
}
});
- 示例2
android:propertyName="trimPathStart" 此属性就是按照绘制轨迹绘制SVG图像
VectorDrawable
~/res/drawable/svg_vector2.xml
objectAnimator
~/res/anim/svg_objanimator2.xml
AnimatedVectorDrawable
~res/drawable/svg_animted_vector2.xml
7)动画特效
-
灵动菜单
public class MainActivity extends AppCompatActivity {
private ImageView img, img1, img2, img3, img4, img5;
private boolean mFlag = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
img = (ImageView) findViewById(R.id.img);
img1 = (ImageView) findViewById(R.id.img1);
img2 = (ImageView) findViewById(R.id.img2);
img3 = (ImageView) findViewById(R.id.img3);
img4 = (ImageView) findViewById(R.id.img4);
img.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mFlag){
startAnim();
}else{
closeAnim();
}
}
});
}
private void startAnim(){
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(img1,"translationX",200f);
ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(img2,"translationY",200f);
ObjectAnimator objectAnimator3 = ObjectAnimator.ofFloat(img3,"translationX",-200f);
ObjectAnimator objectAnimator4 = ObjectAnimator.ofFloat(img4,"translationY",-200f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(objectAnimator1,objectAnimator2,objectAnimator3,objectAnimator4);
animatorSet.setDuration(1000);
animatorSet.setInterpolator(new BounceInterpolator());
animatorSet.start();
mFlag = false;
}
private void closeAnim(){
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(img1,"translationX",0);
ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(img2,"translationY",0);
ObjectAnimator objectAnimator3 = ObjectAnimator.ofFloat(img3,"translationX",0);
ObjectAnimator objectAnimator4 = ObjectAnimator.ofFloat(img4,"translationY",0);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(objectAnimator1,objectAnimator2,objectAnimator3,objectAnimator4);
animatorSet.setDuration(1000);
animatorSet.setInterpolator(new BounceInterpolator());
animatorSet.start();
mFlag = true;
}
}
8)Activity过渡动画
- Android2.0后可以使用overridePendingtransition(int enterAnim, int exitAnim)。必须写在startActivity或finish后面才能生效。
- Android5.X提供了3种Transition类型(进入,退出,共享元素)
进入退出效果 | 说明 |
---|---|
explode | 分解,从屏幕中间进或出 |
slide | 滑动,从屏幕边缘进或出 |
fade | 淡入淡出 |
~/MainActivity.java
startActivity(new Intent(MainActivity.this,Main2Activity.class), ActivityOptions.makeSceneTransitionAnimation(MainActivity.this).toBundle());
~/MainActivity2.java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
getWindow().setEnterTransition(new Explode());
// getWindow().setEnterTransition(new Slide());
// getWindow().setEnterTransition(new Fade());
setContentView(R.layout.activity_main2);
}
9)帧动画
- 帧动画就是按顺序播放一组预先定义好的图片,使用了Drawable的派生类AnimationDrawable,使用
标签。 - 使用简单,避免使用大图片,会OOM
View animation_view = findViewById(R.id.animation_view);
AnimationDrawable drawable3 = (AnimationDrawable) animation_view.getBackground();
drawable3.start();
10)Lottie动画
json转动画,美术可以介入app动画,因为json也支持热更新动效。支持Android,ReactNative,iOS,Web四端