android中主要有三种方式来实现动画,补间动画、帧动画、属性动画。
1.补间动画(tween Animation)
补间动画就是对图像进行
- 平移 translate
- 缩放 schale
- 旋转 rotale
- 渐变alpha
xml使用补间动画
下面创建一个rotate.xml,里面有四组标签分别是,旋转、缩放、渐变、平移,我们可以在set标签中组合使用这些动画标签。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
>
<rotate
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="30%"
android:toDegrees="360">
</rotate>
<scale></scale>
<alpha></alpha>
<translate></translate>
</set>
这四种动画实现方式都是通过Animation类和AnimationUtils配合实现,也可以通过xml实现,动画的XML文件在工程中res/anim目录。
通过AnimationUtils.loadAnimation方法来加载刚刚定义的动画配置。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Animation anim = AnimationUtils.loadAnimation(mContext, R.anim.rotate);
//监听动画的状态(开始,结束)
anim.setAnimationListener(new EffectAnimationListener());
textWidget = (TextView)findViewById(R.id.text_widget);
textWidget.setText("画面旋转动画效果");
textWidget.startAnimation(anim);
}
2. 代码中使用补间动画
可以看到每个补间动画都对应一个类,不同动画效果选择不同的类,它们都是Animation的子类。
private void startAlpha() {
AlphaAnimation alpha = new AlphaAnimation(0.0f, 1f);
alpha.setDuration(1000);
but.startAnimation(alpha);
}
private void startScale() {
ScaleAnimation scale = new ScaleAnimation(0.1f, 1.2f, 0.1f, 1.2f);
scale.setDuration(1000);
but.startAnimation(scale);
}
private void startTrans() {
TranslateAnimation trans = new TranslateAnimation(0, 1, 0, 1);
trans.setDuration(1000);
but.startAnimation(trans);
}
private void starRotate() {
RotateAnimation rotate = new RotateAnimation(0.1f, 1.1f);
rotate.setDuration(1000);
but.startAnimation(rotate);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i("jinwei", "onCreate");
setContentView(R.layout.layout_glide);
but = findViewById(R.id.but);
img = findViewById(R.id.iamge);
initLRUCache();
findViewById(R.id.but).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startAlpha();
startScale();
startTrans();
starRotate();
}
});
}
AnimationSet动画集合
private void startTrans() {
AnimationSet set = new AnimationSet(true);
TranslateAnimation trans = new TranslateAnimation(0, 1, 0, 1);
trans.setDuration(1000);
RotateAnimation rotate = new RotateAnimation(0.1f, 1.1f);
rotate.setDuration(1000);
}
2. 逐帧动画(frame-by-frame animation)
逐帧动画就是类似于动画片似的,一张一张图片连贯的播放,就可以动画的效果。
Frame Animation
帧动画顺序播放设置好的图片,就像电影一样一帧一帧的播放。
<?xml version="1.0" encoding="utf-8"?>
<animation-list
xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true"
>
<item android:drawable="@drawable/p1" android:duration="1000"></item>
<item android:drawable="@drawable/p2" android:duration="1000"></item>
<item android:drawable="@drawable/p3" android:duration="1000"></item>
<item android:drawable="@drawable/p4" android:duration="1000"></item>
<item android:drawable="@drawable/p5" android:duration="1000"></item>
<item android:drawable="@drawable/p6" android:duration="1000"></item>
</animation-list>
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_glide);
findViewById(R.id.but).setOnClickListener(new View.OnClickListener() {
AnimationDrawable anim = (AnimationDrawable)getResources().
Drawable anim = getDrawable(R.drawable.frame);
textWidget = (TextView)findViewById(R.id.text_widget);
textWidget.setText("背景渐变动画效果");
textWidget.setBackgroundDrawable(anim);
anim.start();
}
}
3. 补间动画原理
整套动画的执行逻辑还是很复杂的,我们只做最基本的了解,明白动画是靠什么执行变换的。
- 1.首先看view.startAnimation()
/**
* Start the specified animation now.
*
* @param animation the animation to start now
*/
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
- setAnimation方法把定义的animation传给了view,这里可以看出最终的动画调用逻辑肯定是在view中完成的,接着invalidate返回会刷新view也就是会调用draw()方法。
- 3.draw()
可以补间动画的效果主要是通过Matrix来完成的,操作缩放、位移、旋转、倾斜的工具类。
if (transformToApply != null) {
if (concatMatrix) {
if (drawingWithRenderNode) {
// 应用动画数据
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
canvas.translate(-transX, -transY);
// 应用动画数据
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
float transformAlpha = transformToApply.getAlpha();
if (transformAlpha < 1) {
// 应用动画数据
alpha *= transformAlpha;
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
}
4.属性动画 Property Animation
-
什么属性动画
属性动画是android3.0官方推出的动画功能扩展版,功能更加强大可以完全的替代掉之前介绍的补间动画,主要是为了满足补间动画的一些不足。 -
为什么需要属性动画
-
前面介绍的帧动画和补间动画可以很多的实现我们日常的动画需求了,但是可以看到只能作用于view,如果操作的对象不是一个view创建的动画就执行不了了,有个疑问为什么需要对一个不是view的对象做动画?有时候我们会有一些根据动画值变更来做具体操作的需求,比如我需要计算一个颜色间值,这个值是根据计算动态变更,这种需求前面介绍的方式就满足不了了。
-
还有一个就是补间动画过程中是不能响应view的事件的,但是属性动画是可以的。
-
nineoldandroids
nineoldandroids是为了满足3.0之前版本也能使用属性动画,而推出的一个兼容包。
ObjectAnimator
ObjectAnimator是属性动画很重要的一个类,我们可以通过它的一系列静态方法来创建出属性动画,它是ValueAnimator的子类。
public final class ObjectAnimator extends ValueAnimator {
}
通过ofFloat方法创建出来一个animator对象,第二个是动画的属性名字,这个属性名字是传入对象里必须要有这个属性的get set方法,没有的话编译不会通过,我们这个传入了translationX可以在view中验证一下这个结论。
private void startObjectAnimation() {
ObjectAnimator animator = ObjectAnimator.ofFloat(but, "translationX", 0, but.getWidth());
animator.setDuration(2000);
animator.start();
View中对应的属性 get set方法。
@ViewDebug.ExportedProperty(category = "drawing")
public float getTranslationX() {
.....
}
public void setTranslationX(float translationX) {
......
}
常用的属性
-
translationX和translationY
这两个属性作为一种增量控制着View对象从它布局容器左上角坐标开始的位置 -
rotation、rotationX和rotationY
这个三个属性控制View对象围绕支点进行2D和3D旋转 -
scaleX和scaleY
这两个属性控制着View对象围绕他的支点进行2D缩放 -
pivotX和pivotY
这两个属性控制着View对象的支点位置,围绕这个支点进行旋转和缩放变换处理。默认情况下,该支点的位置就是View对象的中心点。x和y:这两个简单实用的属性,描述了View对象在它的容器中的最终位置,它是最初的左上角坐标和translationX、translationY值的累积和。 -
alpha
表示View对象的alpha透明度。默认值是1(不透明),0代表完全透明(不可见)。
可以看到上面这个属性基本上和涵盖补间动画的几种动画效果,如果传入的对象是view中没有的属性怎么办,这个属性动画也可以搞定
使用自定义属性
前面说了传入的属性名字,在对象中是必须有get set方法的。那么我可以自定义一个类重写一下自己属性的get set方法
private static class WrapperView {
private View mTarget;
public WrapperView(View mTarget) {
this.mTarget = mTarget;
}
public int getWidth() {
return mTarget.getLayoutParams().width;
}
public void setWidth(float width) {
Log.i("jinwei", " width =" + width);
mTarget.getLayoutParams().width = (int) width;
mTarget.requestLayout();
}
}
private void startTypeObjectAnimation() {
WrapperView wrapperView = new WrapperView(but);
ObjectAnimator valueAnimator = ObjectAnimator.ofFloat(wrapperView, "width", 0, 2000);
valueAnimator.setDuration(2000);
valueAnimator.start();
}
可以看到传入的参数是我的width字段,它在我们的包装类中是有get set方法的,所以符合规范编译通过,有个疑问width属性是如何被调用的,要知道内部是通过反射机制来调用这个width方法,具体原理这里不做分析。
ValueAnimator
ValueAnimator是属性动画的核心所在,但是我们一般使用都是通过它的子类ObjectAnimator来创建动画,ValueAnimator本身不提供任何动画任何动画效果,一般使用来监听动画数值的变化。
onAnimationUpdate会返回初始化设置的动画数值,我们可以根据这些数值来给对象做一些动画的变化操作。
private void valueAnimator() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 200);
valueAnimator.setTarget(but);
valueAnimator.setDuration(2000);
valueAnimator.start();
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.i("jinwei", animation.getAnimatedValue().toString());
}
});
}
动画监听
一般通过AnimatorListenerAdapter这个抽象类实现方法,这样比接口的好处是不用每个方法都实现。
private void startObjectAnimation() {
WrapperView wrapperView = new WrapperView(but);
ObjectAnimator animator = ObjectAnimator.ofFloat(but, "translationX", 0, but.getWidth());
animator.setDuration(2000);
animator.start();
animator.setInterpolator(new AccelerateInterpolator());
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
}
});
AnimatorSet
属性动画也有动画集合,可以对一列动画进行同步执行和顺序执行。
private void startAnimatorSet() {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(but, "translationX", 300f);
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(but, "scaleX", 1f, 0f, 1f);
ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(but, "scaleY", 1f, 0f, 1f);
AnimatorSet set = new AnimatorSet();
set.setDuration(3000);
//一起执行
set.playTogether(objectAnimator, objectAnimator1, objectAnimator2);
//可以进行动画精准顺序控制
set.play(objectAnimator).before(objectAnimator1).after(objectAnimator2);
set.start();
}
PropertyValuesHolder
这个类似于AnimatorSet也可以对多组动画同步执行,但是不像AnimatorSet那样可以进行一些顺序的操作。
private void propertyValue() {
PropertyValuesHolder translationX = PropertyValuesHolder.ofFloat("translationX", 500);
PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1f, 0, 1f);
PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1f, 0, 1f);
ObjectAnimator.ofPropertyValuesHolder(but, translationX, scaleX, scaleY).setDuration(1000).start();
}
布局中使用属性动画
属性动画和补间动画一样支持xml配置动画,值得注意的是动画文件有规范,必须是res>animator下面的文件才能识别。
这里定义一个scale.xml的属性动画文件
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:propertyName="scaleX"
android:valueFrom="0"
android:valueTo="1"
android:valueType="intType">
</objectAnimator>
private void loadXmlAnimator() {
Animator animator = AnimatorInflater.loadAnimator(this, R.animator.scale);
animator.setTarget(but);
animator.start();
}
view的animate方法
android3.0官方对view新增了animate属性,可以直接对view进行属性动画操作,通过这种方式写起来更加简便。
private void animate() {
but.animate().setDuration(2000).scaleY(0).scaleYBy(3000).start();
but.animate().setDuration(2000).translationX(0).translationXBy(3000).start();
but.animate().setDuration(2000).translationY(0).translationYBy(3000).start();
but.animate().setDuration(2000).alpha(0).alphaBy(3000).start();
}
布局动画
布局动画的作用是可以在viewGroup添加view的时候,加入一个过渡动画效果。
private void addViewGroupAnimate() {
ViewGroup viewById = findViewById(R.id.mainview);
AlphaAnimation scaleAnimation = new AlphaAnimation(0, 1);
scaleAnimation.setDuration(600);
LayoutAnimationController lac = new LayoutAnimationController(scaleAnimation, 0.5f);
viewById.setLayoutAnimation(lac);
Button button = new Button(this);
button.setWidth(200);
button.setHeight(200);
viewById.addView(button);
}
插值器
插值器的主要作用是控制动画的变化速率,比如去实现一些弹性的动画效果,在android中自带了几种常用的插值器。
- AccelerateDecelerateInterpolator 在动画开始与介绍的地方速率改变比较慢,在中间的时候加速
- AccelerateInterpolator在动画开始的地方速率改变比较慢,然后开始加速
- AnticipateInterpolator开始的时候向后然后向前甩
- AnticipateOvershootInterpolator 开始的时候向后然后向前甩一定值后返回最后的值
- BounceInterpolator 动画结束的时候弹起
- CycleInterpolator 动画循环播放特定的次数,速率改变沿着正弦曲线
- DecelerateInterpolator 在动画开始的地方快然后慢
- LinearInterpolator 以常量速率改变
- OvershootInterpolator 向前甩一定值后再回到原来位置
- PathInterpolator 路径插值器
网友评论