第4章 CustomView属性动画进阶

一、PropertyValuesHolder与Keyframe

ValueAnimator和ObjectAnimator除了ofInt()、ofFloat()、ofObject()函数外,还都有一个函数。

// ValueAnimator的
public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values)

// ObjectAnimator的
public static ObjectAnimator ofPropertyValuesHolder(Object target, PropertyValuesHolder... values)

它们的使用方法都差不多,但相比而言,ValueAnimator的使用机会不多,所以,这里只讲ObjectAnimator中的ofPropertyValuesHolders()函数的用法。

PropertyValuesHolder:

1.概述

PropertyValuesHolder类的含义就是,它其中保存了动画过程中所需要操作的属性和对应的值。

我们通过ofFloat(Object target, String propertyName, float... values)构造的动画,ofFloat()函数的内部实现其实就是传入的参数封装成PropertyValuesHolder实例来保存动画状态的。在封装成PropertyValuesHolder实例以后,后期的各种操作也是以PropertyValuesHolder为主的。

所以,ObjectAnimator通过暴露PropertyValuesHolder的方法,向我们提供了一个口子,让我们可以通过PropertyValuesHolder来构造动画。

创建PropertyValuesHolder实例的函数有以下几个(针对API Level 11):

public static PropertyValuesHolder ofFloat(String propertyName, float... values)
public static PropertyValuesHolder ofInt(String propertyName, int... values)
public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator, Object... values)
public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values)

2.PropertyValuesHolder之ofFloat()、ofInt()

1)ofFloat()、ofInt()

● propertyName:表示ObjectAnimator需要操作的属性名。即ObjectAnimator需要通过反射查找对应属性的setProperty()函数。

● values:属性所对应的参数,可以指定多个。如果只指定了一个,那么ObjectAnimator会通过查找对应属性的getProperty()函数来获得初始值。

2)ObjectAnimator.ofPropertyValuesHolder()

前面提到,ObjectAnimator给我们提供了一个设置PropertyValuesHolder实例的入口。

public static ObjectAnimator ofPropertyValuesHolder(Object target, PropertyValuesHolder... values)
● target:需要执行动画的控件
● values:可以传入多个PropertyValuesHolder实例。由于每个PropertyValuesHolder实例都会针对一个属性执行动画操作,
          所以,如果传入多个PropertyValuesHolder实例,则会对控件的多个属性同时执行动画操作。
PropertyValuesHolder rotationHolder = PropertyValuesHolder.ofFloat("Rotation", 60f, -60f, 40f, -40f, -20f, 20f, 10f, -10f, 0f);
PropertyValuesHolder alphaHolder = PropertyValuesHolder.ofFloat("alpha", 0.1f, 1f, 0.1f, 1f);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mTv, rotationHolder, alphaHolder);
animator.setDuration(3000);
animator.start();

3.PropertyValuesHolder.ofObject()

public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator, Object... values)
● propertyName:ObjectAnimator动画操作的属性名
● evaluator:Evaluator实例。它是根据当前动画进度计算出当前值的类。可以使用系统自带的IntEvaluator、FloatEvaluator,也可以自定义
● values:可变长参数,表示操作动画属性的值
——————————————————————————————————————————————————————————————————————————————————————
它与ObjectAnimator.ofObject()函数参数类似,只是少了个target参数:
public static ObjectAnimator ofObject(Object target, String propertyName, TypeEvaluator evaluator, Object... values)
public class CharEvaluator implements TypeEvaluator<Character> {
    @Override
    public Character evaluate(float fraction, Character startValue, Character endValue) {
        int startInt = (int) startValue;
        int endInt = (int) endValue;
        int curInt = (int) (startInt + fraction * (endInt - startInt));
        char result = (char) curInt;
        return result;
    }
}
public class MyTextView extends TextView {
    public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public void setCharText(Character character) {
        setText(String.valueOf(character));
    }
}
PropertyValuesHolder charHolder = PropertyValuesHolder.ofObject("CharText", new CharEvaluator(), new Character('A'), new Character('Z'));
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(myTv, charHolder);
animator.setDuration(3000);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();

Keyframe:

1.概述

要想控制动画速率的变化,可以通过自定义插值器,也可以通过自定义Evaluator来实现。但让我们为了速率变化效果而自定义插值器或者Evaluator,因为大部分涉及数学知识,恐怕也不简单。

为了解决方便地控制动画速率的问题,有一个Keyframe类,直译过来就是“关键帧”。

通过分析Flash动画的制作原理,可以知道,一个关键帧必须包含两个元素:时间点和位置。即这个关键帧表示的是某个物体在哪个时间点应该在哪个位置上。

public static Keyframe ofFloat(float fraction, float value)
● fraction:表示当前的显示进度,即在插值器中getInterpolation()函数的返回值
● value:表示动画当前所在的数值位置

比如: 

Keyframe.ofFloat(0, 0):动画进度为0时,动画所在的数值位置为0;
Keyframe.ofFloat(0.25f, -20f):动画进度为25%时,动画所在的数值位置为-20;
Keyframe.ofFloat(1f, 0):动画结束时,动画所在的数值位置为0。

我们再来看看PropertyValuesHolder是如何使用Keyframe对象的。

public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values)
● propertyName:动画所要操作的属性名
● values:Keyframe的列表。PropertyValuesHolder会根据每个Keyframe的设定,定时将指定的值输出给动画

2.示例

第一步,生成Keyframe对象。

第二步,利用PropertyValuesHolder.ofKeyframe()函数生成PropertyValuesHolder对象。

第三步,利用ObjectAnimator.ofPropertyValuesHolder()函数生成对应的Animator。

Keyframe frame0 = Keyframe.ofFloat(0f,0);
Keyframe framel = Keyframe.ofFloat(0.1f,-20f);
Keyframe frame2 = Keyframe.ofFloat(0.2f,20f);
Keyframe frame3 = Keyframe.ofFloat(0.3f,-20f);
Keyframe frame4 = Keyframe.ofFloat(0.4f,20f);
Keyframe frame5 = Keyframe.ofFloat(0.5f,-20f);
Keyframe frame6 = Keyframe.ofFloat(0.6f,20f);
Keyframe frame7 = Keyframe.ofFloat(0.7f,-20f);
Keyframe frame8 = Keyframe.ofFloat(0.8f,20f);
Keyframe frame9 = Keyframe.ofFloat(0.9f,-20f);
Keyframe framel0 = Keyframe.ofFloat(1,0);
PropertyValuesHolder frameHolder = PropertyValuesHolder.ofKeyframe("rotation",frame0,framel,frame2,frame3,frame4,frame5,frame6,frame7,frame8,frame9,frame10);
Animator animator = ObjectAnimator.ofPropertyValuesHolder(mImg,frameHolder);
animator.setDuration(l000);
animator.start();

3.ofFloat与ofInt

public static Keyframe ofFloat(float fraction)
public static Keyframe ofFloat(float fraction, float value)

public static Keyframe ofInt(float fraction)
public static Keyframe ofInt(float fraction, int value)

public void setFraction(float fraction)
public void setValue(Object value)

4.插值器

Keyframe也允许我们在Keyframe动作期间设置插值器。

Keyframe frame0 = Keyframe.ofFloat(0f, 0);
Keyframe frame1 = Keyframe.ofFloat(0.1f, -20f);
frame1.setInterpolator(new BounceInterpolator());
Keyframe frame2 = Keyframe.ofFloat(1f, 20f);
frame2.setInterpolator(new LinearInterpolator());

效果为:

从frame0到frame1的中间值计算过程中,使用回弹插值器(进度从0到0.1);

从frame1到frame2的中间值计算过程中,使用线性插值器(进度从0.1到1)。

很显然,给frame0设置插值器是无效的,因为它是第一帧。

5.Keyframe之ofObject

public static Keyframe ofObject(float fraction)
public static Keyframe ofObject(float fraction, Object value)
Keyframe frame0 = Keyframe.ofObject(0f,new Character('A'));
Keyframe frame1 = Keyframe.ofObject(0.1f,new Character('L'));
Keyframe frame2 = Keyframe.ofObject(1,new Character('Z'));

PropertyValuesHolder frameHolder = PropertyValuesHolder.ofKeyframe("CharText", frame0, frame1, frame2);
frameHolder.setEvaluator(new CharEvaluator());
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(myTv, frameHolder);
animator.setDuration(3000);
animator.start();

再次强调:在使用ofObject()函数来制作动画的时候,必须调用frameHolder.setEvaluator()函数显示设置Evaluator,因为系统根本无法知道动画中的中间值Object真正是什么类型的。

6.疑问:如果没有进度为0或1时的关键帧,则会怎样

● 如果去掉第0帧,则将以第一个关键帧为起始位置。

● 如果去掉结束帧,则将以最后一个关键帧为结束位置。

● 使用Keyframe来构建动画,至少要有2帧,否则报错(IndexOutOfBoundsException)。

PropertyValuesHolder之其他函数:

public void setEvaluator(TypeEvaluator evaluator)
// 用于设置ofFloat()所对应的动画值列表
public void setFloatValues(float... values)
public void setIntValues(int... values)
public void setObjectValues(Object... values)
// 用于设置ofKeyframe()所对应的动画值列表
public void setKeyframes(Keyframe... values)
// 设置动画属性名
public void setPropertyName(String propertyName)
Keyframe frame0 = Keyframe.ofFloat(0f, 0);
Keyframe frame1 = Keyframe.ofFloat(0.1f, -20f);
Keyframe frame2 = Keyframe.ofFloat(0.2f, 20f);
// 省略
PropertyValuesHolder frameHolder1 = PropertyValuesHolder.ofKeyframe("rotation", frame0, frame1, frame2, frame3, frame4, frame5, frame6, frame7, frame8, frame9, frame10);

Keyframe scaleXframe0 = Keyframe.ofFloat(0f, 1);
Keyframe scaleXframe1 = Keyframe.ofFloat(0.1f, 1.1f);
Keyframe scaleXframe9 = Keyframe.ofFloat(0.9f, 1.1f);
Keyframe scaleXframe10 = Keyframe.ofFloat(1, 1);
PropertyValuesHolder frameHolder2 = PropertyValuesHolder.ofKeyframe("ScaleX", scaleXframe0, scaleXframe1, scaleXframe9, scaleXframe10);

Keyframe scaleYframe0 = Keyframe.ofFloat(0f, 1);
Keyframe scaleYframe1 = Keyframe.ofFloat(0.1f, 1.1f);
Keyframe scaleYframe9 = Keyframe.ofFloat(0.9f, 1.1f);
Keyframe scaleYframe10 = Keyframe.ofFloat(1, 1);
PropertyValuesHolder frameHolder3 = PropertyValuesHolder.ofKeyframe("ScaleY", scaleYframe0, scaleYframe1, scaleYframe9, scaleYframe10);

Animator animator = ObjectAnimator.ofPropertyValuesHolder(mImage, frameHolder1, frameHolder2, frameHolder3);
animator.setDuration(1000);
animator.start();

二、ViewPropertyAnimator

Android3.1补充了ViewPropertyAnimator这个机制,让对View的操作更加便捷。

之前让一个TextView从常规状态变成透明状态:

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 0f);
animator.start();

ViewPropertyViewAnimator提供了更加易懂、更加面向对象的API,如下:

textview.animate().alpha(0f);

将控件移动到点(50, 100):

myView.animate().x(50).y(100);

● animate():整个系统从调用View的这个叫作animate()的新函数开始。这个函数会返回一个ViewePropertyAnimator对象,可以通过调用这个对象的函数来设置需要实现动画的属性。

● 自动开始:不用显示调用start()函数。在新API中,启动动画是隐式的,在声明完成后,动画就开始了。这里有一个细节,就是这些动画实际上会在下一次界面刷新时启动,ViewPropertyAnimator正是通过这个机制来将所有动画结合在一起的。如果你继续声明动画,它就会继续将这些动画添加到将在下一帧开始的动画列表中。而当你声明完毕并结束对UI线程的控制之后,事件队列机制开始起作用,动画也就开始了。

● 流畅(Fluent):ViewPropertyAnimator拥有一个流畅的接口(Fluent Interface),它允许将多个函数调用很自然地串在一起,并把一个多属性的动画写成一行代码。所有的调用(如x()、y())都会返回一个ViewPropertyAnimator实例。

常用函数:

1.属性设置

findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        tv1.animate().scaleY(2);
        tv2.animate().scaleYBy(2);
    }
});

2.设置监听器

虽然ViewPropertyAnimator并非派生自Animator,但它仍允许我们设置Animator.AnimatorListener监听器。

tv.animate().scaleX(2).scaleY(2).setListener(new Animator.AnimatorListener() {
    public void onAnimationStart(Animator animation) {
    }
    public void onAnimationEnd(Animator animation) {
    }
    public void onAnimationRepeat(Animator animation) {
    }
});
    

性能考量:

ViewPropertyAnimator并没有像ObjectAnimator一样使用反射或者JNI技术;而ViewPropertyAnimator会根据预设的每一个动画帧计算出对应的所有属性值,并设置给控件,然后调用一次invalidate()函数进行重绘,从而解决了在使用ObjectAnimator时每个属性单独计算、单独重绘的问题。所以ViewPropertyAnimator相对于ObjectAnimator 和组合动画,性能有所提升。

以往要实现一个组合动画,要么采用AnimatorSet实现:

ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

要么使用PropertyValuesHolder实现:

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY).start();

而使用ViewPropertyAnimator就可以直接避开这些烦琐的组合动画构造过程 。

使用ObjectAnimator时并不需要太过担心性能,使用反射和 JNI 等技术所带来的开销相对于整个程序来讲都是微不足道的。使用 ViewPropertyAnimator最大的优势也不在于性能的提升,而是它提供的简明易读的代码书写方式。

三、为ViewGroup内的组件添加动画

ViewAnimator、ObjectAnimator、AnimatorSet 都只能针对一个控件做动画。如果我们想对ViewGroup内部控件做统一入场动画、出场动画等,比如,对listview中的每个item在入场时添加动画、在出场时添加动画、在数据变更新时添加动画,那么使用ViewAnimator、ObjectAnimator、AnimatorSet是无法实现的。

为ViewGroup内的组件添加动画,Android共提供了4种方法:

1.layoutAnimation标签与LayoutAnimationController

layoutAnimation它专门针对listview添加入场动画所使用的。LayoutAnimationController是它的代码实现。它可以实现在listview创建时对其中每个item添加入场动画,而且动画可以自定义。然而,在listview创建完成后,如果再添加数据,则新数据是不会有入场动画的。

2.gridLayoutAnimation标签与GridLayoutAnimationController

gridLayoutAnimation它专门针对gridview添加入场动画所使用的。GridLayoutAnimationController是它的代码实现。它可以实现在gridview创建时对其中每个item添加入场动画,而且动画可以自定义。动画只会在gridview初次创建时出现,如再添加数据,新添加数据不会有入场动画。

3.android:animateLayoutChanges属性

android:animateLayoutChanges="true/false",所有派生自ViewGroup类的控件都具有此属性。只要在XML中添加这个属性,就能实现在添加/删除其中的控件时带有默认动画。遗憾的是,动画不能自定义。

4.LayoutTransition

它可以实现在ViewGroup动态添加或删除其中的控件时指定动画,动画可以自定义。

对比以上4种方法,LayoutTransition是最强大的,这也是这里要介绍的重点。

animateLayoutChanges属性:

添加、删除控件的方法如下:

private void addButtonView() {
    i++;
    Button button = new Button(this);
    button.setText("button" + i);
    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    button.setLayoutParams(params);
    linearLayoutContainer.addView(button, 0);
}

private void removeButtonView() {
    if (i > 0) {
        linearLayoutContainer.removeViewAt(0);
    }
    i--;
}

因为在添加控件时,总是将按钮添加为第一个元素,所以,在删除控件时,也只需将第一个元素删除即可。

android:animateLayoutChanges="false"/"true",效果如下左/右:

    

LayoutTransition:

使用LayoutTransition非常容易,只需要三步:

// 第一步,创建实例
LayoutTransition transitioner = new LayoutTransition();
// 第二步,创建动画并进行设置
ObjectAnimator animOut = ObjectAnimator.ofFloat(null, "rotation", 0f, 90f, 0f);
transitioner.setAnimator(LayoutTransition.DISAPPEARING, animOut);
// 第三步,将LayoutTransition设置到ViewGroup中
linearLayout.setLayoutTransition(transitioner);
public void setAnimator(int transitionType, Animator animator)
其中,int transitionType表示当前应用动画的对象范围
● APPEARING:元素在容器中出现时所定义的动画
● DISAPPEARING:元素在容器中消失时所定义的动画
● CHANGE_APPEARING:由于容器中要显现一个新的元素,其他需要变化的元素所应用的动画
● CHANGE_DISAPPEARING:当容器中某个元素消失时,其他需要变化的元素所应用的动画
其中,Animator animator表示当前所选范围的控件所使用的动画

1.LayoutTransition.APPEARING与LayoutTransition.DISAPPEARING 

public void onCreate(Bundle savedinstanceState) {
    super.onCreate(savedinstanceState) ;
    setContentView(R.layout.animate_layout_changes_activity);
    linearLayoutContainer = (LinearLayout) findViewByid(R.id.linearlayoutcontainer);
    LayoutTransition transition = new LayoutTransition();
    // 入场动画:view在这个容器中显示时触发的动画
    ObjectAnimator animIn = ObjectAnimator.ofFloat(null, "rotationY", 0f, 360f, 0f);
    transition.setAnimator(LayoutTransition.APPEARING, animIn);
    // 出场动画:view消失时的动画
    ObjectAnimator animOut = ObjectAnimator.ofFloat (null, "rotation", 0f, 90f, 0f);
    transition.setAnimator(LayoutTransition.DISAPPEARING, animOut);
    linearLayoutContainer.setLayoutTransition(transition);
    findViewById(R.id.add_btn).setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {
            addButtonView();
    });
    findViewByid(R.id.remove_btn).setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {
            removeButtonView();
        }
    });
}

2.LayoutTransition.CHANGE_APPEARING

LayoutTransition transition = new LayoutTransition();

PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0,0);
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt ("top", 0,0);
PropertyValuesHolder pvhScaleX = PropertyValuesHolder.ofFloat ("scaleX", 1f, 0f, 1f);
Animator changeAppearAnimator = ObjectAnimator.ofPropertyValuesHolder(linearLayoutContainer, pvhLeft, pvhTop, pvhScaleX);
transition.setAnimator(LayoutTransition.CHANGE_APPEARING, changeAppearAnimator);

linearLayoutContainer.setLayoutTransition(transition);

有几点要注意:

(1)LayoutTransition.CHANGE_APPEARING和LayoutTransition.CHANGE_DISAPPEARING必须使用PropertyValuesHolder所构造的动画才会有效果。也就是说,使用ObjectAnimator构造的动画,在这里是不会有效果的。

(2)在构造PropertyValuesHolder动画时,"left"、"top"属性的变动是必写的!如果不需要变动,则直接写为:

PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 0);
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 0);

(3)在构造PropertyValuesHolder动画时,所使用的ofInt()、ofFloat()函数中的参数值,第一个值和最后一个值必须相同;否则此属性所对应的动画将被放弃,在此属性上将不会有效果。

PropertyValuesHolder pvhScaleX = PropertyValuesHolder.ofFloat("scaleX", 1f, 0f, 1f);
如果改为ofFloat("scaleX", 0f, 1f),由于首尾不一致,将被视为无效参数,不会有效果。

(4)在构造PropertyValuesHolder动画时,所使用的ofInt()、ofFloat()函数中,如果所有参数值都相同,将不会有动画效果。

PropertyValuesHolder pvhScaleX = PropertyValuesHolder.ofFloat("scaleX", 100f, 100f);
所有参数相同,所以scaleX属性上的这个动画被放弃,在scaleX属性上也不会应用任何动画。

3.LayoutTransition.CHANGE_DISAPPEARING

<LinearLayout
    android:id="@+id/linearlayoutcontainer"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:animateLayoutChanges="false"
    android:orientation="vertical"/>
LayoutTransition transition = new LayoutTransition();
PropertyValuesHolder outLeft = PropertyValuesHolder.ofInt("left",0,0);
PropertyValuesHolder outTop = PropertyValuesHolder.ofInt("top",0,0);
Keyframe frame0 = Keyframe.ofFloat(0f, 0);
Keyframe framel = Keyframe.ofFloat(0.1f, -20f);
Keyframe frame2 = Keyframe.ofFloat(0.2f, 20f);
Keyframe frame3 = Keyframe.ofFloat(0.3f, -20f);
Keyframe frame4 = Keyframe.ofFloat(0.4f, 20f);
Keyframe frame5 = Keyframe.ofFloat(0.5f, -20f);
Keyframe frame6 = Keyframe.ofFloat(0.6f, 20f) ;
Keyframe frame7 = Keyframe.ofFloat(0.7f, -20f);
Keyframe frame8 = Keyframe.ofFloat(0.8f, 20f);
Keyframe frame9 = Keyframe.ofFloat(0.9f, -20f);
Keyframe frame10 = Keyframe.ofFloat(1, 0);

PropertyValuesHolder mPropertyValuesHolder = PropertyValuesHolder.ofKeyframe("rotation", frame0, frame1, frame2, frame3, frame4, frame5, frame6, frame7, frame8, frame9,frame10);

ObjectAnimator mObjectAnimatorChangeDisAppearing = ObjectAnimator.ofPropertyValuesHolder(this, outLeft, outTop, mPropertyValuesHolder);

transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, mObjectAnimatorChangeDisAppearing);

linearLayoutContainer.setLayoutTransition(transition);

其他函数:

1.基本设置

// 设置所有动画完成所需要的时长
public void setDuration(long duration)
// 针对单个Type设置动画时长(transitionType=APPEARING/DISAPPEARING/CHANGE_APPEARING/CHANGE_DISAPPEARING)
public void setDuraton(int transitionType, long duration)
// 针对单个Type设置插值器
public void setInterpolator(int transitionType, TimeInterpolator interpolator)
// 针对单个Type设置动画延时
public void setStartDelay(int transitionType, long delay)
// 针对单个Type设置每个子item动画的时间间隔,即各个item间做动画的间隔
public void setStagger(int transitionType, long duration)

2.设置监听

public void addTransitionListener(TransitionListener listener)
// 其中:
public interface TransitionListener {
    public void startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType);
    public void endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType);
}
● transition:当前LayoutTransition实例
● container:当前应用LayoutTransition的容器
● view:当前在做动画的View对象
● transitionType:当前LayoutTransition类型

在任何类型的LayoutTransition开始和结束时,都会调用TransitionListener的startTransition()和endTransition()函数。

在上面示例中,给transiton添加addTransitionListener监听,打印日志:

(父容器是LinearLayout、添加/删除的控件是Button)

transition.addTransitionListener(new LayoutTransition.TransitionListener() {
    @Override
    public void startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
        Log.d("TAG", "start:" + "transitionType=" +  transitionType + " , count=" + container.getChildCount() + " , view=" + view.getClass().getName());
    }
    @Override
    public void endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
        Log.d("TAG", "  end:" + "transitionType=" +  transitionType + " , count=" + container.getChildCount() + " , view=" + view.getClass().getName());
    }
});

点击“添加控件”,再点击“移除控件”: 

D/TAG: start:transitionType=APPEARING ,           count=0 , view=android.widget.Button
D/TAG: start:transitionType=CHANGE_APPEARING ,    count=2 , view=android.widget.LinearLayout
D/TAG:   end:transitionType=APPEARING ,           count=1 , view=android.widget.Button
D/TAG:   end:transitionType=CHANGE_APPEARING ,    count=2 , view=android.widget.LinearLayout
D/TAG: start:transitionType=DISAPPEARING ,        count=1 , view=android.widget.Button
D/TAG: start:transitionType=CHANGE_DISAPPEARING , count=2 , view=android.widget.LinearLayout
D/TAG:   end:transitionType=DISAPPEARING ,        count=0 , view=android.widget.Button
D/TAG:   end:transitionType=CHANGE_DISAPPEARING , count=2 , view=android.widget.LinearLayout
各transitionType取值对应为:
CHANGE_APPEARING = 0
CHANGE_DISAPPEARING = 1
APPEARING = 2
DISAPPEARING = 3

在添加/删除控件时,先出现startTransition()函数回调,再出现endTransition()函数回调。

APPEARING/DISAPPEARING事件所对应的View是控件;CHANGE_APPEARING/CHANGE_DISAPPEARING事件所对应的View是容器。

这是因为,在添加/删除控件时,APPEARING/DISAPPEARING事件只针对当前被添加/删除的控件做动画,所以返回的View是当前被添加/删除的控件;而CHANGE_APPEARING/CHANGE_DISAPPEARING事件针对容器中所有已经存在的控件做动画,所以返回的View是容器。

四、开源动画库NineOldAndroids

NineOldAndroids是一个可以在任意Android版本上使用的Animation API,和Android3.0中的API类似。

NineOldAndroids动画库的官网地址为:http://nineoldandroids.com

常用类有ObjectAnimator、ValueAnimator、AnimatorSet、PropertyValuesHolder、Keyframe、ViewPropertyAnimator和ViewHelper。

NineOldAndroids动画库中没有LayoutTransition,除Android3.0动画API外,NineOldAndroids还提供了一个动画类ViewHelper。

NineOldAndroids中的ViewPropertyAnimator:

NineOldAndroids也引入了ViewPropertyAnimator,唯一不同的是animate()函数,NineOldAndroids中提供了ViewPropertyAnimator.animate(mView)函数与其对应。

// 官方API (3.1以上)
mView.animate().setDuration(5000).rotationY(720).x(100).y(100).start();
// NineOldAndroids
ViewPropertyAnimator.animate(mView).setDuration(5000).rotationY(720).x(100).y(100).start();

NineOldAndroids中的ViewHelper:

1.概述

ViewHelper提供了一系列静态set/get函数去操作View的各种属性,比如透明度、偏移量、旋转角度等,大大方便了我们的使用,而且无须考虑低版本的兼容性问题。

ViewHelper的成员函数有:

public static float getAlpha(View view)
public static void setAlpha(View view, float alpha)

public static void setPivotX(View view, float pivotX)
public static float getPivotX(View view)

public static void setPivotY(View view, float pivotY)
public static float getPivotY(View view)

public static void setRotation(View view, float rotation)
public static float getRotation(View view)

public static void setRotationX(View view, float rotationX)
public static float getRotationX(View view)

public static void setRotationY(View view, float rotationY)
public static float getRotationY(View view)

public static void setScaleX(View view, float scaleX)
public static float getScaleX(View view)

public static void setScaleY(View view, float scaleY)
public static float getScaleY(View view)

public static void setScrollX(View view, int scrollX)
public static float getScrollX(View view)

public static void setScrollY(View view, int scrollY)
public static float getScrollY(View view)

public static void setTranslationX(View view, float translationX)
public static float getTranslationX(View view)

public static void setTranslationY(View view, float translationY)
public static float getTranslationY(View view)

public static void setX(View view, float x)
public static float getX(View view)

public static void setY(View view, float y)
public static float getY(View view)

ViewHelper是如何做到完美兼容各个Android版本的呢?下面简单剖析一下原理。

ViewHelper类源码中的setAlpha()函数:

public static void setAlpha(View view, float alpha) {
    if (NEEDS_PROXY) {
        wrap(view).setAlpha(alpha);
    } else {
        Honeycomb.setAlpha(view, alpha);
    }
}

NEEDS_PROXY是一个静态常量,在程序初始化的时候被赋值,当手机API < 11时被赋值为true,否则赋值为false。

public static final boolean NEEDS_PROXY = Integer.valueOf(Build.VERSION_SDK).intValue() < Build.VERSION_CODES.HONEYCOMB;

这里的Honeycomb.setAlpha(view, alpha)意思就是调用view.setAlpha(alpha)函数来设置透明度。而当 API 小于11时,会进入if语句,由于view.setAlpha(alpha)函数是在 API 11 以后引入的,所以需要先调用AnimatorProxy类中的wrap() 函数对View进行包装,然后再设置透明度。
需要注意的是,ViewHelper与属性动画一样,也是通过改变控件的自有属性来改变控件的各项值的 。所以,如果控件移动,那么控件也是会在新位置响应单击事件的 。

2.示例

final TextView tv = (TextView) findViewById(R.id.tv);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ValueAnimator animator = ValueAnimator.ofFloat(0, 200);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                Float cur = (Float) valueAnimator.getAnimatedValue();
                ViewHelper.setTranslationX(tv, cur);
                ViewHelper.setTranslationY(tv, cur);
            }
        });
        animator.setDuration(2000);
        animator.start();
    }
});
tv.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeToast(ViewHelperActivity.this, "单击了tv", Toast.LENGTH_SHORT).show();
    }
});

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

itzyjr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值