Android 属性动画特效

        在 Android 3.0 之前已有的动画框架 Animation 存在一些局限性—— 动画改变的只是显示,并不能响应事件(相比属性动画,视图动画的一个非常大的缺陷就是不具备交互性,当某个元素发生视图动画后,其响应事件的位置依然在动画前的地方,所以视图动画只能做普通的动画效果,避免交互的发生。它的优点是效率比较高使用方便)。

本文要实现的效果如下:




在这之前先简单的介绍一下属性动画。


       1.   属性动画涉及的 API :

      (1)Animator :它提供了创建属性动画的基类,基本上不会直接使用该类。通常该类只用于被继承并重写它的相关方法。

      (2) ValueAnimator :属性动画主要的时间引擎,它负责计算各个帧的属性值。属性动画主要有两方面组成:

        ——> 1)计算各帧的相关属性值;

        ——> 2)为指定对象设置这些计算后的值;

        ValueAnimator 只负责第一方面内容,因此程序员必须根据 ValueAnimator  计算并监听值更新来更新对象的相关属性值。使用 ValueAnimator 需要注册 AnimatorUpdateListener 监听器。

      (3)ObjectAnimator :它是 ValueAnimator  的子类,允许程序员对指定对象的属性执行动画。在实际应用中,它比 ValueAnimator  使用起来更加简单。使用 ObjectAnimator 就不需要注册 AnimatorUpdateListener 监听器了。

      (4) AnimatorSet :它是 Animator  的子类,组合多个 Animator ,并指定多个 Animator  是按次序播放,还是同时播放。


        在 Animator 框架中使用最多的就是 AnimatorSet 和 ObjectAnimator ,使用 ObjectAnimator  进行更精细化的控制,只控制一个对象的一个属性值,而使用多个 ObjectAnimator  组合到 AnimatorSet 形成一个动画。属性动画通过调用属性的 get 、set 方法来真实地控制一个 View 的属性值,因此强大的动画框架,基本可以实现所有的动画效果。



       2.  使用属性动画的步骤如下:

      (1)创建 ObjectAnimator 或  ValueAnimator 对象——即可从 XML 资源文件加载该动画资源,也可以直接调用  ObjectAnimator 或  ValueAnimator 的静态工厂方法来创建动画。

        ——> 静态方法 ofInt() 、ofFloat() 或者 ofObject();

——> 注意使用 ObjectAnimator 的静态方法创建 ObjectAnimator 对象时,需要指定具体的对象,以及对象的属性名。如: ObjectAnimator animator0 = ObjectAnimator.ofFloat(foo, "alpha", 0.5F, 1F);

       (2)根据需要为 Animator  对象设置属性。

       (3)如果需要监听 Animator  的动画开始事件、动画结束事件、动画重复事件、动画值改变事件,并根据事件提供相应的处理代码,则应该为 Animator  对象设置事件监听器。

       (4)如果有多个动画需要按次序或者同时播放,则应使用 AnimatorSet 组合这些动画。

       (5)调用 Animator  对象的 start() 方法启动动画。



         ObjectAnimator 

         ObjectAnimator  参数包括一个对象和对象的属性名,但这个属性必须有 get() 和 set() 函数,内部会通过 Java 反射机制来调用 set 函数修改对象属性。

         前文的使用 ObjectAnimator animator0 = ObjectAnimator.ofFloat(foo, "alpha", 0.5F, 1F); 创建一个 ObjectAnimator 对象。其中第一个参数时需要操纵的 View;第二个参数则是操纵的属性;而最后一个参数是一个可变数组参数,需要传进去该属性变化的一个取值过程。

第二个参数的属性值如下:

      (1)translationX 和 translationY:这两个属性作为一种增量来控制着 View 对象从它布局容器的左上角坐标偏移的位置。

      (2)rotation 、rotationX 和 rotationY:这三个属性控制着 View 对象围绕支点进行 2D 和 3D 旋转。

      (3)scaleX 和 scaleY:这两个属性控制着 View 对象围绕它的支点进行 2D 缩放。

      (4)pivotX 和 pivotY:这两个属性控制着 View 对象的支点位置,围绕这个支点进行旋转和缩放变换处理。默认情况下,该支点的位置就是 View 对象的中心点。

      (5)x 和 y:它描述了 View 对象在它的容器中的最终位置,它是最初的左上角坐标和 translationX 、 translationY 值得累计和。

      (6)alpha:它表示 View 对象的 alpha 透明度。默认值为 1 (不透明),0 代表完全透明(不可见)。


属性的 get() 和 set() 方法也可以从如下两个方案来解决:

        ——> 通过自定义一个属性或者包装类,来间接地给这个属性增加 get() 、set() 方法。

——> 通过 ValueAnimator  来实现。


        AnimatorSet 

        对于一个属性同时作用多个属性动画效果,可以使用 PropertyValuesHolder 实现这样的效果,但是通过 AnimatorSet 不仅能实现这样的效果,同行也能实现更为精确的顺序控制。代码如下:

 ObjectAnimator animator1 = ObjectAnimator.ofFloat(foo, "translationY", -200F, 0);
 ObjectAnimator animator1 = ObjectAnimator.ofFloat(foo, "translationX", -200F, 0);
 AnimatorSet set = new AnimatorSet();
 set.setDuration(500);
 set.setInterpolator(new BounceInterpolator());
 set.playTogether(animator1, animator2);
 set.start();

        在属性动画中, AnimatorSet  正是通过 playTogether()、playSequentially()、animSet.play().with()、before()、after()这些方法来控制多个动画的协同工作方式。



第一个效果代码:

        forclick_layout.xml :

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

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/imageView_b"
        android:src="@drawable/b"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/imageView_c"
        android:src="@drawable/c"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/imageView_d"
        android:src="@drawable/d"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/imageView_e"
        android:src="@drawable/e"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/imageView_a"
        android:src="@drawable/a"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

</RelativeLayout>


PropertyTest.java :

package com.imooc.anim;

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.BounceInterpolator;
import android.widget.ImageView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class PropertyTest extends Activity implements View.OnClickListener {

    private int[] mRes = {R.id.imageView_a, R.id.imageView_b, R.id.imageView_c,
            R.id.imageView_d, R.id.imageView_e};
    private List<ImageView> mImageViews = new ArrayList<>();
    private boolean mFlag = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.forclick_layout);

        int sum = mRes.length;
        for (int i = 0; i < sum; i++) {
            ImageView imageView = (ImageView) findViewById(mRes[i]);
            imageView.setOnClickListener(this);
            mImageViews.add(imageView);
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.imageView_a:
                if (mFlag) {
                    startAnim();
                } else {
                    closeAnim();
                }
                break;
            case R.id.imageView_b:
                Toast.makeText(PropertyTest.this, "相机", Toast.LENGTH_SHORT).show();
                break;
            case R.id.imageView_c:
                Toast.makeText(PropertyTest.this, "音乐", Toast.LENGTH_SHORT).show();
                break;
            case R.id.imageView_d:
                Toast.makeText(PropertyTest.this, "定位", Toast.LENGTH_SHORT).show();
                break;
            case R.id.imageView_e:
                Toast.makeText(PropertyTest.this, "夜晚", Toast.LENGTH_SHORT).show();
                break;
        }
    }

    private void closeAnim() {
        ObjectAnimator animator0 = ObjectAnimator.ofFloat(mImageViews.get(0),
                "alpha", 0.5F, 1F);
        ObjectAnimator animator1 = ObjectAnimator.ofFloat(mImageViews.get(1),
                "translationY", 200F, 0);
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(mImageViews.get(2),
                "translationX", 200F, 0);
        ObjectAnimator animator3 = ObjectAnimator.ofFloat(mImageViews.get(3),
                "translationY", -200F, 0);
        ObjectAnimator animator4 = ObjectAnimator.ofFloat(mImageViews.get(4),
                "translationX", -200F, 0);
        AnimatorSet set = new AnimatorSet();
        set.setDuration(500);
        set.setInterpolator(new BounceInterpolator());
        set.playTogether(animator0, animator1, animator2, animator3, animator4);
        set.start();
        mFlag = true;
    }

    private void startAnim() {
        ObjectAnimator animator0 = ObjectAnimator.ofFloat(
                mImageViews.get(0),
                "alpha",
                1F,
                0.5F);
        ObjectAnimator animator1 = ObjectAnimator.ofFloat(
                mImageViews.get(1),
                "translationY",
                200F);
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(
                mImageViews.get(2),
                "translationX",
                200F);
        ObjectAnimator animator3 = ObjectAnimator.ofFloat(
                mImageViews.get(3),
                "translationY",
                -200F);
        ObjectAnimator animator4 = ObjectAnimator.ofFloat(
                mImageViews.get(4),
                "translationX",
                -200F);
        AnimatorSet set = new AnimatorSet();
        set.setDuration(500);
        set.setInterpolator(new BounceInterpolator());
        set.playTogether(
                animator0,
                animator1,
                animator2,
                animator3,
                animator4);
        set.start();
        mFlag = false;
    }
}



第二个效果的实现:

drop.xml :

<?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="wrap_content"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:onClick="llClick"
        android:background="@android:color/holo_blue_bright"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/app_icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:src="@drawable/ic_launcher" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dp"
            android:gravity="left"
            android:text="Click Me"
            android:textSize="30sp" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/hidden_view"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:background="@android:color/holo_orange_light"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:visibility="gone">

        <ImageView
            android:src="@drawable/ic_launcher"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />

        <TextView
            android:id="@+id/tv_hidden"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:textSize="20sp"
            android:text="我是隐藏着的!" />
    </LinearLayout>
</LinearLayout>

点击上面的 LinearLayout 时,需要获取到隐藏的 LinearLayout 最终需要达到的一个高度,即是目标值,通过将布局文件文件中的 dp 值转化为像素值即可。

        // 获取像素密度
        mDensity = getResources().getDisplayMetrics().density;
        // 获取布局的高度
        mHiddenViewMeasuredHeight = (int) (mDensity * 40 + 0.5);
40 就是在 XML 文件中定义的布局高度。


需要使用 ValueAnimator  来创建一个从 0 到目标值的数值发生器,并由此来改变 View 的布局属性。

 ValueAnimator animator = ValueAnimator.ofInt(start, end);
        animator.addUpdateListener(
                new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                int value = (Integer) valueAnimator.getAnimatedValue();
                ViewGroup.LayoutParams layoutParams =
                        view.getLayoutParams();
                layoutParams.height = value;
                view.setLayoutParams(layoutParams);
            }
        });



DropTest.java :

package com.imooc.anim;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

public class DropTest extends Activity {

    private LinearLayout mHiddenView;
    private float mDensity;
    private int mHiddenViewMeasuredHeight;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.drop);
        mHiddenView = (LinearLayout) findViewById(R.id.hidden_view);
        // 获取像素密度
        mDensity = getResources().getDisplayMetrics().density;
        // 获取布局的高度
        mHiddenViewMeasuredHeight = (int) (mDensity * 40 + 0.5);
    }

    public void llClick(View view) {
        if (mHiddenView.getVisibility() == View.GONE) {
            // 打开动画
            animateOpen(mHiddenView);
        } else {
            // 关闭动画
            animateClose(mHiddenView);
        }
    }

    private void animateOpen(final View view) {
        view.setVisibility(View.VISIBLE);
        ValueAnimator animator = createDropAnimator(
                view,
                0,
                mHiddenViewMeasuredHeight);
        animator.start();
    }

    private void animateClose(final View view) {
        int origHeight = view.getHeight();
        ValueAnimator animator = createDropAnimator(view, origHeight, 0);
        animator.addListener(new AnimatorListenerAdapter() {
            public void onAnimationEnd(Animator animation) {
                view.setVisibility(View.GONE);
            }
        });
        animator.start();
    }

    private ValueAnimator createDropAnimator(
            final View view, int start, int end) {
        ValueAnimator animator = ValueAnimator.ofInt(start, end);
        animator.addUpdateListener(
                new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                int value = (Integer) valueAnimator.getAnimatedValue();
                ViewGroup.LayoutParams layoutParams =
                        view.getLayoutParams();
                layoutParams.height = value;
                view.setLayoutParams(layoutParams);
            }
        });
        return animator;
    }
}

其中:AnimatorUpdateListener 中监听的是数值变换,从而完成动画的变换;一个完整的动画具有 Start、Repeat、End 、Cancel 四个过程,通过 AnimatorListener 接口可以很方便地监听到这四个事件。AnimatorListener 接口的源码如下:

  public interface AnimatorListener {
        void onAnimationStart(Animator var1);

        void onAnimationEnd(Animator var1);

        void onAnimationCancel(Animator var1);

        void onAnimationRepeat(Animator var1);
    }

但是,大部分的时候,我们都只关心 onAnimationEnd 事件,所以 Android 也提供了一个 AnimatorListenerAdapter 来让我们选择必要的事件进行监听,如文中的代码:

    animator.addListener(new AnimatorListenerAdapter() {
            public void onAnimationEnd(Animator animation) {
                view.setVisibility(View.GONE);
            }
        });



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值