AlphaAnimation(透明度动画)、RotateAnimation(旋转动画)、ScaleAnimation(缩放动画)、TranslateAnimation(平移动画)
且只能做用于View上。 (应用如Activity切换动画)改变的只是View的画布
,而没有改变View的点击响应区域;而属性动画会经过反射技术来获取和执行属性的get、set方法,从而改变了对象位置的属性值
。经过画布的移动实现动画
),这就是为何Animation不会改变View的属性的根本所在。咱们能够理解为Animation只是操做的View画布而并非改变View的位置(mLeft,mRight,mTop,mBottom)。在View的draw()方法中:java
final Animation a = getAnimation();//是否设置了动画
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
} else {
if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) != 0) {
// No longer animating: clear out old animation matrix
mRenderNode.setAnimationMatrix(null);
mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
if (!drawingWithRenderNode
&& (parentFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
final Transformation t = parent.getChildTransformation();
final boolean hasTransform = parent.getChildStaticTransformation(this, t);
if (hasTransform) {
final int transformType = t.getTransformationType();
transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null;
concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
}
}
}
......
if (transformToApply != null) {
if (concatMatrix) {
if (drawingWithRenderNode) {
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
// Undo the scroll translation, apply the transformation matrix,
// then redo the scroll translate to get the correct result.
canvas.translate(-transX, -transY);
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
......
}
复制代码
在View的draw方法中咱们能够看到当咱们设置了动画以后会生成transformToApply对象,当transformToApply不为null的时候会进行根据动画的参数矩阵进行View的从新绘制。重点看到Animation产生的动画数据实际并非应用在View自己的,而是应用在RenderNode或者Canvas上的,这就是为何Animation不会改变View的属性的根本所在。另外一方面,咱们知道Animation仅在View被绘制的时候才能发挥本身的价值,这也是为何补间动画被放在Android.view包内。git
插值器(Interpolator):根据时间流逝的百分比计算出当前属性值改变的百分比。肯定了动画效果变化的模式,如匀速变化、加速变化等等。View动画和属性动画都可使用。(可理解为改变更画的速度曲线,默认使用的是先加速再减速的插值器
)github
经常使用的系统内置插值器:canvas
自定义插值器实现Interpolator接口。bash
public class LinearInterpolator implements Interpolator {
public LinearInterpolator() {
}
public LinearInterpolator(Context context,AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input;
}
}
复制代码
类型估值器(TypeEvaluator):根据当前属性改变的百分比计算出改变后的属性值(计算在百分之多少的时候返回什么值
)。针对于属性动画,View动画不须要类型估值器。经常使用的系统内置的估值器:app
自定义估值器实现TypeEvaluator接口。ide
public class IntEvaluator implements TypeEvaluator<Integer> {
public Integer evaluate(float fraction,Integer startValue,Integer endValue) {
int startInt = startValue; start:1,end:3 那么在0.2的时候则为: 1+(3-1)*0.2
return (int)(startInt + fraction * (endValue -startInt));
}
}
复制代码
如下为例:动画
mView.setOnTouchListener(new View.OnTouchListener() {
int lastX, lastY;
Toast toast = Toast.makeText(TestActivity.this, "", Toast.LENGTH_SHORT);
@Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int) event.getRawX();
int y = (int) event.getRawY();
if (event.getAction() == MotionEvent.ACTION_MOVE) {
//Toolbar和状态栏的高度
int toolbarHeight = (getWindow().getDecorView().getHeight() - findViewById(R.id.root_view).getHeight());
int widthOffset = mView.getWidth() / 2;
int heightOffset = mView.getHeight() / 2;
mView.setTranslationX(x - mView.getLeft() - widthOffset);
mView.setTranslationY(y - mView.getTop() - heightOffset - toolbarHeight);
toast.setText(String.format("left: %d, top: %d, right: %d, bottom: %d",
mView.getLeft(), mView.getTop(), mView.getRight(), mView.getBottom()));
toast.show();
}
lastX = x;
lastY = y;
return true;
}
});
复制代码
当咱们调用了setTranslationX或setRotation等方法后其实改变的并非View的真正位置,只是对View画布的改变。而改变View真正坐标只能使用view.layout()
方法,那么为何属性动画能够经过setTranslationX等方法改变View的点击事件区域呢?ui
由于在事件分发中当咱们去判断View是否在手指点击区域内的时候会去判断View是否调用了setTranslation,setRotation,setScale这些方法,若是调用的话会调用matrix.mapPoints
这个方法将View的初始坐标值和通过动画改变的坐标值进行一个融合计算从而获得最终的View坐标值,以此值去判断View是否再点击区域内,而补间动画并无将矩阵设置给View,那么最终作坐标融合的时候天然不会以融合改变后的坐标去判断View是否在手指点击区域内,因此View动画不会改变点击区域而属性动画能够。this
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
......
Transformation transformToApply = null;
final Animation a = getAnimation();
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
transformToApply = parent.getChildTransformation();
}
if (transformToApply != null) {
if (drawingWithRenderNode) { //属性动画会设置矩阵
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
canvas.translate(-transX, -transY); //View动画不会设置到矩阵中
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
}
}
复制代码
当咱们设置的动画播放补间动画的时候,咱们所看到的变化,都只是临时
的。而属性动画呢,它所改变的东西,却会更新到这个View所对应的矩阵中
,因此当ViewGroup分派事件的时候,会正确的将当前触摸坐标,转换成矩阵变化后的坐标,这就是为何播放补间动画不会改变触摸区域的缘由了。
public AnimationHandler getAnimationHandler() { return AnimationHandler.getInstance();}
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
getAnimationHandler().addAnimationFrameCallback(this, delay);this就是ValueAnimator自身
}
复制代码
如上当咱们新建ValueAnimator的时候会建立AnimationHandler这个静态类,同时它会持有ValueAnimator,当进入Activity界面后若是有一些和控件绑定在一块儿的属性动画在运行同时设置成了无限循环模式
,退出的时候要记得cancel
掉这些动画不然会形成内存泄漏。
引用链关系:Activity->View->ValueAnimator->AnimationHandler(静态类,GCROOT)
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
复制代码
里边包括各类类型的动画使用,关键帧等的使用实例。 下边给一个属性动画的使用实例
ValueAnimator mFirstPhaseAnimator;
if (mFirstPhaseAnimator == null) {
mFirstPhaseAnimator = ValueAnimator.ofInt(0, 100);
mFirstPhaseAnimator.setDuration(2000);
mFirstPhaseAnimator.setInterpolator(new DecelerateInterpolator());
mFirstPhaseAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
if (!mIsAnimationSetFinished) {
mCurrentVal = (int) valueAnimator.getAnimatedValue(); 获取当前的值
if (mProcessor != null) {
String text = mProcessor.getText(getContext(), mCurrentVal);
if (TextUtils.isEmpty(text)) {
setValText(text);
}
} else {
setValText(String.valueOf(mCurrentVal));
}
}
}
});
mFirstPhaseAnimator.start();//开启动画
}
复制代码