-
目录
-
属性动画与视图动画区别
1 视图动画只能是view使用,对view做出相应的变换,但是属性动画作用于Object,任何对象都可以。
2 视图动画只是改变了视觉效果,但是并没有改变其物理属性,比如button坐标在(100,100,300,300),通过位移进行移动,但是他的可点击区域仍然在(100,100,300,300).
3 最后一点是视图动画提供了四种变化方式。至于其具体的实现由系统计算好了。因此它的形式比较单一。比如(100,100)移动到(300,230)只能以直线的形式移动过去,我们没法控制它的移动轨迹,而属性动画可以自己控制这些东西,因此动画有更多的表现形式。
-
实现过程
我们可以简单归纳属性动画的实现分为以下几个过程
1 设置目的动画的初始值,终值,以及持续时间。
2 设置动画的插值器(TimeInterpolator, 定义动画的变换率,就是控制时间流逝机制),设置动画的估值器(TypeEvaluator)主要是在动画过程中根据初始和最终值确定任意时刻的状态。比如位移动画,估值器就需要确认任意时刻object的坐标。对于TimeInterpolator和TypeEvaluator系统提供了一些可以直接使用的类,简单一些的动画够用。
3 根据上面的逻辑进行不停的计算,每次计算的结果都进行绘制,直到时间结束。
-
API简介
在属性动画中主要的类是Animator, 它是一个抽象类,直接子类有AnimatorSet和ValueAnimator, 间接子类有ObjectAnimator和TimeAnimator,我们常用的是AnimatorSet和valueAnimator,整个属性动画的流程如下所示:
在更新过程中,就是时间插值器TimeInterpolator计算时间, 而TypeEvaluator计算当前时间Ojbect的状态。我们先看一个简单的属性动画的例子。
public class AnimatorDemoActivity extends Activity {
@BindView(R.id.bttest)
Button bt;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_animator_demo);
ButterKnife.bind(this);
}
@OnClick(R.id.bttest)
public void ButtonLarge(){
//设置一个属性动画,移动一个button距离左边的距离,设置从0到1000.
ObjectAnimator va =ObjectAnimator.ofFloat(bt,"translationX",1000);
//这是一个插值器,线性的,表示匀速进行
va.setInterpolator(new MyLinearInterpolator());
//动画时间是2s
va.setDuration(2000);
va.start();
}
}
//以下是mYLinearInterpolator的代码
public class MyLinearInterpolator extends BaseInterpolator {
@Override
public float getInterpolation(float v) {
Log.e("Tag","huai *************the vis======"+v);
return v;
}
在这里我们设置的插值器是一个模仿的线性插值器。我们主要是看看这个时间因子v的变化情况。打印如下:
截取了其中2段作为展示,可以看出是在2s内匀速前进。可以理解为2s内匀速的移动1000pix,在这里之所以展示匀速插值器的案例,是因为我们有时间做的动画不需要允许进行。无规则有时候反而更加美观。而我们可以按照我们想要的规则定制这个时间因子。
以上代码是通过ObjectAnimation移动,属性动画形式为:ObjectAnimator.ofFloat(targetObject, "propName", value...),就是针对Object的某一属性做动画,在上面的例子中我们移动的属性是translationX,在这里原则上可以移动任意属性 ,比如Object的x,y的坐标,等等,但是想要移动某个属性必须注意以下条件:
1 移动的属性,propName必须在TargetObejct的类中包含setter,geter函数。如果没有setter函数,那么可以用valueAnimation代替
2 value...如果只有一个值当做终点值,两个值依次为开始,结束值。
3 执行完有可能需要主动调用invalidate。
下面我们就看一个valueAnimator的例子。
private void valueMove(){
ValueAnimator va = ValueAnimator.ofInt(bt.getWidth(),1500);
va.setDuration(3000);
//设置一个加速的时间插值器
va.setInterpolator(new AccelerateInterpolator());
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//获取移动过程中,移动的当前值然后将值赋予button的宽
int width = (int) valueAnimator.getAnimatedValue();
bt.getLayoutParams().width = width;
bt.requestLayout();
}
});
va.start();
}
在上面的代码我们都可以看到时间插值器,我们选择了一个加速的效果,我们可以看看他的原码中处理时间因子的代码:
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
//mDoubleFactor默认值是2,就是对时间因子input求二次方,x的2次方,在
[0,1)的范围内斜率逐渐增大,因此它是逐渐加速的过程
return (float)Math.pow(input, mDoubleFactor);
}
}
之所以在这里分析两个插值器的源码,是因为我们自己写动画的过程中,一般都是需要重写插值器和估值器。可以根据需求来实现自己所需要的插值器估值器。接下来我们要讲估值器,估值器的作用就是需要做动画的属性在某个时间点的状态。
-
仿花束点赞效果
先上效果图
其实这个动画的过程挺简单,就是计算在某一个时刻,这个心形imageview的位置。所以我们需要移动的是他的位置。就是坐标,可以用一个point来表示的坐标。因此我们可以写明白他的估值器就是要确定任意时刻图片的坐标。 首先我们确认的是图片移动的轨迹是按照三阶贝塞尔曲线来实现的。所以我们可以重写TypeEvaluator如下:
public class BezierEvaluator implements TypeEvaluator<PointF> {
//这个是贝塞尔曲线中的两个控制点,通过构造函数来传递
PointF control1,control2;
public BezierEvaluator(PointF controlPoint1, PointF controlPoint2){
control1 = controlPoint1;
control2 = controlPoint2;
}
@Override
public PointF evaluate(float t, PointF point0, PointF point3) {
//t就是插值器返回的那个时间因子,范围[0,1]
//这里就是对一个三阶贝塞尔曲线的实例应用,完全是按照贝塞尔的
//point0,是贝塞尔的初始点,point3是贝塞尔的结束点
PointF point = new PointF();
point.x = point0.x * (float)Math.pow((1-t),3)
+ 3 * control1.x * t * (float)Math.pow((1-t),2)
+ 3 * control2.x * (float)Math.pow(t,2)* (1 - t)
+ point3.x * (float)Math.pow(t,3);
point.y = point0.y * (float)Math.pow((1-t),3)
+ 3 * control1.y * t * (float)Math.pow((1-t),2)
+ 3 * control2.y * (float)Math.pow(t,2) * (1 - t)
+ point3.y * (float)Math.pow(t,3);
return point;
}
}
我们重写估值器,主要是需要自己实现evaluate()这个函数,在这个函数里面根据时间因子,确定移动的位移坐标的位置。这里直接用的三阶贝塞尔曲线,如果对贝塞尔曲线不是很清楚,可以看贝塞尔曲线的绘制这篇博客。我们写好了估值器。接下来就可以实现自己的动画了。
public class LovelyView extends RelativeLayout {
int mWidth,mHeight;
int drawableHeight,drawableWidth;
Drawable love;
Interpolator[] mInterpolators;
LayoutParams lp;
Random mRandom;
public LovelyView(Context context){
this(context,null);
}
public LovelyView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LovelyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
}
private void init(){
initDrawableConfig();
mRandom = new Random();
lp = new LayoutParams(drawableWidth,drawableHeight);
lp.addRule(CENTER_HORIZONTAL,TRUE);
lp.addRule(ALIGN_PARENT_BOTTOM,TRUE);
}
private void initDrawableConfig(){
love = getResources().getDrawable(R.mipmap.love);
drawableHeight = love.getIntrinsicHeight();
drawableWidth = love.getIntrinsicWidth();
}
public void addView(){
//初始化需要添加的imageview
final ImageView desView = new ImageView(getContext());
desView.setImageDrawable(love);
desView.setLayoutParams(lp);
addView(desView);
AnimatorSet sets = getStartAnimationSet(desView);
ValueAnimator va = getBezierAnimator(desView);
AnimatorSet de = new AnimatorSet();
de.play(sets).before(va);
de.start();
}
/**
* 获取初始化心形图案的动画
* @return 动画集合
*/
private AnimatorSet getStartAnimationSet(ImageView desView){
AnimatorSet sets = new AnimatorSet();
ObjectAnimator alphaAnimation = ObjectAnimator.ofFloat(desView,"alpha",0.3f,1f);
ObjectAnimator scalexAnimation = ObjectAnimator.ofFloat(desView,"scaleX",0.1f,1f);
ObjectAnimator scaleyAnimation = ObjectAnimator.ofFloat(desView,"scaleY",0.1f,1f);
sets.playTogether(alphaAnimation,scaleyAnimation,scalexAnimation);
sets.setDuration(500);
sets.setTarget(desView);
return sets;
}
//获取移动iamgeview的动画
private ValueAnimator getBezierAnimator(final ImageView desView){
int x = (mWidth - drawableWidth)/2;
int y = (mHeight - drawableHeight);
PointF p0 = new PointF(x,y);
PointF p1 = getPoint(1);
PointF p2 = getPoint(2);
PointF destinationPoint = new PointF(mRandom.nextInt(mWidth), 0);
BezierEvaluator evaluator = new BezierEvaluator(p1,p2);
ValueAnimator animator = ValueAnimator.ofObject(evaluator, p0,
destinationPoint);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 不断改变ImageView的坐标
PointF pointF = (PointF) animation.getAnimatedValue();
desView.setX(pointF.x);
desView.setY(pointF.y);
desView.setAlpha((1 - (animation.getAnimatedFraction())));
desView.invalidate();
}
});
animator.setTarget(desView);
animator.setDuration(2000);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
return animator;
}
//获取控制点
private PointF getPoint(int i) {
return new PointF(mRandom.nextInt(mWidth) - drawableWidth / 2,
mRandom.nextInt(mHeight / 2) + (i - 1) * mHeight / 2);
}
}
-
总结
其实动画可以看成是定义插值器,估值器的过程,把整个过程一个个拆开就可以分解成最简单的单元.
自定义Interpolator需要重写getInterpolation(float input),控制时间参数的变化.
自定义TypeEvaluator需要重写evaluate()方法,计算对象的属性值并将其封装成一个新对象返回.