Android动画之属性动画(二)

来源:互联网 发布:冰点文库下载器 mac 编辑:程序博客网 时间:2024/05/05 18:47

定义

The property animation system is a robust framework that allows you to animate almost anything. You can define an animation to change any object property over time, regardless of whether it draws to the screen or not. A property animation changes a property's (a field in an object) value over a specified length of time. 

属性动画系统是一个很健壮的框架。基本允许你在任何View加动画。你可以定义一个动画来改变对象的属性,不管它是否绘制在Screen上。属性动画在一段具体的时间内改变属性的值。

属性设置

你可以定义以下属性来设置一个动画
Duration  动画运行时间,默认的是300ms
Time interpolation 时间插值器,指定动画属性值的运行函数的计算方式
Repeat count and behavior 重复次数和行为,可以顺着重复还可以倒着重复
Animator sets 将动画放入集合中,顺序播放或一起播放
Frame refresh delay 可以指定刷新帧动画的频度,默认的刷新频度是10ms,但是应用程序刷新帧的速度,最终取决于系统的整体速度和系统如何快速服务于底层计时器。

属性动画和View Animation(补间动画)的区别

View Animation是只能针对View来设置动画,对于非View的对象,只能用自己的代码实现。View Animation加在View上的动画也非常有限,比如scale,rotate,类似改变view背景都是不行的。
View Animation的另一个缺点是,它只是改变View绘制的地方,实质上并没有改变原来的View。
对于属性动画就不存在以上的问题了。属性动画它改变的是真实的View。

最后,View Animation构建时间少,代码少。如果View Animation满足你的一切需求,就没有必要使用属性动画。

Animator类

以下类都继承自Animator

ValueAnimator

简述
属性动画的主要时序引擎,还计算动画中的属性值。它包含计算动画值的所有核心功能,并包含每个动画的时间细节,是否重复动画,接收更新事件的监听器,设置自定义计算类型的能力。
一般设置动画过程包含两个步骤:一计算动画的值,二将这些值设置到对象上,并按这些值进行播放。ValueAnimator不能执行步骤二,所以你必须监听ValueAnimator更新的计算值和修改对象,你必须实现自己的逻辑。

使用方法

 

ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f); 
animation.setDuration(1000); 
animation.start();
ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue); 
animation.setDuration(1000); 
animation.start();

 

实例
进行单个属性设置,使用ValueAnimator的工厂方法ofFloat()、ofObject()、ofInt()方法就可以解决。接下来的实例我们来讨论一下多个动画属性值的解决方案。将View从左上位置移到右下位置。
很显然我们需要更新x,y轴方向的坐标。简单的做法就是创建两个动画,并同步播放它们,但是它们的效率不高。我们可以使用PropertyValuesHolder来将多个值联系起来。另外,你可以使用自定义的对象来封装动画的值。
使用PropertyValuesHolder
属性动画为了支持3.0及以下版本,NineOldAndroids提供了一个AnimatorProxy的类来包装view,并处理动画view的绘制。
实现步骤如下:
activity_main.xml

 

<RelativeLayout xmlns:android="<a href="http://schemas.android.com/apk/res/android" "="" style="text-decoration: none; color: blue !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border-bottom-right-radius: 0px !important; border-bottom-left-radius: 0px !important; border: 0px !important; bottom: auto !important; float: none !important; height: auto !important; left: auto !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important; box-sizing: content-box !important; background-image: none !important; background-position: initial initial !important; background-repeat: initial initial !important;">http://schemas.android.com/apk/res/android"
    xmlns:tools="<a href="http://schemas.android.com/tools" "="" style="text-decoration: none; color: blue !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border-bottom-right-radius: 0px !important; border-bottom-left-radius: 0px !important; border: 0px !important; bottom: auto !important; float: none !important; height: auto !important; left: auto !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important; box-sizing: content-box !important; background-image: none !important; background-position: initial initial !important; background-repeat: initial initial !important;">http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
 
    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentTop="true"/>
 
    <LinearLayout
        android:id="@+id/controlBtns"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal">
 
        <Button
            android:id="@+id/btnStart"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Start" />
 
        <Button
            android:id="@+id/btnReset"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Reset" />
 
    </LinearLayout>
 
    <ImageView
        android:id="@+id/image"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_above="@+id/controlBtns"
        android:src="#ffff4444" />
</RelativeLayout>

MainActivty类

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
 
import com.nineoldandroids.animation.PropertyValuesHolder;
import com.nineoldandroids.animation.ValueAnimator;
import com.nineoldandroids.view.animation.AnimatorProxy;
 
public class MainActivity extends AppCompatActivity implements ValueAnimator.AnimatorUpdateListener {
 
    private ImageView imageView;
    private View container;
    private AnimatorProxy animatorProxy;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = (ImageView) this.findViewById(R.id.image);
        container = findViewById(R.id.container);
        animatorProxy = AnimatorProxy.wrap(imageView);
 
        final float originX = animatorProxy.getX();
        final float originY = animatorProxy.getY();
        findViewById(R.id.btnStart).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
 
                PropertyValuesHolder widthPropertyHolder = PropertyValuesHolder.ofFloat("posX", animatorProxy.getX(), container.getWidth() - imageView.getWidth());
                PropertyValuesHolder heightPropertyHolder = PropertyValuesHolder.ofFloat("posY", animatorProxy.getY(), 0);
                ValueAnimator translationAnimator = ValueAnimator.ofPropertyValuesHolder(widthPropertyHolder, heightPropertyHolder);
                translationAnimator.addUpdateListener(MainActivity.this);
                translationAnimator.setDuration(1000);
                translationAnimator.start();
                findViewById(R.id.btnStart).setEnabled(false);
 
            }
        });
 
        findViewById(R.id.btnReset).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                findViewById(R.id.btnStart).setEnabled(true);
                animatorProxy.setX(originX);
                animatorProxy.setY(originY);
            }
        });
 
    }
 
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float posX = (Float) animation.getAnimatedValue();
        float posY = (Float) animation.getAnimatedValue();
        animatorProxy.setX(posX);
        animatorProxy.setY(posY);
    }
}

运行的效果如下:


我们还可以自己创建一个对象来封装动画的变化值,如下所示

修改MainActivity的代码如下:

 

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
 
import com.nineoldandroids.animation.TypeEvaluator;
import com.nineoldandroids.animation.ValueAnimator;
import com.nineoldandroids.view.animation.AnimatorProxy;
 
public class MainActivity extends AppCompatActivity implements ValueAnimator.AnimatorUpdateListener {
 
    private ImageView imageView;
    private View container;
    private AnimatorProxy animatorProxy;
    float originX, originY;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = (ImageView) this.findViewById(R.id.image);
        container = findViewById(R.id.container);
        animatorProxy = AnimatorProxy.wrap(imageView);
        findViewById(R.id.btnStart).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                originX = animatorProxy.getX();
                originY = animatorProxy.getY();
                ValueAnimator translationAnimator = ValueAnimator.ofObject(new PositionTypeEvaluator(), new Position(originX, originY),
                        new Position(container.getWidth() - imageView.getWidth(), 0));
                translationAnimator.addUpdateListener(MainActivity.this);
                translationAnimator.setDuration(1000);
                translationAnimator.start();
                findViewById(R.id.btnStart).setEnabled(false);
 
            }
        });
 
        findViewById(R.id.btnReset).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                findViewById(R.id.btnStart).setEnabled(true);
                animatorProxy.setX(originX);
                animatorProxy.setY(originY);
            }
        });
 
    }
 
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        Position currentPos = (Position) animation.getAnimatedValue();
        animatorProxy.setX(currentPos.getPosX());
        animatorProxy.setY(currentPos.getPosY());
    }
 
    private class PositionTypeEvaluator implements TypeEvaluator<Position> {
 
        @Override
        public Position evaluate(float fraction, Position startValue, Position endValue) {
            Log.d("postion","fraction"+fraction);
            float posX = startValue.getPosX() + (endValue.getPosX() - startValue.getPosX()) * fraction;
            float posY = startValue.getPosY() + (endValue.getPosY() - startValue.getPosY()) * fraction;
            return new Position(posX, posY);
        }
    }
 
    private class Position {
 
        private float posX;
        private float posY;
 
        public Position(float posX, float posY) {
            this.posX = posX;
            this.posY = posY;
        }
 
        public float getPosX() {
            return posX;
        }
 
        public void setPosX(float posX) {
            this.posX = posX;
        }
 
        public float getPosY() {
            return posY;
        }
 
        public void setPosY(float posY) {
            this.posY = posY;
        }
    }
}

运行结果类似上图

那fraction是什么呢?截了个图。从0到1的小数。

fraction The elapsed, interpolated fraction of the animation.

这个应该动画过程中的插值小数。


ObjectAnimator

A subclass of ValueAnimator that allows you to set a target object and object property to animate. This class updates the property accordingly when it computes a new value for the animation. You want to use ObjectAnimator most of the time, because it makes the process of animating values on target objects much easier. However, you sometimes want to use ValueAnimator directly becauseObjectAnimator has a few more restrictions, such as requiring specific acessor methods to be present on the target object.

ObjectAnimator是ValueAnimator的子类,可以改变对象的属性值完成动画。我更愿意使用ObjectAnimator,因为它设置对象的动画更为简单。有时候你可能还得使用ValueAnimator,因为ObjectAnimator有一些限制,比如需要特定的方法展示在指定的对象上。

使用ObjectAnimator实现动画方式有两种,一种是XML,一种用JAVA代码实现。

就我自己使用而言,XML代码实现的动画,格式更清晰,一目了然就知道动画中包含哪些步骤,其次XML格式的不支持9和9以下的版本。Java代码实现,更容易做一些动态的处理,比如你的属性动画和屏幕的大小有直接的关系。动画的大小和比例需要根据运行屏幕的大小来动态计算,这个时候就推荐使用Java代码来实现。

其中的AnimatorSet提供组合动画能力的类。并可设置组中动画的时序关系,如同时播放、有序播放或延迟播放。

实例

XML方式实现

我们先用XML的方式实现一个动画。新建一个anim/animation.xml文件,表示动画的内容

<set xmlns:android="<a href="http://schemas.android.com/apk/res/android" "="" style="text-decoration: none; color: blue !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border-bottom-right-radius: 0px !important; border-bottom-left-radius: 0px !important; border: 0px !important; bottom: auto !important; float: none !important; height: auto !important; left: auto !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important; box-sizing: content-box !important; background-image: none !important; background-position: initial initial !important; background-repeat: initial initial !important;">http://schemas.android.com/apk/res/android"
 android:ordering="sequentially">
 <set android:ordering="together">
 <objectAnimator
 android:duration="500"
 android:propertyName="scaleX"
 android:valueTo="1.05f" />
 <objectAnimator
 android:duration="500"
 android:propertyName="scaleY"
 android:valueTo="1.05f" />
 <objectAnimator
 android:duration="500"
 android:propertyName="translationY"
 android:valueTo="100" />
 </set>
 <set android:ordering="together">
 <objectAnimator
 android:duration="500"
 android:propertyName="scaleX"
 android:valueFrom="1.05f"
 android:valueTo="0.3f" />
 <objectAnimator
 android:duration="500"
 android:propertyName="scaleY"
 android:valueFrom="1.05f"
 android:valueTo="0.3f" />
 <objectAnimator
 android:duration="500"
 android:propertyName="translationY"
 android:valueTo="-100" />
 </set>
</set>

 

通过xml文件很好分析动画的步骤。主要分为两个环节,一个是扩大下移的过程,一个是缩小上移的过程。

 

布局anim.xml文件如下所示

 

<LinearLayout xmlns:android="<a href="http://schemas.android.com/apk/res/android" "="" style="text-decoration: none; color: blue !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border-bottom-right-radius: 0px !important; border-bottom-left-radius: 0px !important; border: 0px !important; bottom: auto !important; float: none !important; height: auto !important; left: auto !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important; box-sizing: content-box !important; background-image: none !important; background-position: initial initial !important; background-repeat: initial initial !important;">http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">
    <FrameLayout
        android:id="@+id/layout"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:background="@android:color/white"/>
    <Button
        android:id="@+id/click"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="start"/>
</LinearLayout>

 

主类代码如下所示:

 

import android.animation.AnimatorInflater;
import android.animation.AnimatorSet;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
 
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
 
    private View view;
    private Button button;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.anim);
        view = findViewById(R.id.layout);
        button = (Button) findViewById(R.id.click);
        button.setOnClickListener(this);
    }
 
 
    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.click) {
            AnimatorSet animatorSet = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.anim.animation);
            animatorSet.setTarget(view);
            animatorSet.start();
        }
 
    }
}

动画效果如下:


Java代码实现(动态计算,缩小到固定的大小)

@Override
    public void onClick(View v) {
        if (v.getId() == R.id.click) {
            final int  width=97;
            final int  height=58;
 
            float scaleTo=1.05f;
            Util util=new Util();
            float widthRatio = util.dp2px(this, width) / (view.getWidth() * scaleTo);
            float heightRatio = util.dp2px(this, height) / (view.getHeight() * scaleTo);
            AnimatorSet animatorSet = new AnimatorSet();
            //放大并下移
            Animator animator = zoomAndTranslateAnimation(view, 1, scaleTo, 1, scaleTo,100);
            animator.setDuration(500);
            //缩小并上移
            Animator animator1 = zoomAndTranslateAnimation(view, scaleTo, widthRatio, scaleTo, heightRatio, -100);
            animator1.setDuration(500);
            animatorSet.playSequentially(animator, animator1);
            animatorSet.start();
 
        }
 
    }
 
    public Animator zoomAndTranslateAnimation(View view, float xScaleValueFrom, float xScaleValueTo, float yScaleValueFrom,
                                              float yScaleValueTo, float translation) {
        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", xScaleValueFrom, xScaleValueTo);
        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", yScaleValueFrom, yScaleValueTo);
        PropertyValuesHolder translationY = PropertyValuesHolder.ofFloat("translationY", translation);
        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, scaleX, scaleY, translationY);
        return animator;
    }
 
 
    class Util {
        public int dp2px(Context context, int dp) {
            DisplayMetrics metrics = context.getResources().getDisplayMetrics();
            return (int) (dp * metrics.density);
        }
    }
}

另外,还可以通过AnimatorSet.Builder的方式将要播放的动画联系在一起。

AnimatorSet animatorSet = new AnimatorSet();
//放大并下移
Animator animator = zoomAndTranslateAnimation(view, 1, scaleTo, 1, scaleTo,100);
animator.setDuration(500);
AnimatorSet.Builder builder=animatorSet.play(animator);
 
Aniamtor animator1=........
.......
....
builder.with(animator1);


参考链接:https://github.com/android-cn/android-open-project-analysis/blob/master/tech/animation/README.md(非常好的总结,推荐阅读)




0 0