0%

动画一:常规动画

属性动画(android.animation)

插值器(Interpolator)

Interpolator 是可以使用 XML 来定义和解析的,它的作用是根据输入产生一个 [0, 1]之间的值

1
2
3
public interface TimeInterpolator {
float getInterpolation(float input); //输入和输出区间都是[0, 1]
}

Interpolator 有各种子类,最简单的是线性的 LinearInterpolator ,各种子类的公式如下

  • $LinearInterpolator : a(t) = t $
  • $AccelerateInterpolator : a (t) = t*t $
  • $DecelerateInterpolator : a (t) =1 - (1-t)*(1-t) $
  • $AccelerateDecelerateInterpolator : a (t) =\frac{cos((t+1)*\pi }{2} + 0.5 $
  • $AnticipateInterpolator : a(t) = t t ((tension + 1) * t - tension)$ 越来越快
  • $BounceInterpolator :$分段函数,呈弹跳效果
  • $CycleInterpolator : a (t) = sin(2\picyclest) $ \将时间映射成正弦曲线,呈振动式效果,这一点实际非常有用,如果时间给500ms,那么就是半周期,数值复0,适合与View的animate方法联合使用。*
  • $LookupTableInterpolator : $通过查表来产生结果值

插值器在属性动画中用于时间轴的变换。

估值器(TypeEvaluator)

TypeEvaluator 类用来根据起始值和时间坐标计算中间值。

1
2
3
public interface TypeEvaluator<T> {
public T evaluate(float fraction, T startValue, T endValue);
}

典型的估值器实现如 IntEvaluator,RectEvaluator,ArgbEvaluator 等均是线性实现。

通过自定义估值器可以实现属性动画。例如自定义一个改变控件高度的属性动画

1
2
3
4
5
6
7
8
9
10
11
//1.创建估值器,注意要改的属性是 LayoutParams.height。
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int num = super.evaluate(fraction, startValue, endValue);
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = num;
v.setLayoutParams(params);
return num;
}
//2.创建属性动画
ValueAnimator.ofObject(new MyEvalutor(this), 0f, 1f).start();

使用估计器比添加 AnimatorUpdateListener 接口要简洁一些。

Property 与 PropertyValuesHolder

Property`类表示属性,所以它要表示键值对,对于键,必须是 String 类型,其值表示属性名;对于值,需要一个对象(Class:T) 来提供,数据类型为V,使用 get/set 方法可以从对象中读取和设置属性值。

1
2
3
4
5
6
public abstract class Property<T, V> {
private final String mName;
private final Class<V> mType;
public void set(T object, V value);
public abstract V get(T object);
}

以 View 中的”alpha”属性为例,View 对象提供 alpha 属性,其值的格式是 float。

1
2
3
4
5
6
7
8
9
10
public static final Property<View, Float> ALPHA = new FloatProperty<View>("alpha") {
@Override
public void setValue(View object, float value) {
object.setAlpha(value);
}
@Override
public Float get(View object) {
return object.getAlpha();
}
};

PropertyValuesHolder 是对属性 Property 的包装,合成了Property ,属性名称以及属性值集合等,并通过反射(Method)来修改属性值。

1
2
3
4
5
6
7
public static PropertyValuesHolder ofInt(Property<?, Integer> property, int... values) { 
//Keyframe 是 time/value 的键值对
mKeyframes = KeyframeSet.ofInt(values);
mPropertyName = property.getName();
mProperty = property;
return new IntPropertyValuesHolder(property, values);
}

传入的多个属性值被转为 Keyframe 集合,且设置了属性名,值类型被确定为 int,通过 Property 对象可以应用属性值。只是缺少一个对象来提供和接受 int 类型的值。

  • PropertyValuesHolder 还可以用 Object 对象来构造。
1
2
3
4
5
6
7
public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,
Object... values) {
PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
pvh.setObjectValues(values);
pvh.setEvaluator(evaluator);
return pvh;
}

这里对象将被转换为 ObjectKeyframe,只不过属性值由Object对象提供。

PropertyValuesHolder 的重要职责是完成真正的步进计算

1
2
3
4
void calculateValue(float fraction) {
Object value = mKeyframes.getValue(fraction);
mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}

值动画(ValueAnimator)和对象动画(ObjectAnimator)

ValueAnimator 的主要域如下

1
2
3
4
long mStartTime;
PropertyValuesHolder[] mValues; //属性集合
HashMap<String, PropertyValuesHolder> mValuesMap; //属性查找表
ArrayList<AnimatorUpdateListener> mUpdateListeners;

ValueAnimator 在构造时即是通过 PropertyValuesHolder 来完成的。

1
2
3
4
5
6
7
8
9
public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) {
ValueAnimator anim = new ValueAnimator();
mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
for (int i = 0; i < numValues; ++i) {
PropertyValuesHolder valuesHolder = values[i];
mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
}
return anim;
}

使用 ofInt 方法构造的 PropertyValuesHolder 属性名为空。对于 ARGB 颜色而言需要 Evaluator。

ObjectAnimator 比 ValueAnimator 就多了一个对象,可以使用反射来提供和接收数值。······················

1
private WeakReference<Object> mTarget;

当属性动画启动后的处理如下

1
2
3
4
5
6
7
8
9
PropertyValuesHolder[] mValues;
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].calculateValue(fraction);
}
//mUpdateListeners 回调
}

状态列表动画(StateListAnimator)

StateListAnimator 这种动画可以使得 View 在状态切换时启动属性动画,你可以只有使用

1
<com.lxt.toast.text.FadeText    android:stateListAnimator="@animator/test"/>

也可以用代码

1
setStateListAnimator(AnimatorInflater.loadStateListAnimator(getContext(), R.animator.test));

所用的 StateListAnimator 可以用XML来定义,标签是 selector。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<set>
<objectAnimator android:propertyName="translationX"
android:duration="1000"
android:valueTo="200"
android:valueType="floatType"/>
</set>
</item>
<item
android:state_pressed="false">
<set>
<objectAnimator android:propertyName="translationX"
android:duration="1000"
android:valueTo="0"
android:valueType="floatType"/>
</set>
</item>
</selector>

AnimatedVectorDrawableCompat

VectorDrawable 在XML解析时会将 path 字符串解析成 VFullPath 对象,包括 PathDataNode 数组和一些绘制信息,由此构建节点树,绘制时先将节点树绘制在缓存 Bitmap 上。

这里谈谈它的动画效果类 AnimatedVectorDrawableCompat,如果要兼容低版本,在XML中使用 app:srcCompat来引用。

1.首先定义VectorDrawable,重点是给 path 标记 name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:name="end"
android:fillColor="#FF000000"
android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
<path
android:name="star"
android:fillColor="#FF000000"
android:pathData="M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zm3,15c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
</vector>

2.定义属性动画,注意 trimPathStart 和 trimPathEnd 这两个属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially">
<objectAnimator
android:duration="1000"
android:propertyName="trimPathStart"
android:valueFrom="1"
android:valueTo="0"/>
<objectAnimator
android:duration="1000"
android:propertyName="trimPathEnd"
android:valueFrom="1"
android:valueTo="0"/>
</set>

3.合成动画Drawable

1
2
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"                 android:drawable="@drawable/ic_menu_camera">    <target        android:name="star"  //pathname        android:animation="@animator/alpha"/> // 使用的属性动画    <target        android:name="end"        android:animation="@animator/alpha"/>
</animated-vector>

最后调用动画

1
(AnimatedVectorDrawableCompat) fab.getDrawable().start();

AnimatorSet

AnimatorSet 可以多个属性动画设置播放顺序,如顺序播放,并行播放以及延迟播放,其原理是形成这些动画之间的依赖关系。

例如顺序播放的实现如下

1
2
3
4
5
public void playSequentially(Animator... items) {
for (int i = 0; i < items.length - 1; ++i) {
play(items[i]).before(items[i + 1]);
}
}

play 方法并不是播放,而是将 Animator 转为 Node 类,Node 定义了其父节点,兄弟节点和子节点集合,以便形成依赖关系

1
2
3
4
5
6
private static class Node implements Cloneable {
Animator mAnimation;
ArrayList<Node> mChildNodes = null;
ArrayList<Node> mSiblings;
ArrayList<Node> mParents;
}

before 方法会在儿子节点集合中添加代表下一个动画的 Node 节点。

并行播放的实现如下

1
2
3
4
5
6
public void playTogether(Animator... items) {
Builder builder = play(items[0]);
for (int i = 1; i < items.length; ++i) {
builder.with(items[i]);
}
}

with 方法会在兄弟节点集合中添加代表下一个动画的 Node 节点。

至于 after 方法自然就是添加到父节点集合中去了,此外 after 方法还能够设置延迟,这是通过插入一个时长为 delay 的空动画来实现的。

1
2
3
4
5
6
7
public Builder after(long delay) {
// setup dummy ValueAnimator just to run the clock
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(delay);
after(anim);
return this;
}

在构建有向图的时候会使用DFS算法初始化依赖树,使用Build的这几个方法能够随心所欲的构建起动画播放顺序。

显露动画(RevealAnimator)

ViewAnimationUtils 这个工具类可以用来创建显露动画,这是经过优化的一种动画,可以产生波纹效果。它是一种属性动画,其扩展应用很广泛,这个留在实践部分讲。创建方法如下

1
2
3
4
public static Animator createCircularReveal(View view,
int centerX, int centerY, float startRadius, float endRadius) {
return new RevealAnimator(view, centerX, centerY, startRadius, endRadius);
}

注意波纹效果仅在View区域内。

补间动画(android.view.animation)

系统定义的 Animation 确实只有四种,但完全可以通过自定义来扩展。自定义 Animation 需要重写下列方法,如直接改写控件尺寸

1
2
3
4
5
6
7
@Override
protected void applyTransformation(float interpolatedTime, Transformation t)
{
int newWidth = mStartWidth + (int) ((mWidth - mStartWidth) * interpolatedTime);
mView.getLayoutParams().width = newWidth;
mView.requestLayout();
}

系统定义的 Animation 如 AlphaAnimation 是通过 Transformation 类来完成变换的,不过其功能有限,这里通过引入 View 直接修改参数。

各种动画的参数值得注意,如 使用百分比。

布局动画(LayoutAnimationController)

布局动画为 ViewGroup 所独有,它能够使得各个View依次按照延迟播放动画,其构建方法如下

1
public LayoutAnimationController(Animation animation, float delay);

其参数在 View 的布局参数中,AnimationParameters 中包含动画的延迟,默认按照 View 的序号生成。

实际通过延迟发送也能达到这种效果。

1
2
3
4
for (int i = 0; i < size; i++) {
final double delay = 3 * 500 * (i / size);
new Handler().postDelayed(animation, delay);
}

属性动画的常规使用套路

某些控件的绘制依赖于一个参数 process(float) 的变化,对其使用属性动画是一个常规套路,能取得什么效果主要取决于 开发者的想象力。AVLoadingIndicatorViewMkLoader库就是典型代表。

img img

  • 以 ClassicSpinner 的实现为例,基本效果是画8个圆,动画效果是分别播放一个属性动画,但设置延迟,造成透明度不同。
1
2
3
4
ValueAnimator fadeAnimator = ValueAnimator.ofInt(126, 255, 126);
fadeAnimator.setRepeatCount(ValueAnimator.INFINITE);
fadeAnimator.setDuration(1000);
fadeAnimator.setStartDelay(index * 120);

这里将圆形抽象成Circle类,以便于使用动画。

  • FishSpinner的绘制是5个圆,依次旋转一定角度。动画效果为360度的旋转,视觉效果即通过延迟来达到。

更复杂的使用:分段绘制

将几个 process 控制的绘制过程拼接起来,可以达到更炫的效果。GADownloadingJJSearchViewAnim是分段绘制的典型。

img img

暴露动画的应用

CircularAnim 是一个扩展暴露动画的典型例子,要点在于计算中心点位置和始终半径。

Login & Home Screen ui ux invision app prototyping iphone material gif ae animation mobile login

注意中心点位置是相对于 view 的坐标位置,可以在view区域之外。这样如果要以另一个View B为动画的中心,就需要计算B的中心点相对与View左上点的位置,并要确保动画半径容纳原 View。

1
2
3
4
5
6
int[] mCL = new int[2];
mContentLayout.getLocationOnScreen(mCL);
int[] mAL = new int[2];
view.getLocationOnScreen(mAL);
int cX = mAL[0] + view.getWidth()/2-mCL[0];
int cY = mAL[1] + view.getHeight()/2-mCL[1];

如果要实现全屏效果,可以在 DecorView 上添加一个 ImageView 来完成动画,ImageView 可以任意设置图片或颜色效果,要注意在动画结束后删除这个 ImageView 。

至于图中的效果,是通过隐藏一个ProgressBar来完成的,将TextView的收缩半径设置为ProgressBar的一半高度,动画完成后隐藏 TextView,显示 ProgressBar 即可。

更复杂的暴露动画效果:RippleLayout

暴露动画实际也是属性动画,将它和其它属性动画结合能产生一些视觉效果。

RippleLayout

这里图片上的效果可以分成几部分

1.点击活动A的按钮,开始触发波纹效果,动画结束之后启动活动B

2.活动B的布局分为上下两层,分别执行TransitionY动画。

与之类似,点击返回键,将反向播放内容布局动画和波纹动画。

入屏动画

img

这是一个属性动画的封装库,能够流式的使用属性动画,但无法取消以前的动画,只能用作入屏动画。

1
2
3
4
5
6
7
8
PropertyAction fabAction = PropertyAction.newPropertyAction(fab).scaleX(0).scaleY(0).duration(750).build();
Player.init().
animate(headerAction).
then().
animate(fabAction).
then().
animate(bottomAction).
play();

FabulousFilter

img