Property Animation是一个很强劲的动画框架,几乎可以为所有的事物加上动画效果。
相关API
Duration:动画的持续时间,默认300ms
Time interpolation:时间差值,定义动画的变化率
Repeat count and behavior:重复次数,以及重复模式;可以定义重复多少次,重复时是从头开始还是反向
Animator set:动画集合,可以定义一组动画,一起执行或者顺序执行
Frame refresh delay:帧刷新延迟,多久刷新一次帧,默认为10ms。但最终依赖系统的当前状态,基本不用管
objectAnimator:动画的执行类
ValueAnimator:动画的执行类
AnimatorSet:用于控制一组动画的执行:线性,一起,每个动画的先后执行等
AnimatorInflater:用于加载属性动画的xml文件
TypeEvalutor:类型估值,主要用于设置动画操作属性的值
TimeInterpolator:时间插值
总得来说,属性动画就是,动画的执行类来设置动画操作的对象的属性,持续时间,开始和结束的属性值,时间差值等,然后系统会根据设置的参数动态的变化对象的属性
1,ObjectAnimator实现动画
先看一个最简单的
public void startAnimation(View view){
ObjectAnimator
.ofFloat(view,"rotationX",0.0f,360.0f)
.setDuration(1000)
.start();
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.asus1.testpropertyanimation.MainActivity">
<ImageView
android:layout_width="200dp"
android:layout_height="200dp"
android:id="@+id/iv_image"
android:layout_centerInParent="true"
android:src="@drawable/bg"
android:onClick="startAnimation"
/>
</RelativeLayout>
对于ObjectAnimator
1,提供了ofInt,ofFloat,ofObject,这几个方法都是设置动画动作的元素,作用的属性,动画开始,结束,以及中间的任意个属性。
当对于属性值,只设置一个的时候,会认为当前对象该属性值的开始 (getPropName反射获取),如果设置两个,则一个为开始,一个为结束
2,ObjectAnimator让我们动画的过程变简单,当我们不需要去实现ValueAnimator.AnimatorUpdateListener,因为该对象的动画属性会自动更新
3,ObjectAnimator会自动更新动画,因此必须有一个setter方法,去访问这个属性
关于这个setter方法,我们来看看
public void ViewSetter(View view){
ViewWrap wrap = new ViewWrap(mBall);
ObjectAnimator.ofFloat(wrap,"width",500)
.setDuration(5000)
.start();
}
public class ViewWrap{
private View mTarget;
public ViewWrap(View view){
mTarget = view;
}
public int getWdith(){
return mTarget.getLayoutParams().width;
}
public void setWidth(float width){
mTarget.getLayoutParams().width = (int) width;
mTarget.requestLayout();
}
}
如果我们用渐变动画来:
ScaleAnimation scaleAnimation = new
ScaleAnimation(1.0f,3.0f,1.0f,1.0f,
Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
scaleAnimation.setDuration(5000);
mBall.startAnimation(scaleAnimation);
我们可以看到,它的里面的内容也在发生改变,而且还超出了屏幕范围
这样就不好看
注意一个地方,我们如果直接对Button的width属性做动画是没有效果的,虽然Button内部提供了getWidth和setWidth方法,但是这个setWidth并不是改变视图得出大小,它是TextView新增的方法,View是没有这个setWidth方法的,由于Button是继承了TextView,所以Button也就有了setWidth方法
我们看看源码:
/**
* Makes the TextView exactly this many pixels wide.
* You could do the same thing by specifying this number in the
* LayoutParams.
*
* @see #setMaxWidth(int)
* @see #setMinWidth(int)
* @see #getMinWidth()
* @see #getMaxWidth()
*
* @attr ref android.R.styleable#TextView_width
*/
@android.view.RemotableViewMethod
public void setWidth(int pixels) {
mMaxWidth = mMinWidth = pixels;
mMaxWidthMode = mMinWidthMode = PIXELS;
requestLayout();
invalidate();
}
我们看到它是用来设置TextView的最小宽度和最大宽度的,这个和TextView的宽度不是一个东西
具体来说,TextView的宽度对应Xml文件中的layout_width属性,而android:width属性就是对应setWdith方法的
在官方文档中,对于ObjectAnimator有这种解决方式:
- 给你的对象加上get和set方法,如果你有权限的话
- 用一个类来包装原始对象,间接为其提供get和set方法
- 采用ValueAnimator,监听动画过程,自己实现属性的改变
如果我们想要实现多个动画效果用ObejctAnimator
我们可以这样
ObjectAnimator animator = ObjectAnimator
.ofFloat(view,"foo",0.0f,1.0f)
.setDuration(2000)
;
animator.start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float value = (float)valueAnimator.getAnimatedValue();
view.setAlpha(value);
view.setScaleX(value);
}
});
关于设置属性的那个字符串,可以随便写一个,也就是不管,我们只需要按照时间插值和持续时间计算的那个值,自己手动调用
4,我们还可以使用propertyValuesHolder
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha",0.0f,1.0f);
PropertyValuesHolder scale = PropertyValuesHolder.ofFloat("scaleX",0.0f,1.0f);
PropertyValuesHolder rotate = PropertyValuesHolder.ofFloat("rotationX",0.0f,360.0f);
ObjectAnimator.ofPropertyValuesHolder(view,alpha,scale,rotate).setDuration(2000)
.start();
效果:
ValueAnimator
其实ObjectAnimator是ValueAnimator的子类,所以两个的用法很相似
public void runVertical(final View view){
ValueAnimator valueAnimator =
ValueAnimator.ofFloat(0.0f,mLaout.getHeight()-mBall.getHeight());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float verticalValue = (float)valueAnimator.getAnimatedValue();
view.setTranslationY(verticalValue);
}
});
valueAnimator.setDuration(3000);
valueAnimator.setInterpolator(new AccelerateInterpolator());
valueAnimator.start();
}
public void paowuxian(View view){
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setDuration(5000);
valueAnimator.setTarget(mBall2);
valueAnimator.setInterpolator(new AccelerateInterpolator());
valueAnimator.setObjectValues(new PointF(0,0));
valueAnimator.setEvaluator(new TypeEvaluator() {
@Override
public Object evaluate(float v, Object o, Object t1) {
PointF pointF = new PointF();
pointF.set(200*v*3,(float)(0.5*200*v*v*9));
return pointF;
}
});
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
PointF pointF = (PointF)valueAnimator.getAnimatedValue();
mBall2.setX(pointF.x);
mBall2.setY(pointF.y);
}
});
valueAnimator.start();
}
我们可以看到,ValueAnimation是没有指定属性的,因此不需要操作的对象的属性一定有getter和setter方法,我们可以根据当前动画的计算值,来做操作
在做抛物线运动的时候,因为需要两个值来确定位置,x,y。所以我们用了TypeEvaluator,实现它的evluate方法,通过这个方法,我们可以返回当前动画点的自己需要的动画值
监听动画的事件
对于动画,一般都是一些辅助效果,比如要删除个元素,希望一个淡出的效果,但是最终还是删掉,并不是透明度没有了,还占着位置,所以我们需要知道动画如何结束
public void fadeOut(View view)
{
ObjectAnimator anim = ObjectAnimator.ofFloat(mBlueBall, "alpha", 0.5f);
anim.addListener(new AnimatorListener()
{
@Override
public void onAnimationStart(Animator animation)
{
Log.e(TAG, "onAnimationStart");
}
@Override
public void onAnimationRepeat(Animator animation)
{
// TODO Auto-generated method stub
Log.e(TAG, "onAnimationRepeat");
}
@Override
public void onAnimationEnd(Animator animation)
{
Log.e(TAG, "onAnimationEnd");
ViewGroup parent = (ViewGroup) mBlueBall.getParent();
if (parent != null)
parent.removeView(mBlueBall);
}
@Override
public void onAnimationCancel(Animator animation)
{
// TODO Auto-generated method stub
Log.e(TAG, "onAnimationCancel");
}
});
anim.start();
}
这样就可以监听动画的开始、结束、被取消、重复等事件~但是有时候会觉得,我只要知道结束就行了,这么长的代码我不能接收,那你可以使用
anim.addListener(new AnimatorListenerAdapter()
{
@Override
public void onAnimationEnd(Animator animation)
{
Log.e(TAG, "onAnimationEnd");
ViewGroup parent = (ViewGroup) mBlueBall.getParent();
if (parent != null)
parent.removeView(mBlueBall);
}
});
AnimatorListenerAdapter继承了AnimatorListener接口,然后空实现了所有的方法~
animator还有cancel()和end()方法:cancel动画立即停止,停在当前的位置,end动画直接到最终状态
AnimatorSet使用
首先是几个动画组合在一起
public void playTogether(View view){
AnimatorSet set = new AnimatorSet();
ObjectAnimator alpha = ObjectAnimator.ofFloat(view,"alpha",1.0f,0.0f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view,"scaleX",1.0f,0.0f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view,"scaleY",1.0f,0.0f);
ObjectAnimator translation =
ObjectAnimator.ofFloat(view,"translationX",0,mLaout.getWidth()/3*2);
set.playTogether(alpha,scaleX,scaleY,translation);
set.setDuration(5000);
set.start();
}
然后我们来试试先后动画
public void playAfter(View view){
AnimatorSet set = new AnimatorSet();
ObjectAnimator alpha = ObjectAnimator.ofFloat(view,"alpha",1.0f,0.3f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view,"scaleX",1.0f,0.3f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view,"scaleY",1.0f,0.3f);
ObjectAnimator translation =
ObjectAnimator.ofFloat(view,"translationX",0,mLaout.getWidth()/3);
ObjectAnimator alphaAfter = ObjectAnimator.ofFloat(view,"alpha",0.3f,1.0f);
ObjectAnimator scaleXAfter = ObjectAnimator.ofFloat(view,"scaleX",0.3f,1.5f);
ObjectAnimator scaleYAfter = ObjectAnimator.ofFloat(view,"scaleY",0.3f,1.5f);
set.setDuration(5000);
set.play(alpha).with(scaleX).with(scaleY).with(translation);
set.play(alphaAfter).with(scaleXAfter).with(scaleYAfter).after(translation);
set.start();
}
使用xml文件创建属性动画
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together" >
<objectAnimator
android:duration="1000"
android:propertyName="scaleX"
android:valueFrom="1"
android:valueTo="0.5" >
</objectAnimator>
<objectAnimator
android:duration="1000"
android:propertyName="scaleY"
android:valueFrom="1"
android:valueTo="0.5" >
</objectAnimator>
</set>
然后在方法中加载这个动画
public void scaleX(View view){
Animator animator = AnimatorInflater.loadAnimator(this,R.animator.scalex);
animator.setTarget(view);
view.setPivotX(view.getWidth()/2);
view.setPivotY(view.getHeight()/2);
animator.start();
}
使用set标签,有一个orderring属性设置为together(还有一个值,sequentially 表示一个接一个)
布局动画
主要使用LayoutTransition为布局的容器设置动画,当容器中的视图层次发生变化时存在过渡的动画效果
过渡的类型:
LayoutTransition.APPEARING :当一个view在ViewGroup中出现时,对此View设置的动画
LayoutTransition.CHANGR_APPEARING:当一个view在ViewGroup中出现时,对此View对其他View位置造成影响,对其他View设置的动画
LayoutTransition.DISAPPEARING:当一个view在ViewGroup中消失的时候。对此view设置的动画
LayoutTransition.CHANGE.DISAPPEARING:当一个View在ViewGroup中消失时,对此View对其他View位置造成影响,对其他View设置的动画
LayoutTransition.CHANGE:不是由于VIew出现或者消失造成对其他View位置造成影响,然后对其他View设置的动画
@Override
public void onClick(View view) {
final Button button = new Button(this);
button.setText(String.valueOf(mValue++));
mGridView.addView(button,Math.min(1,mGridView.getChildCount()));
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mGridView.removeView(button);
}
});
}
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
mLayoutTransition = new LayoutTransition();
mLayoutTransition.
setAnimator(LayoutTransition.APPEARING,
(mAppear.isChecked()?
mLayoutTransition.getAnimator(LayoutTransition.APPEARING):
null));
mLayoutTransition.
setAnimator(LayoutTransition.CHANGE_APPEARING,
(mAppear.isChecked()?
mLayoutTransition.getAnimator(LayoutTransition.CHANGE_APPEARING):
null));
mLayoutTransition.
setAnimator(LayoutTransition.DISAPPEARING,
(mAppear.isChecked()?
mLayoutTransition.getAnimator(LayoutTransition.DISAPPEARING):
null));
mLayoutTransition.
setAnimator(LayoutTransition.CHANGE_DISAPPEARING,
(mAppear.isChecked()?
mLayoutTransition.getAnimator(LayoutTransition.CHANGE_DISAPPEARING):
null));
mGridView.setLayoutTransition(mLayoutTransition);
}
这个表现的不是很清楚,我们可以自己定义这个动画
mLayoutTransition.
setAnimator(LayoutTransition.APPEARING,
(mAppear.isChecked()?
ObjectAnimator.ofFloat(button,"scaleX",0.0f,1.0f)
:
null));
原本的淡入淡出,变成了宽度从中间开始放大
View的anim方法
我们继续模拟下落的过程
先看代码:
public void ViewAnim(final View view){
mBall.animate()
.y(mLaout.getHeight())
.alpha(0.3f)
.rotationBy(0)
.rotationBy(360)
.setDuration(3000)
.withEndAction(new Runnable() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
mBall.setY(0);
mBall.setAlpha(1.0f);
}
});
}
})
.start();
}
看效果:
啊啊啊啊GIF图好难录,大概就是这个效果,它是从顶开始的
感觉animate使得动画变得更加简单
补充:
AccelerateDecelerateInterolator 先加速后减速,开始结束时慢,中间加速
AccelerateInterpolator 加速,开始时慢中间加速
DecelerateInterpolator 减速,开始时快然后减速
AnticipateInterpolator 反向 ,先向相反方向改变一段再加速播放
AnticipateOvershootInterpolator 反向加超越,先向相反方向改变,再加速播放,会超出目的值然后缓慢移动至目的值
BounceInterpolator 跳跃,快到目的值时值会跳跃,如目的值100,后面的值可能依次为85,77,70,80,90,100
CycleIinterpolator 循环,动画循环一定次数,值的改变为一正弦函数:Math.sin(2 mCycles Math.PI * input)
LinearInterpolator 线性,线性均匀改变
OvershottInterpolator 超越,最后超出目的值然后缓慢改变到目的值
TimeInterpolator 一个接口,允许你自定义interpolator,以上几个都是实现了这个接口
代码练习的链接
https://github.com/vivianluomin/PracticeEveryDay/tree/master/TestPropertyAnimation