Android Animation全面总结

1.帧动画Frame-by-frame Animations
2.补间动画Tweened Animations
3.属性动画 Property Animations(自定义Interpolator及Evaluator)
4.ViewPropertyAnimator
5.LayoutAnimationController
6.LayoutTransition
7.转场动画 Transition
8.ViewAnimationUtil
9.ArcMotion
10.AnimatedVectorDrawable
11.Ripple点击效果
12.SpringAnimation
13.FlingAnimation

帧动画与补间动画在android3.0前就存在,针对View
而属性动画在android3.0后出现,不仅仅针对View,适用于任何对象的属性(包含有相应的getter,setter)。

帧动画

由一系列图片构成的一帧一帧的动画,和以前放电影的原理相似。但要注意如果图片过多过大会出现OOM,应用场景如:页面加载动画,下拉刷新动画等。

特别注意,AnimationDrawable的start()方法不能在Activity的onCreate方法中调运,因为AnimationDrawable还未完全附着到window上,所以最好的调运时机是onWindowFocusChanged()方法中。
使用方法:

res/drawable/frame_animation.xml

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true"> //只播放一次,默认false循环播放
    <item
        android:drawable="@drawable/drawable_0"
        android:duration="100" />
    <item
        android:drawable="@drawable/drawable_1"
        android:duration="100" />
    <item
        android:drawable="@drawable/drawable_2"
        android:duration="100" />
</animation-list>

<ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/frame_animation"
        />


ImageView imageView= (ImageView) findViewById(R.id.imageView);
//imageView.setImageResource(R.drawable.frame_animation);//也可通过xml文件的src设置。
AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getDrawable();
animationDrawable .start();

补间动画

包括以下几种:所在包为android.view.animation(从包名可以看出补间动画是针对View的)。
都继承自 android.view.animation.Animation抽象类。
优点:利用率高,不用担心内存泄露问题,

  1. AlphaAnimation <alpha>
  2. RotateAnimation <rotate>
  3. ScaleAnimation <scale>
  4. TranslationAnimation <translate>
  5. AnimationSet <set> 将以上动画组合

res/anim/alpha.xml

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:duration="1000"
    android:fromAlpha="1.0"//渐渐消失
    android:toAlpha="0.0" />4
Animation animation = AnimationUtils.loadAnimation(this, R.anim.alpha);

或者通过代码来实现:
AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f, 0.0f);
alphaAnimation.setDuration(500);
alphaAnimation.setInterpolator(new AccelerateInterpolator());

res/anim/scale.xml

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:duration="@android:integer/config_longAnimTime"
    android:fillAfter="true"
    android:pivotX="50%"
    android:pivotY="50%"
    android:fromXScale="1.0"
    android:toXScale="1.5"
    android:fromYScale="1.0"
    android:toYScale="1.5">
</scale>

Animation animation = AnimationUtils.loadAnimation(this, R.anim.scale);
view.startAnimation(animation);

或者通过代码来实现:
ScaleAnimation animation = new ScaleAnimation(1.0f, 1.5f, 1.0f, 1.5f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animation.setDuration(getResources().getInteger(android.R.integer.config_longAnimTime));
animation.setInterpolator(new AccelerateInterpolator());
view.startAnimation(animation);

res/anim/rotate.xml

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:interpolator="@android:anim/anticipate_interpolator"
    android:fromDegrees="0"
    android:toDegrees="360"
    android:pivotY="50%"
    android:pivotX="50%"
    android:drawable="@mipmap/ic_launcher">//当前anim可以设置为ImageView的src背景,该Drawable为RotateDrawable,代码中通过ImageView的getDrawable()获得,并设置Level来达到控件不动,背景旋转的目的。
</rotate>

Animation animation = AnimationUtils.loadAnimation(this, R.anim.rotate);
view.startAnimation(animation);

或者通过代码来实现:
RotateAnimation animation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animation.setDuration(300);
animation.setInterpolator(this, android.R.anim.anticipate_interpolator);
view.startAnimation(animation);

res/anim/translate.xml

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXDelta="0%"
    android:toXDelta="100%"
    android:fromYDelta="0%"
    android:toYDelta="100%"
    android:duration="500"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    >
</translate>

Animation animation = AnimationUtils.loadAnimation(this, R.anim.translate);
view.startAnimation(animation);
或者通过代码来实现:
TranslateAnimation animation = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, 0, Animation.RELATIVE_TO_PARENT, 1.0f,Animation.RELATIVE_TO_PARENT, 0, Animation.RELATIVE_TO_PARENT, 1.0f);
animation.setDuration(1000);
view.startAnimation(animation);

xml文件由<set>标签包裹
<set xmlns:android="http://schemas.android.com/apk/res/android"  android:shareInterpolator="true">
  <rotate/>...
  <scale/>...
  <translate/>...
  <alpha/>...
  <set></set>
</set>
set中的元素可以通过android:startOffset="1000"来设置动画播放的延迟。

Animation animation = AnimationUtils.loadAnimation(this, R.anim.set);
view.startAnimation(animation);


或者通过代码来实现:
AnimationSet animationSet = new AnimationSet(shareInterpolater);//shareInterpolater:是否将设置的interpolater应用到Set中的每一个动画。

AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f, 0.0f);
alphaAnimation.setDuration(500);
alphaAnimation.setInterpolator(new AccelerateInterpolator());

animationSet.addAnimation(alphaAnimation);
imageView.startAnimation(animationSet);

属性动画

所在包为android.animator下。Android3.0后才出现。
继承关系


image.png

AnimatorSet 对应xml中的<set>
ValueAnimator 对应xml中的 <animator>
ObjectAnimator 对应xml中的<objectAnimator>

Activity onStop时需要手动停止动画,不然会出现Activity无法释放而内容泄露。

ObjectAnimator

直接对对象的属性值进行改变操作,从而实现动画效果

ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0.0f).setDuration(500).start();
ObjectAnimator.ofFloat(view, "scaleX", 0.0f, 1.0f).setDuration(300).start();
ObjectAnimator.ofFloat(view, "scaleY", 0.0f, 2.0f).setDuration(300).start();
ObjectAnimator.ofFloat(view, "translationX", 0, 100).setDuration(300).start();//移动的像素
ObjectAnimator.ofFloat(view, "translationY", 0, 100).setDuration(300).start();

AnimatorSet set = new AnimatorSet();
set.playTogether(animA,animB);
set.setDuration(3000);
set.start();

通过xml定义
res/animator/alpha.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:ordering="together">
    <objectAnimator
        android:duration="300"
        android:valueFrom="1.0"
        android:valueTo="0.0"
        android:propertyName="alpha"
        android:valueType="floatType"/>
</set>

Animator animator = AnimatorInflater.loadAnimator(context, R.animator.alpha);
animator.setTarget(view);
animator.start();

res/animator/rotation

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:ordering="together">
    <objectAnimator
        android:duration="300"
        android:valueFrom="0"
        android:valueTo="360"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:repeatCount="3"
        android:repeatMode="reverse"
        android:startOffset="2000"
        android:propertyName="rotation"
        android:valueType="floatType"/>
</set>
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.rotate);
animator.setTarget(view);
animator.start();
ValueAnimator

可以理解为一个数值生成器,根据duration, interpolator, evaluator,生成从开始数值到结束数值的中间数值。获得中间的值后再手动的设置的相应的布局上,实现无间的动画效果。

300毫秒内得到从0到10中间的数值:

ValueAnimator animator = ValueAnimator.ofInt(0, 10).setDuration(300);
animator.addUpdateListener(animation -> {
    int animatedValue = (int) animation.getAnimatedValue();
    Log.e(TAG, "onAnimationUpdate: "+animatedValue );
});
animator.start();

打印结果:(默认Interpolator不是线性的,而是AccelerateDecelerateInterpolator)
onAnimationUpdate: 0
onAnimationUpdate: 0
onAnimationUpdate: 0
onAnimationUpdate: 0
onAnimationUpdate: 0
onAnimationUpdate: 1
onAnimationUpdate: 1
onAnimationUpdate: 2
onAnimationUpdate: 3
onAnimationUpdate: 4
onAnimationUpdate: 5
onAnimationUpdate: 6
onAnimationUpdate: 6
onAnimationUpdate: 7
onAnimationUpdate: 8
onAnimationUpdate: 8
onAnimationUpdate: 9
onAnimationUpdate: 9
onAnimationUpdate: 9
onAnimationUpdate: 10
属性动画的两个监听器:
/** 
 * 监听器一:监听动画变化时的实时值 
 * 添加方法为:public void addUpdateListener(AnimatorUpdateListener listener)  
 */  
public static interface AnimatorUpdateListener {  
    void onAnimationUpdate(ValueAnimator animation);  
}  

/** 
 * 监听器二:监听动画变化时四个状态 
 * 添加方法为:public void addListener(AnimatorListener listener) 
 */  
public static interface AnimatorListener {  
    void onAnimationStart(Animator animation);  
    void onAnimationEnd(Animator animation);  
    void onAnimationCancel(Animator animation);  
    void onAnimationRepeat(Animator animation);  
}  

/** 
 * 移除AnimatorUpdateListener 
 */  
void removeUpdateListener(AnimatorUpdateListener listener);  
void removeAllUpdateListeners();  
 /** 
  * 移除AnimatorListener 
  */  
void removeListener(AnimatorListener listener);  
void removeAllListeners(); 


AnimatorListenerAdapter AnimatorListener的代理,默认空实现了所有的AnimatorListener的接口。

//只需要实现想要监听的方法即可。
animator1.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationStart(Animator animation) {
        super.onAnimationStart(animation);
    }
});

PropertyValueHolder

针对多个属性值同时计算的情况,可以通过PropertyValueHolder来实现

            PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", 30, 600);
            PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", 30, 600);

            ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(x, y);
            animator.addUpdateListener(animation -> {
                Integer animatedValue = animation.getAnimatedValue();//看源码可知与x1的值是相同的,取的是PropertyValuesHolder[0].getAnimatedValue();而PropertyValuesHolder[0]是上面的x
                Integer x1 = animation.getAnimatedValue("x");
                Integer y1 = animation.getAnimatedValue("y");
                //再设置mPoint的X,Y坐标并invalidate();
            });
            animator1.setDuration(1000);
            animator1.start();
KeyFrame

通过更加精准的设置每一帧的属性值,来实现动画。

            Keyframe kf0 = Keyframe.ofInt(0, 400);
            Keyframe kf1 = Keyframe.ofInt(0.25f, 200);
            Keyframe kf2 = Keyframe.ofInt(0.5f, 400);
            Keyframe kf4 = Keyframe.ofInt(0.75f, 100);
            Keyframe kf3 = Keyframe.ofInt(1f, 500);
            PropertyValuesHolder valuesHolder = PropertyValuesHolder.ofKeyframe("width", kf0, kf1, kf2, kf4, kf3);
            ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, valuesHolder);
            animator.setDuration(2000);
            animator.start();
TypeEvaluator

实现小球X轴及Y轴的同时移动。此时用IntEvaluator无法对两个值进行计算,所以需要自定义Evaluator

class PointEvaluator implements TypeEvaluator<Point>{
    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {
        int x = (int) (startPoint.x + fraction * (endPoint.x - startPoint.x));
        int y = (int) (startPoint.y + fraction * (endPoint.y - startPoint.y));
        return new Point(x, y);
    }
}

自定义View的onDraw()中,通过mPoint设置小球的当前坐标:
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawCircle(mPoint.x, mPoint.y, 30, mPaint);
}

自定义View中通过start()方法,以动画的形式更改mPoint的值,然后invalidate(),实现动画效果
public void start() {
    final ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(),  new Point(30, 30), new Point(600, 600));
    animator.setDuration(2000);
    animator.setRepeatCount(ValueAnimator.INFINITE);
    animator.setRepeatMode(ValueAnimator.REVERSE);
    animator.setInterpolator(new LinearInterpolator());
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mPoint = (Point)animation.getAnimatedValue();
            invalidate();
        }
    });
    animator.start();
}
Interpolator

TimeInterpolator接口:

public interface TimeInterpolator {
    /**
     * Maps a value representing the elapsed fraction of an animation to a value that represents
     * the interpolated fraction. This interpolated value is then multiplied by the change in
     * value of an animation to derive the animated value at the current elapsed animation time.
     *
     * @param input A value between 0 and 1.0 indicating our current point
     *        in the animation where 0 represents the start and 1.0 represents
     *        the end //input 从0到1匀速变化
     * @return The interpolation value. This value can be more than 1.0 for
     *         interpolators which overshoot their targets, or less than 0 for
     *         interpolators that undershoot their targets.//通过计算,返回值可以为大于1或小于1
     */
    float getInterpolation(float input);
}
TimeInterpolator子类.png

LinearInterpolator:

public float getInterpolation(float input) {
        return input;//均匀的线性值
}

AccelarateInterpolator:

    public float getInterpolation(float input) {
        if (mFactor == 1.0f) {
            return input * input;// y = x^2 会越来越快
        } else {
            return (float)Math.pow(input, mDoubleFactor);//大于1的话,看图可知,factor越大,开始就越慢,结束就越快。
        }
    }
image.png

数学好的可以自定义Evaluator及Interpolator
参考: https://www.cnblogs.com/wondertwo/p/5327586.html

ViewPropertyAnimator

Android3.1 Api12 出现。优点,对之前的属性动画的优化,一方面更加易读(链式调用),另一方面减少了invalidate()的次数,并且自动播放。

                   v.animate()
                    .alpha(0.5f)
                    .rotation(360)
                    .scaleX(1.5f)
                    .scaleY(1.5f)
                    .translationX(50)
                    .translationY(50)
                    .withLayer()//requires API level 16 开启硬件加速
                    .withStartAction(() -> Log.e(TAG, "startAction"))//requires API level 16 onAnimationStart回调前执行
                    .withEndAction(() -> Log.e(TAG, "endAction"))//requires API level 16 onAnimationEnd回调后执行
                    .setListener(new Animator.AnimatorListener() {
                        @Override
                        public void onAnimationStart(Animator animation) {
                            Log.e(TAG, "onAnimationStart: ");
                        }
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            Log.e(TAG, "onAnimationEnd: ");
                        }
                        @Override
                        public void onAnimationCancel(Animator animation) {
                            Log.e(TAG, "onAnimationCancel: ");
                        }
                        @Override
                        public void onAnimationRepeat(Animator animation) {
                            Log.e(TAG, "onAnimationRepeat: ");
                        }
                    })
                    .setDuration(5000);

控制台输出结果:
startAction
onAnimationStart: 
onAnimationEnd: 
endAction
StateListAnimator

https://www.cnblogs.com/android-blogs/p/5816965.html

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- the pressed state; increase x and y size to 150% -->
    <item android:state_pressed="true">
        <set>
            <objectAnimator android:propertyName="scaleX"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1.5"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1.5"
                android:valueType="floatType"/>
        </set>
    </item>
    <!-- the default, non-pressed state; set x and y size to 100% -->
    <item android:state_pressed="false">
        <set>
            <objectAnimator android:propertyName="scaleX"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1"
                android:valueType="floatType"/>
        </set>
    </item>
</selector>

<Button android:stateListAnimator="@xml/animate_scale"
        ... />

LayoutAnimationController GridLayoutAnimationController

LayoutAnimation可以用在任何ViewGroup上

通过xml实现:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layoutAnimation="@anim/layout_animation"//指定LayoutAnimation
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v7.widget.RecyclerView>
</android.support.constraint.ConstraintLayout>

res/anim/layout_animation.xml

<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:animation="@anim/item_animation"
    android:animationOrder="reverse"
    android:delay="0.2"
    android:interpolator="@android:anim/decelerate_interpolator"
    />

res/anim/item_animation.xml

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="400">
    <translate
        android:fromYDelta="-20%"
        android:toYDelta="0"
        android:interpolator="@android:anim/decelerate_interpolator"
        />
    <alpha
        android:fromAlpha="0"
        android:toAlpha="1"
        android:interpolator="@android:anim/decelerate_interpolator"
        />
    <scale
        android:fromXScale="105%"
        android:fromYScale="105%"
        android:toXScale="100%"
        android:toYScale="100%"
        android:pivotX="50%"
        android:pivotY="50%"
        android:interpolator="@android:anim/decelerate_interpolator"
        />
</set>

而在Activity中,只需要常规设置RecyclerView即可。

        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(new ReAdapter());
3.gif
通过代码实现:
        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(new ReAdapter());

        LayoutAnimationController controller = new LayoutAnimationController(AnimationUtils.loadAnimation(this, R.anim.item_animation));
        //也可以通过此方法获得
        //LayoutAnimationController controller = AnimationUtils.loadLayoutAnimation(this, R.anim.layout_animation);
        controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
        controller.setDelay(0.2f);//前面还没结束,后面已经开始,实现连续
        recyclerView.setLayoutAnimation(controller);
        recyclerView.startLayoutAnimation();//貌似不加这句动画也会自动实现

效果:


3.gif

在数据改变时,再执行LayoutAnimation

    ArrayList list = new ArrayList<String>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main4);
        initData();
        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(new ReAdapter());

        Observable.timer(3, TimeUnit.SECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(aLong -> {
                    list.add(0, "added");
                    updateLayout(recyclerView);//3秒后加了条数据,执行动画
                });
    }

    private void updateLayout(RecyclerView recyclerView) {
        LayoutAnimationController controller = AnimationUtils.loadLayoutAnimation(this, R.anim.layout_animation);
        controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
        recyclerView.setLayoutAnimation(controller);
        recyclerView.getAdapter().notifyDataSetChanged();
        recyclerView.scheduleLayoutAnimation();//没有该操作,同样可以执行动画。
    }

    private void initData() {
        for (char i = 'A'; i <= 'z'; i++) {
            list.add("character  " + i);
        }
    }

效果:

3.gif
将LinearLayoutManager改为GridLayoutManager
        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new GridLayoutManager(this, 4));
        recyclerView.setAdapter(new ReAdapter());

        //LayoutAnimationController controller = new LayoutAnimationController(AnimationUtils.loadAnimation(this, R.anim.translate));
        LayoutAnimationController controller = AnimationUtils.loadLayoutAnimation(this, R.anim.layout_animation);
        controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
        controller.setDelay(0.1f);
        recyclerView.setLayoutAnimation(controller);
        recyclerView.startLayoutAnimation();

效果:


3.gif
如果将LayoutAnimationController改为GridLayoutAnimationController 会出现异常
        GridLayoutAnimationController controller = new GridLayoutAnimationController(AnimationUtils.loadAnimation(this, R.anim.item_animation));
        controller.setDirection(GridLayoutAnimationController.DIRECTION_LEFT_TO_RIGHT);
        controller.setRowDelay(0.1f); 
        controller.setColumnDelay(0.1f);
        recyclerView.setLayoutAnimation(controller);
        recyclerView.startLayoutAnimation();

java.lang.ClassCastException: android.view.animation.LayoutAnimationController$AnimationParameters cannot be cast to android.view.animation.GridLayoutAnimationController$AnimationParameters

解决方法:
自定义RecyclerView

package com.example.gy.myapplication;

import android.content.Context;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.GridLayoutAnimationController;

public class GridRecyclerView extends RecyclerView {
 
    /** @see View#View(Context) */
    public GridRecyclerView(Context context) { super(context); }
 
    /** @see View#View(Context, AttributeSet) */
    public GridRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); }
 
    /** @see View#View(Context, AttributeSet, int) */
    public GridRecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); }
 
    @Override
    protected void attachLayoutAnimationParameters(View child, ViewGroup.LayoutParams params,
                                                   int index, int count) {
        final LayoutManager layoutManager = getLayoutManager();
        if (getAdapter() != null && layoutManager instanceof GridLayoutManager){
 
            GridLayoutAnimationController.AnimationParameters animationParams =
                    (GridLayoutAnimationController.AnimationParameters) params.layoutAnimationParameters;
 
            if (animationParams == null) {
                // If there are no animation parameters, create new once and attach them to
                // the LayoutParams.
                animationParams = new GridLayoutAnimationController.AnimationParameters();
                params.layoutAnimationParameters = animationParams;
            }
 
            // Next we are updating the parameters
 
            // Set the number of items in the RecyclerView and the index of this item
            animationParams.count = count;
            animationParams.index = index;
 
            // Calculate the number of columns and rows in the grid
            final int columns = ((GridLayoutManager) layoutManager).getSpanCount();
            animationParams.columnsCount = columns;
            animationParams.rowsCount = count / columns;
 
            // Calculate the column/row position in the grid
            //animationParams.column与animationParams.row这两个必需要设置,不然不会出效果。
            //column及row的值与item的位置,及动画时长,delay时长计算出当前item开始动画的时间,并应用到item view。
            final int invertedIndex = count - 1 - index;
            animationParams.column = columns - 1 - (invertedIndex % columns);
            animationParams.row = animationParams.rowsCount - 1 - invertedIndex / columns;
            //上面参考其他人的方法,本人觉得下面的算法与上面的是一样的,更加简单。
            //animationParams.column = index % columns;//个人认为这样和上面的结果是一样的
            //animationParams.row = index/columns;//如果是固定值,整个一列的动画同时进行
 
        } else {
            // Proceed as normal if using another type of LayoutManager
            super.attachLayoutAnimationParameters(child, params, index, count);
        }
    }
}

修改布局

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.gy.myapplication.GridRecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </com.example.gy.myapplication.GridRecyclerView>
</android.support.constraint.ConstraintLayout>

最终效果:和上面还是有区别的


3.gif
我们来修改上面自定义的GridRecyclerView,来加深理解AnimationParams的含义
  • AnimationParams.count,父容器中包含需要绑定该AnimationParams的Item的个数。(不是RecyclerView的所有Item的个数)
  • AnimationParams.index Item的下标位置。
  • AnimationParams.columnsCount RecyclerView设置列的个数
  • AnimationParams.rowsCount RecyclerView设置行的个数
  • AnimationParams.column 当前Item在第几列
  • AnimationParams.row 当前Item在第几行

每个ITEM动画delay计算方式(来自引用)

itemAnimationDuration = 300ms
rowDelay              = 10% (30ms)
columnDelay           = 10% (30ms)
direction             = top_to_bottom|left_to_right

 +------->
 | +---+---+---+
 | | 0 | 1 | 2 |
 | +---+---+---+
 V | 3 | 4 | 5 |
   +---+---+---+
   | 6 | 7 | 8 |
   +---+---+---+
   ROW   COLUMN   sFinal
0 = 0*30 + 0*30 = 0ms
1 = 0*30 + 1*30 = 30ms
2 = 0*30 + 2*30 = 60ms
3 = 1*30 + 0*30 = 30ms
4 = 1*30 + 1*30 = 60ms
5 = 1*30 + 2*30 = 90ms
6 = 2*30 + 0*30 = 60ms
7 = 2*30 + 1*30 = 90ms
8 = 2*30 + 2*30 = 120m

animation order by delay
+-----+-----+-----+
|  0  | 30  | 60  |
+-----+-----+-----+
| 30  | 60  | 90  |
+-----+-----+-----+
| 60  | 90  | 120 |
+-----+-----+-----+

将column的值改为固定值,则同一行中的所有Item都将以相同的时间开始。

        animationParams.column = 1;//如果是固定值,整个一行的动画同时进行
        animationParams.row = index/columns;

效果:


row.gif

将row的值改为固定值,则同一列中的所有Item都将以相同的时间开始

        animationParams.column = index % columns;
        animationParams.row = 1;//如果是固定值,整个一列的动画同时进行

效果:


colum.gif

LayoutTransition

LayoutTransition 是Android 3.0 API Level 11 才出现的
当ViewGroup中有View添加、删除、隐藏、显示的时候才会体现出来,即调用 ViewGroup的addView()、removeView(), setVisibility()时才会有动画效果。

目前系统中支持以下5种状态变化,应用程序可以为下面任意一种状态设置自定义动画:
1、APPEARING:容器中出现一个视图。
2、DISAPPEARING:容器中消失一个视图。
3、CHANGING:布局改变导致某个视图随之改变,例如调整大小,但不包括添加或者移除视图。
4、CHANGE_APPEARING:其他视图的出现导致某个视图改变。
5、CHANGE_DISAPPEARING:其他视图的消失导致某个视图改变。

我们先来看看系统默认的LayoutTransition
<?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="vertical" >
    <Button
        android:id="@+id/main_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="添加控件"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:animateLayoutChanges="true"//开启了LayoutTransition
        android:id="@+id/main_container"
        android:orientation="vertical"/>
</LinearLayout>
public class LayoutTransitionActivity extends AppCompatActivity implements View.OnClickListener {

    private LinearLayout mContainer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_layout_transition);
        mContainer = (LinearLayout) findViewById(R.id.main_container);
        findViewById(R.id.main_btn).setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        Button btn = new Button(this);
        btn.setText("移除自己");
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mContainer.removeView(v);
            }
        });
        mContainer.addView(btn, 0, new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
    }
}

看下面的动图得知系统默认的动画为:
APPEARING:逐渐显现
DISAPPEARING:逐渐消失
CHANGE_APPEARING:滑动
CHANGE_DISAPPEARING:滑动


2.gif
接下来我们看看如何自定义LayoutTransition
        LayoutTransition transition = new LayoutTransition();
        mContainer.setLayoutTransition(transition);

        //APPEARING
        Animator appearAnim = ObjectAnimator.ofFloat(null, "rotationX", 90f, 0)//X轴翻转进入
                .setDuration(transition.getDuration(LayoutTransition.APPEARING));
        transition.setAnimator(LayoutTransition.APPEARING, appearAnim);

        //DISAPPEARING
        Animator disappearAnim = ObjectAnimator.ofFloat(null, "rotationX", 0, 90f)//X轴翻转消失
                .setDuration(transition.getDuration(LayoutTransition.DISAPPEARING));
        transition.setAnimator(LayoutTransition.DISAPPEARING, disappearAnim);

        //CHANGE_APPEARING(这其实就是系统默认的CHANGE_APPEARING)
        PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1);
        PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1);
        PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 1);
        PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1);
        PropertyValuesHolder pvhScaleXa = PropertyValuesHolder.ofFloat("scrollX", 0f, 1f);
        PropertyValuesHolder pvhScaleYa = PropertyValuesHolder.ofFloat("scrollY",0f, 1f);

        ObjectAnimator changeIn = ObjectAnimator.ofPropertyValuesHolder(this, pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScaleXa, pvhScaleYa)
                .setDuration(transition.getDuration(LayoutTransition.CHANGE_APPEARING));
        transition.setAnimator(LayoutTransition.CHANGE_APPEARING, changeIn);

        //CHANGE_DISAPPEARING
        PropertyValuesHolder pvhSlide = PropertyValuesHolder.ofFloat("y", 0, 1);//滑动
        PropertyValuesHolder pvhScaleY = PropertyValuesHolder.ofFloat("scaleY", 1f, 0.5f, 1f);//缩小一半
        PropertyValuesHolder pvhScaleX = PropertyValuesHolder.ofFloat("scaleX", 1f, 0.5f, 1f);//缩小一半

        Animator changingDisappearAnim = ObjectAnimator.ofPropertyValuesHolder(this, pvhSlide, pvhScaleY, pvhScaleX);
        changingDisappearAnim.setDuration(transition.getDuration(LayoutTransition.CHANGE_DISAPPEARING));
        transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, changingDisappearAnim);

APPEARING:X轴翻转进入
DISAPPEARING:X轴翻转消失
CHANGE_APPEARING:滑动
CHANGE_DISAPPEARING:滑动

效果:
2.gif

转场动画

(1).overridePendingTransition(int enterAnim, int exitAnim);

在startActivity()或finish()后调用的,只能设置B页面的进入动画,和A页面的退出动画:

    //CurrentActivity点击Button
    public void onClick(View view) {
        startActivity(new Intent(this, TargetActivity.class));
        overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
    }

    //TargetActivity点击Button
    public void back(View view) {
        finish();
        overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
    }
override_pending_transition.gif
(2). Theme中设置android:windowAnimationStyle
  • ActivityAnimation
    将A页面和B页面的进入退出动画统一设置,应用到所有页面。
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="android:windowAnimationStyle">@style/WindowAnimationStyle</item>
    </style>

    <style name="WindowAnimationStyle" parent="@android:style/Animation.Activity">
        <item name="android:activityOpenEnterAnimation">@anim/activity_open_enter</item>//打开B页面B页面进入动画
        <item name="android:activityOpenExitAnimation">@anim/activity_open_exit</item>//打开B页面A页面的退出动画
        <item name="android:activityCloseExitAnimation">@anim/activity_close_exit</item>//关闭B页面B页面退出动画
        <item name="android:activityCloseEnterAnimation">@anim/activity_close_enter</item>//关闭B页面A页面进入动画
    </style>

activity_open_enter.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime">
    <translate
        android:fromXDelta="100%"
        android:toXDelta="0" />
</set>

activity_open_exit.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime">
    <scale
        android:fromXScale="1"
        android:toXScale="0.9"
        android:fromYScale="1"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toYScale="0.9"/>
    <translate
        android:fromXDelta="0"
        android:toXDelta="-50%"
        />
    <alpha
        android:fromAlpha="1.0"
        android:toAlpha="0.0"/>
</set>

activity_close_enter.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime">
    <scale
        android:fromXScale="0.9"
        android:toXScale="1"
        android:fromYScale="0.9"
        android:toYScale="1"
        android:pivotY="50%"
        android:pivotX="50%"
        />
    <translate
        android:fromXDelta="-50%"
        android:toXDelta="0" />
    <alpha
        android:fromAlpha="0.0"
        android:toAlpha="1.0"/>
</set>

activity_close_exit.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime">
    <translate android:fromXDelta="0"
        android:toXDelta="100%"/>
</set>

正常的效果为(Button文字没有改,只看效果即可, 下同):


normal.gif

但是如果以上面1的例子为基础,运行后是没有效果的。原因是overridePendingTransition的优先权更高。尝试将overridePendingTransition()注释掉:

    //CurrentActivity点击Button
    public void onClick(View view) {
        startActivity(new Intent(this, TargetActivity.class));
        //overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
    }

    //TargetActivity点击Button
    public void back(View view) {
        finish();
        overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
    }

效果为,返回时的overridePendingTransition覆盖了Style中的android:windowAnimationStyle:


priority.gif
  • WindowAnimation
    我们在定义windowAnimationStyle时 也会看到有这么几个属性:
        <item name="android:windowEnterAnimation"></item>
        <item name="android:windowExitAnimation"></item>
        <item name="android:windowShowAnimation"></item>
        <item name="android:windowHideAnimation"></item>
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowAnimationStyle">@style/WindowAnimationStyle</item>
    </style>

    <style name="WindowAnimationStyle">
        <item name="android:windowEnterAnimation">@anim/activity_open_enter</item>
        <item name="android:windowExitAnimation">@anim/activity_close_exit</item>
    </style>

效果:


window.gif
  • 以上ActivityAnimation与WindowAnimation都可以通过代码实现:
    目前没找到什么规律,不推荐使用。以下将设置结果列出来,以供参考。
    getWindow().setWindowAnimations(R.style.WindowAnimationStyle);
    activityAnimation:
    <style name="WindowAnimationStyle" parent="@android:style/Animation.Activity">
        <item name="android:activityOpenEnterAnimation">@anim/activity_open_enter</item>
        <item name="android:activityOpenExitAnimation">@anim/activity_open_exit</item>
        <item name="android:activityCloseEnterAnimation">@anim/activity_close_enter</item>
        <item name="android:activityCloseExitAnimation">@anim/activity_close_exit</item>
    </style>

A设置B不设置效果:(与我想的效果不一样)


a.gif

B设置A不设置效果:(与我想的效果不一样)


b.gif

windowAnimation:

    <style name="WindowAnimationStyle">
        <item name="android:windowEnterAnimation">@anim/activity_open_enter</item>
        <item name="android:windowExitAnimation">@anim/activity_close_exit</item>
        <!--<item name="android:windowHideAnimation">@anim/activity_open_exit</item>-->
    </style>

A设置B不设置效果:(与我想的效果不一样)


bb.gif

B设置A不设置效果:(效果一样, 但是会闪一下)


aa.gif
  • ActivityAnimation与WindowAnimation:

一. windowAnimation包括 windowEnterAnimationwindowExitAnimation, windowHideAnimationwindowShowAnimation不知道干什么的。以上设置后不起作用。且只能控制下一个Window的转场//TODO
而activityAnimation包括activityOpenEnterAnimation, activityOpenExitAnimation, activityCloseEnterAnimation, activityCloseExitAnimation四种,用来控制两个页面的转场动画。

二.经测试,windowAnimation与activityAnimation不冲突,都会执行。

三.activityAnimation只作用在Activity上, 而windowAnimation除了可以作用在Activity上(因为每个Acitivity中包含一个PhoneWindow),更重要的一点,还可以作用在带有Window的Dialog上。

  • AlertDialog
    第一种方法:不起作用
  <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:alertDialogTheme">@style/DialogTheme</item>
  </style>

  <style name="DialogTheme" parent="ThemeOverlay.AppCompat.Dialog.Alert">
        <item name="android:windowAnimationStyle">@style/DialogStyle</item>
    </style>

    <style name="DialogStyle" parent="Animation.AppCompat.Dialog">
        <item name="android:windowEnterAnimation">@anim/activity_open_enter</item>
        <item name="android:windowExitAnimation">@anim/activity_close_exit</item>
        <item name="android:windowShowAnimation">@anim/scale_in</item>//没有什么效果@TODO
        <item name="android:windowHideAnimation">@anim/scale_out</item>//没有什么效果@TODO
    </style>

        AlertDialog alertDialog = new AlertDialog.Builder(this)
                .setTitle("fuck")
                .setMessage("message")
                .create();
        alertDialog.show();

第二种方法:将Style设置到Dialog中Attributes的windowAnimations属性中。

        AlertDialog alertDialog = new AlertDialog.Builder(this)
                .setTitle("fuck")
                .setMessage("message")
                .create();
        WindowManager.LayoutParams attributes = alertDialog.getWindow().getAttributes();
        attributes.windowAnimations = R.style.DialogStyle;
        alertDialog.getWindow().setAttributes(attributes);
        alertDialog.show();

第三种方法:创建Dialog时将Theme传进去。

new AlertDialog.Builder(this, R.style.DialogTheme)
                .setTitle("fuck")
                .setMessage("message")
                .show();
alertdailog1.gif
(3). ActivityOptions,ActivityOptionsCompat

Android4.1 API16后可以使用ActivityOptions() 和startActivity(Intent, Bundle);
而ActivityOptionsCompat是 android.support.v4.app兼容包中的类,支持兼容老版。
added in API level 16

首先设置(如果当前应用的主题是Material主题,AppCompat主题根据Android 版本,加载不同的主题,以下几种方法不设置也没问题, 自动设置为true.)

<style name="AppTheme" parent="Theme.AppCompat">
    <item name="android:windowContentTransitions">true</item>
</style>

或在Activity onCreate()中:
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);

ActivityOptionsCompat.makeCustomAnimation(context, enterResId, exitResId)
这个与overridePendingTransition(int enterAnim, int exitAnim);一样,只能设置B页面的进入动画,和A页面的退出动画

ActivityOptionsCompat options = ActivityOptionsCompat.makeCustomAnimation(context, R.anim.activity_open_enter, R.anim.activity_open_exit);
Intent intent = new Intent(context, MeiziDetailActivity.class);
intent.putExtra("url", url);
ActivityCompat.startActivity(context, intent, options.toBundle());

但是还有一点,overridePendingTransition()在finish()后也能调用。而ActivityOptionsCompat就没办法控制返回时的动画了。(没有相关的API)

效果:返回时没效果


a.gif

ActivityOptionsCompat.makeScaleUpAnimation(view, startX, startY, initialWidth, initialHeight)
实现点哪,新页面就从哪儿展开。
点击某个View,打开B页面,B页面会以相对于View的某个位置放大展开。

但是无法自定义动画时长,退出动画也无法设置。

        ActivityOptionsCompat options = ActivityOptionsCompat.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
        Intent intent = new Intent(context, MeiziDetailActivity.class);
        intent.putExtra("url", url);
        ActivityCompat.startActivity(context, intent, options.toBundle());

效果:


b.gif

ActivityOptionsCompat.makeThumbnailScaleUpAnimation(View source, Bitmap thumbnail, int startX, int startY)
切换时,会先显示一个Bitmap,再过渡到新的页面。这里为了演示,用的是系统的启动图标。

        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher);
        ActivityOptionsCompat options = ActivityOptionsCompat.makeThumbnailScaleUpAnimation(view, bitmap, view.getWidth()/2, view.getHeight()/2);
        Intent intent = new Intent(context, MeiziDetailActivity.class);
        intent.putExtra("url", url);
        ActivityCompat.startActivity(context, intent, options.toBundle());

效果:不是特别明显,因为动画时间无法自定义。


c.gif

ActivityOptionsCompat makeClipRevealAnimation(View source, int startX, int startY, int width, int height)
跳转新页面时,新页面的剪切范围逐渐扩大,直到完全显示。

        ActivityOptionsCompat options = ActivityOptionsCompat.makeClipRevealAnimation(view, 0, 0, view.getWidth(), view.getHeight());
        Intent intent = new Intent(context, MeiziDetailActivity.class);
        intent.putExtra("url", url);
        ActivityCompat.startActivity(context, intent, options.toBundle());
d.gif

ActivityOptionsCompat makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName)

ActivityOptionsCompat makeSceneTransitionAnimation(Activity activity,
Pair<View, String>... sharedElements)

涉及共享元素之间的转换。

        //view是RecyclerView的ItemView
        ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(context, view, "share");
        Intent intent = new Intent(context, MeiziDetailActivity.class);
        intent.putExtra("url", url);
        ActivityCompat.startActivity(context, intent, options.toBundle());
e.gif
(4). WindowTransition

与WindowAnimation不同。包括ContentTransition与Share Element Transition.

   
        // 未设置setReturnTransition()默认和setEnterTransition一样    
        getWindow().setEnterTransition(transition);//android.R.styleable#Window_windowEnterTransition
        //   未设置setReenterTransition()默认和setExitTransition一样        
        getWindow().setExitTransition(transition);//android.R.styleable#Window_windowExitTransition
        getWindow().setReturnTransition(transition);//android.R.styleable#Window_windowReturnTransition
        getWindow().setReenterTransition(transition);//android.R.styleable#Window_windowReenterTransition

        //SharedElementEnter Required Api 21        
        getWindow().setSharedElementEnterTransition(transition);//android.R.styleable#Window_windowSharedElementEnterTransition
        getWindow().setSharedElementExitTransition(transition);//android.R.styleable#Window_windowSharedElementExitTransition
        getWindow().setSharedElementReturnTransition(transition);//android.R.styleable#Window_windowSharedElementReturnTransition
        getWindow().setSharedElementReenterTransition(transition);//android.R.styleable#Window_windowSharedElementReenterTransition

        //相应的Style
        <item name="android:windowEnterTransition"></item>
        <item name="android:windowExitTransition"></item>
        <item name="android:windowReturnTransition"></item>
        <item name="android:windowReenterTransition"></item>
        <item name="android:windowSharedElementEnterTransition"></item>
        <item name="android:windowSharedElementExitTransition"></item>
        <item name="android:windowSharedElementReturnTransition"></item>
        <item name="android:windowSharedElementReenterTransition"></item>

        //Enter与Exit是否同时进行
        <item name="android:windowAllowEnterTransitionOverlap">true</item>
        //Retrun与Reenter是否同时进行
        <item name="android:windowAllowReturnTransitionOverlap">true</item>

Transition相关的类在android.transition包下:


image.png

继承关系如下:


image.png
  • ContentTransition
    接下来举几个例子,看看如何进行页面转换

通用的ActivityA

        ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(context);
        Intent intent = new Intent(context, MeiziDetailActivity.class);
        intent.putExtra("url", url);
//        ActivityCompat.startActivity(context, intent, options.toBundle());//兼容api16以下的
        context.startActivity(intent, options.toBundle());

Explode
ActivityB

        Explode transition = new Explode();
        getWindow().setEnterTransition(transition);//B
        getWindow().setReturnTransition(transition);//B

效果:


a.gif

ActivityB

        Explode transition = new Explode();
        transition.excludeTarget(android.R.id.statusBarBackground, true);//去掉状态栏的动画效果
        getWindow().setEnterTransition(transition);//B
        getWindow().setReturnTransition(transition);//B

效果:更为合理


b.gif

Slide
ActivityB

        Slide transition = new Slide(Gravity.RIGHT);//可以不传参数,默认为Gravity.BOTTOM.
        transition.excludeTarget(android.R.id.statusBarBackground, true);
        getWindow().setEnterTransition(transition);//B
        getWindow().setReturnTransition(transition);//B

效果:


c.gif

Fade
ActivityB

        Fade transition = new Fade();
        transition.excludeTarget(android.R.id.statusBarBackground, true);
        getWindow().setEnterTransition(transition);//B
        getWindow().setReturnTransition(transition);//B

效果:


d.gif

以上Fade Slide Explode都是继承自Visibility,动画通常与可见度相关,用于页面间的切换
而接下来介绍与View的尺寸,位置,样式相关的几个Transition动画。

  • Share Element Transition
    Share Element Transition 在上一节中已经介绍过了,通过ActivityOptionsCompat.makeSceneTransitionAnimation来实现。
    我们并没有通过
    getWindow().setSharedElement[]Transition(transition)

    <item name="android:windowSharedElement[]Transition"></item>
    来指定Transition的情况下,系统默认使用@android:transition/move - (将所有变换同时进行的一个TransitionSet )。

ChangeBounds -捕获共享元素的layout bound,然后播放layout bound变化动画。ChangeBounds 是共享元素变换中用的最多的,因为前后两个activity中共享元素的大小和位置一般都是不同的。

ChangeTransform - 捕获共享元素的缩放(scale)与旋转(rotation)属性 ,然后播放缩放(scale)与旋转(rotation)属性变化动画。

ChangeClipBounds - 捕获共享元素clip bounds,然后播放clip bounds变化动画。

ChangeImageTransform - 捕获共享元素(ImageView)的transform matrices 属性,然后播放ImageViewtransform matrices 属性变化动画。与ChangeBounds相结合,这个变换可以让ImageView在动画中高效实现大小,形状或者ImageView.ScaleType 属性平滑过度

以最常用的ChangeBounds为例 (Required Api 19)

        Fade fade  = new Fade();
        fade.excludeTarget(android.R.id.statusBarBackground, true);
        fade.excludeTarget(android.R.id.navigationBarBackground, true);
        getWindow().setEnterTransition(fade);
        getWindow().setReturnTransition(fade);

        ChangeBounds changeBounds = new ChangeBounds();
        getWindow().setSharedElementEnterTransition(changeBounds);
        getWindow().setSharedElementExitTransition(changeBounds);
a.gif
  • XML方式定义
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <slide android:duration="500">
        <targets >
            <target android:excludeId="@android:id/statusBarBackground"/>
            <target android:targetId="@android:id/targetId"/>
        </targets>
    </slide>
</transitionSet>

<?xml version="1.0" encoding="utf-8"?>
<fade android:duration="500"
    xmlns:android="http://schemas.android.com/apk/res/android">
</fade>

<?xml version="1.0" encoding="utf-8"?>
<explode xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500">
    <targets>
        <target android:excludeId="@android:id/statusBarBackground"/>
    </targets>
</explode>


<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:interpolator/accelerate_quad"
    android:duration="2000"
    android:transitionOrdering="together"
    android:startDelay="1000">
    <fade/>
    <explode/>
    <slide/>
    <changeBounds/>
    <changeTransform/>
    <changeImageTransform/>
    <changeClipBounds/>
    <changeScroll/>
</transitionSet>

ViewAnimationUtil

只有一个方法,以一个中心慢慢扩展的显示View的动画效果。
https://developer.android.google.cn/reference/android/view/ViewAnimationUtils.html

@param view The View will be clipped to the animating circle.
@param centerX The x coordinate of the center of the animating circle, relative to  view.
@param centerY The y coordinate of the center of the animating circle, relative to view.
@param startRadius The starting radius of the animating circle.
@param endRadius The ending radius of the animating circle.
Animator createCircularReveal(View view, int centerX,  int centerY, float startRadius, float endRadius)

ArcMotion

http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0901/3400.html

系统自带的PathInterpolator

@interpolator/fast_out_linear_in.xml
@interpolator/fast_out_slow_in.xml
@interpolator/linear_out_slow_in.xml
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    Path path = new Path();
    path.arcTo(0f, 0f, 1000f, 1000f, 270f, -180f, true);
    ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.X, View.Y, path);
    animator.setDuration(2000);
    animator.start();
} else {
    // Create animator without using curved path
}
a.gif

AnimatedVectorDrawable

//www.greatytc.com/writer#/notebooks/15481674/notes/22126140/preview

Ripple

https://blog.csdn.net/AwayEagle/article/details/52583913
https://blog.csdn.net/a396901990/article/details/40187203

SpringAnimation

https://developer.android.google.cn/guide/topics/graphics/spring-animation.html

FlingAnimation

https://developer.android.google.cn/guide/topics/graphics/fling-animation.html

最后分享几个不错的动画效果及网址:
KeyFrames
NineOldAndroids
Transitions-Everywhere
参考 :
官网
https://www.cnblogs.com/ldq2016/p/5407061.html
//www.greatytc.com/p/2412d00a0ce4
http://blog.csdn.net/harvic880925/article/details/50525521
//www.greatytc.com/p/0f83fbe756aa
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0819/8397.html
http://blog.csdn.net/u010142437/article/details/53819573
//www.greatytc.com/p/e497123652b5
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0201/2394.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容

  • 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让全面说说Android的动画,所以今...
    未聞椛洺阅读 2,709评论 0 10
  • 3.0以前,android支持两种动画模式,tween animation,frame animation,在an...
    Ten_Minutes阅读 1,654评论 0 4
  • 【Android 动画】 动画分类补间动画(Tween动画)帧动画(Frame 动画)属性动画(Property ...
    Rtia阅读 6,157评论 1 38
  • 转载一篇高质量博文,原地址请戳这里转载下来方便今后查看。1 背景不能只分析源码呀,分析的同时也要整理归纳基础知识,...
    Elder阅读 1,942评论 0 24
  • Android框架提供了两种类型的动画:View Animation(也称视图动画)和Property Anima...
    RxCode阅读 1,633评论 1 5