Android动画机制与使用技巧

参考资料

LayoutAnimationController实战案例
郭霖- Android属性动画完全解析


目录

  • 1)视图动画
  • 2)属性动画
  • 3)对任意属性做动画
  • 4)布局动画
  • 5)Interpolators(插值器)&TypeEvaluator(估值器)
  • 6)Android5.X SVG矢量动画
  • 7) 动画特效
  • 8)Activity过渡动画
  • 9)帧动画
  • 10)Lottie动画

1)视图动画

缺陷 优点
不具备交互性 效率高&方便
Android动画机制与使用技巧_第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-完全透明
Android动画机制与使用技巧_第2张图片
属性动画
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。


Android动画机制与使用技巧_第3张图片
Button宽度属性做动画

属性动画要求动画作用的对象提供该属性的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"

7月-06-2017 16-37-18.gif
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
  • 估值器,即根据属性改变的百分比来计算改变后的属性值。


    Android动画机制与使用技巧_第4张图片
    TimeInterpolator
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


    Android动画机制与使用技巧_第5张图片
    示例1

    Android动画机制与使用技巧_第6张图片
    工程目录

    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图像

Android动画机制与使用技巧_第7张图片
示例2

VectorDrawable
~/res/drawable/svg_vector2.xml




    
    
    
    

objectAnimator
~/res/anim/svg_objanimator2.xml



    

AnimatedVectorDrawable
~res/drawable/svg_animted_vector2.xml



    


7)动画特效

  • 灵动菜单


    Android动画机制与使用技巧_第8张图片
    灵动菜单
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 淡入淡出
Android动画机制与使用技巧_第9张图片
explode

~/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四端


Android动画机制与使用技巧_第10张图片
屏幕快照 2018-03-02 下午4.59.53.png

Android动画机制与使用技巧_第11张图片
Lottie

你可能感兴趣的:(Android动画机制与使用技巧)