4.动画视图

4.1 问题

应用程序要让视图对象运动起来,实现变化或其他特效。

4.2 解决方案

(API Level12)
ObjectAnimator实例,例如ViewPropertyAnimator,可以用来操作View对象的属性,例如视图的位置或旋转。ViewPropertyAnimation是通过View.animate()获得的,然后根据动画的特征进行修改。通过这个API进行的修改会影响到View对象本身的真实属性。

4.3 实现机制

ViewPropertyAnimation是对视图内容制作动画的最便利方法。此API的工作方式就像生成器一样,所有对不同属性修改的调用都可以连接起来组成一个动画。在当前线程的Looper的相同迭代中,对ViewPropertyAnimation的所有调用都会汇集到一个动画中。以下两个代码演示了一个简单的视图过渡Activity。
res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/toggleButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Click to Toggle" />
    <View
        android:id="@+id/theView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#AAA" />
</LinearLayout>

使用了ViewPropertyAnimation的Activity

public class AnimateActivity extends Activity implements View.OnClickListener {

    private View mViewToAnimate;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        Button button = (Button)findViewById(R.id.toggleButton);
        button.setOnClickListener(this);

        mViewToAnimate = findViewById(R.id.theView);
    }

    @Override
    public void onClick(View v) {
        if(mViewToAnimate.getAlpha() > 0f) {
            //如果视图可见,将其从右侧滑出
            mViewToAnimate.animate().alpha(0f).translationX(500f);
        } else {
            //如果视图是隐藏的,原地做渐显动画
            //Property Animations会实际修改视图,因此必须首先恢复视图的位置
            mViewToAnimate.setTranslationX(0f);
            mViewToAnimate.animate().alpha(1f);
        }
    }
}

在这个示例中,滑动动画和渐显动画是通过alpha和translationX(这个过渡值需要足够大才能够让视图移除屏幕)属性一起实现的。我们不需要将这些方法调用链接到一起,从而组成一个动画。即使我们在不同的地方调用,它们还是会一起执行,因为它们都是在主线程的Looper的相同迭代中设置的。
注意,这里我们首先恢复了View的过渡属性,然后运行了没有滑动效果的渐显动画。这是因为属性动画会修改视图本身,而不只是暂时地绘制(之前的动画API的机制)。如果不恢复这个属性,依然会有渐显动画,但会在屏幕外右侧1000像素处进行。

1.ObjectAnimation

虽然ViewPropertyAnimation可以很方便且快速地实现简单的属性动画,但对于一些更加复杂的工作,例如将多个动画链接到一起,这种方式会受到一定的限制。这时我们可以使用它的父类ObjectAnimator。通过ObjectAnimator,我们可以设置监听器,从而在动画开始和结束时得到相应的通知;另外在动画做增量更新时也可以得到通知。
以下两个代码显示了如何使用ObjectAnimator来修改我们的Flipper动画代码:
res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/flip_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"/>
</FrameLayout>

使用Objectanimator实现掷硬币动画

public class FlipperActivity extends Activity {

    private boolean mIsHeads;
    private ObjectAnimator mFlipper;
    private Bitmap mHeadsImage, mTailsImage;
    private ImageView mFlipImage;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mHeadsImage = BitmapFactory.decodeResource(getResources(), R.drawable.heads);
        mTailsImage = BitmapFactory.decodeResource(getResources(), R.drawable.tails);

        mFlipImage = (ImageView)findViewById(R.id.flip_image);
        mFlipImage.setImageBitmap(mHeadsImage);
        mIsHeads = true;

        mFlipper = ObjectAnimator.ofFloat(mFlipImage, "rotationY", 0f, 360f);
        mFlipper.setDuration(500);
        mFlipper.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                if (animation.getAnimatedFraction() >= 0.25f && mIsHeads) {
                    mFlipImage.setImageBitmap(mTailsImage);
                    mIsHeads = false;
                }
                if (animation.getAnimatedFraction() >= 0.75f && !mIsHeads) {
                    mFlipImage.setImageBitmap(mHeadsImage);
                    mIsHeads = true;
                }
            }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(event.getAction() == MotionEvent.ACTION_DOWN) {
            mFlipper.start();
            return true;
        }
        return super.onTouchEvent(event);
    }
}

属性动画提供了一些之前旧动画系统没有的变换功能,例如x轴和y轴的旋转效果,从而实现三维变换效果。本例中,我们不需要计算缩放比例来是实现旋转,只需要告诉视图沿着y轴旋转即可。正因为如此,我们不再需要使用两个动画来操作硬币,整个旋转过程只需要操作视图的rotationY属性即可。
另一个强大之处就是AnimationUpdateListener,它提供了动画运行过程中的常规回调方法。getAnimationFraction()方法会返回当前动画完成的百分比。还可以通过getAnimatedValue()得到当前动画某个属性的准确值。
本例中,我们使用第一个方法在动画运行到两个时刻(硬币可以换面,即90°和270°或者动画时长的25%和75%)时,更换正反面的图片。因为并不能保证从每个角度我们可以得到通知,所有当达到阈值后,我们会立即更换图片。我们还设置了一个布尔标识来避免在旋转过程中对同一个值进行重复的图片设置(这会产生不必要的性能损耗)。
如果应用程序需要链接多个动画,ObjectAnimation还可以支持更加传统的AnimationListener来响应动画的主要事件,例如开始、结束和重复。
提示:
在Android4.4上,Animation还支持pause()和resume()方法以挂起运行中的动画而不用完全取消它。

2.AnimationSet

如果需要执行多个动画,则可以在AnimationSet中聚集这些动画。可以同时播放AnimationSet中的所有动画,或者依次播放每个动画。定义动画集合的Java代码可能稍微有些冗长,因此我们将在此例中改为使用XML动画格式。以下代码定义了一组将应用于硬币翻转的动画。
res/animator/flip.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">
    <!-- 建立硬币旋转的线性重复 -->
    <objectAnimator
        android:propertyName="rotationX"
        android:duration="400"
        android:valueFrom="0"
        android:valueTo="360"
        android:valueType="floatType"
        android:repeatMode="restart"
        android:repeatCount="3"
        android:interpolator="@android:interpolator/linear"/>
    <!-- 增加一个提升动画以显示硬币在空中上升 -->
    <objectAnimator
        android:propertyName="translationY"
        android:duration="800"
        android:valueTo="-200"
        android:valueType="floatType"
        android:repeatMode="reverse"
        android:repeatCount="1" />
</set>

此处我们在中定义了两个同时播放的动画(通过android:ordering = “together”)。第一个动画我们前面以及看过,用于旋转硬币图片一次。此动画设置为重复3次,提供3个完整的旋转。图片的默认插值器是加速/减速时间曲线,这看起来会结束硬币翻转动画。为提供全程一致的速度,我们改为对动画应用系统的线性插值器。
第二个动画使硬币在旋转期间沿着视图向上滑动,看起来就像是硬币被扔到空中的效果。因为硬币还必须落回来,将此动画设置为在完成后反向运行一次。
以下代码显示了附加到翻转器Activity的新动画。
带有XML版本的AnimatorSet的翻转器动画

public class FlipperActivity extends Activity {

    private boolean mIsHeads;
    private AnimatorSet mFlipper;
    private Bitmap mHeadsImage, mTailsImage;
    private ImageView mFlipImage;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mHeadsImage = BitmapFactory.decodeResource(getResources(), R.drawable.heads);
        mTailsImage = BitmapFactory.decodeResource(getResources(), R.drawable.tails);

        mFlipImage = (ImageView)findViewById(R.id.flip_image);
        mFlipImage.setImageResource(R.drawable.heads);
        mIsHeads = true;

        mFlipper = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.flip);
        mFlipper.setTarget(mFlipImage);

        ObjectAnimator flipAnimator = (ObjectAnimator) mFlipper.getChildAnimations().get(0);
        flipAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                if (animation.getAnimatedFraction() >= 0.25f && mIsHeads) {
                    mFlipImage.setImageBitmap(mTailsImage);
                    mIsHeads = false;
                }
                if (animation.getAnimatedFraction() >= 0.75f && !mIsHeads) {
                    mFlipImage.setImageBitmap(mHeadsImage);
                    mIsHeads = true;
                }
            }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(event.getAction() == MotionEvent.ACTION_DOWN) {
            mFlipper.start();
            return true;
        }
        return super.onTouchEvent(event);
    }
}

在此例中,我们在XML中使用AnimatorInflater构造AnimatorSet对象。产生的动画必须通过setTarget()附加到适当的目标视图,这是ObjectAnimator.ofFloat()隐式完成的工作。我们仍然需要AnimatorUpdateListener确定何时从头部切换到尾部,但该侦听器不能直接应用于集合对象。因此,我们必须使用getChildAnimations()查找集合内的旋转动画,以便在适当的位置附加此侦听器。
运行此新的示例将产生更加真实的硬币翻转动画。

提供drawable资源:
heads.png
tails.png

猜你喜欢

转载自blog.csdn.net/qq_41121204/article/details/82454925
今日推荐