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>

此处我们在<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

Demo下载地址:
1.4 动画视图

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

推荐阅读更多精彩内容