android 属性动画使用

来源:互联网 发布:js split函数 编辑:程序博客网 时间:2024/06/13 21:43

安卓动画大致可以分为3大类。
帧动画,类似于放电影,一帧一帧的进行播放,需要多张图片资源进行支撑,会加大客户端的体积,而且扩展性差,几乎不使用。
补间动画,具备平移、缩放、旋转属性,组合后可以实现一些酷炫的动画效果,但是由于没有改变View的属性,对于一些特殊的动画执行不能很好的控制。
属性动画,具备补间动画的属性,同时可以改变View的真实属性(位置、大小等),可以实现各种复杂的动画效果。文章通过一个具体的案例对属性动画进行一个分析。
来源:产品需要实现一个页面的切换按钮的动画效果,设计按钮的旋转和字体大小的渐变。
先来一张图:


分解一下就是:分割条的旋转、文字旋转以及文字大小的改变。
先贴代码:在代码中的注释进行分析
public class SarrsVersionAnimationSwitcher extends FrameLayout {
private final staticString TAG= SarrsVersionAnimationSwitcher.class.getSimpleName();
private ImageView mDevider;
private TextView mBigFrontText;
private TextView mSmallFrontText;
private RelativeLayout mRootView;
//外围文字旋转的基础坐标以及旋转半径
private float mX,mY,mRdius;
private boolean isStartFromBig=true;
//旋转动画的组合器,用来对动画过程的监听
private AnimatorSet mAnimatorSet;
//具体的属性动画对象,通过插值器来对属性进行设置
private ValueAnimator mBig2SmallAnim,mSmall2BigAnim,mBigFrontRotation,mSmallFrontRotation;
//包装的监听器
private RotationAnimatorListener mRotationAnimatorListener;
public void setBigFrontText(CharSequence text) {
mBigFrontText.setText(text);
}
public void setSmallFrontText(CharSequence text) {
mSmallFrontText.setText(text);
}
public SarrsVersionAnimationSwitcher(Context context) {
this(context, null);
}
public SarrsVersionAnimationSwitcher(Context context,AttributeSet attrs) {
this(context,attrs,0);
}
public SarrsVersionAnimationSwitcher(Context context,AttributeSet attrs, int defStyleAttr) {
super(context,attrs,defStyleAttr);
this.initView();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed,l,t,r,b);
//测量完毕后进行初始化,因为需要获取跟布局的大小(也可以在onMeasure里面初始化)
this.initAnim();
}
private void initView() {
this.mRootView= (RelativeLayout) LayoutInflater.from(
MyApp.getContext()).inflate(R.layout.sarrs_toutiao_switcher_layout, this, false);
this.mDevider= (ImageView)mRootView.findViewById(R.id.sarrs_version_switch_devider);
this.mBigFrontText= (TextView)mRootView.findViewById(R.id.sarrs_version_switcher_big_front);
this.mSmallFrontText= (TextView)mRootView.findViewById(R.id.sarrs_version_switcher_small_front);
this.addView(this.mRootView);
this.setClickable(true);
}
protected void initAnim() {
this.mAnimatorSet=new AnimatorSet();
this.mAnimatorSet.setDuration(300).setInterpolator(new LinearInterpolator());
//选择一个对象对动画过程进行监听,动画过程中不支持点击
this.mAnimatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
SarrsVersionAnimationSwitcher.this.setEnabled(false);
if(mRotationAnimatorListener!=null) {
mRotationAnimatorListener.onAnimStart();
}
}
@Override
public void onAnimationEnd(Animator animation) {
isStartFromBig= !isStartFromBig;
SarrsVersionAnimationSwitcher.this.setEnabled(true);
if(mRotationAnimatorListener!=null) {
mRotationAnimatorListener.onAnimFinish();
}
}
@Override
public void onAnimationCancel(Animator animation) {
SarrsVersionAnimationSwitcher.this.setEnabled(true);
if(mRotationAnimatorListener!=null) {
mRotationAnimatorListener.onAnimCancel();
}
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
//改变字体大小——变小,通过插值器取值
mBig2SmallAnim= ValueAnimator.ofFloat(12.0f,8.0f);
mBig2SmallAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
floatsize = (float) animation.getAnimatedValue();
//此处注意对文字大小选用正确的单位,否则在喜欢系统字体大小时,会出现文字越界等问题
(isStartFromBig?mBigFrontText:mSmallFrontText).setTextSize(TypedValue.COMPLEX_UNIT_DIP,size);
}
});
//改变字体大小——变大,通过插值器取值
mSmall2BigAnim= ValueAnimator.ofFloat(8.0f,12.0f);
mSmall2BigAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
floatsize = (float) animation.getAnimatedValue();
(isStartFromBig?mSmallFrontText:mBigFrontText).setTextSize(TypedValue.COMPLEX_UNIT_DIP,size);
}
});
//轴点的位置:借助了分隔条的大小
mX=mRootView.getX() +mRootView.getWidth() /2;
mY=mRootView.getY() +mRootView.getHeight() /2;
mRdius=mDevider.getWidth() /2;
//旋转:通过自定义插值器来进行处理
//大字体旋转
mBigFrontRotation= ValueAnimator.ofObject(newBig2SmallTypeEvaluator(180,mRdius,135),mX,mY);
mBigFrontRotation.setInterpolator(new LinearInterpolator());
mBigFrontRotation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public voidonAnimationUpdate(ValueAnimator animation) {
PointF pointValue = (PointF) animation.getAnimatedValue();
if(BuildConfig.DEBUG) {
Log.d(TAG,"大字体旋转:x="+ pointValue.x+"  y="+ pointValue.y);
}
(isStartFromBig?mBigFrontText:mSmallFrontText).setX(pointValue.x- (isStartFromBig?mBigFrontText:mSmallFrontText).getWidth() /2);
(isStartFromBig?mBigFrontText:mSmallFrontText).setY(pointValue.y- (isStartFromBig?mBigFrontText:mSmallFrontText).getHeight() /2);
(isStartFromBig?mBigFrontText:mSmallFrontText).invalidate();
}
});
//小字体旋转
mSmallFrontRotation= ValueAnimator.ofObject(new Small2BigTypeEvaluator(-180,mRdius,225),mX,mY);
mSmallFrontRotation.setInterpolator(new LinearInterpolator());
mSmallFrontRotation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF pointValue = (PointF) animation.getAnimatedValue();
if(BuildConfig.DEBUG) {
Log.d(TAG,"小字体旋转:x="+ pointValue.x+"  y="+ pointValue.y);
}
(isStartFromBig?mSmallFrontText:mBigFrontText).setX(pointValue.x- (isStartFromBig?mSmallFrontText:mBigFrontText).getWidth() /2);
(isStartFromBig?mSmallFrontText:mBigFrontText).setY(pointValue.y- (isStartFromBig?mSmallFrontText:mBigFrontText).getHeight() /2);
(isStartFromBig?mSmallFrontText:mBigFrontText).invalidate();
}
});
}
private void setCircleAnimValueTarget() {
mBig2SmallAnim.setTarget(isStartFromBig?mBigFrontText:mSmallFrontText);
mSmall2BigAnim.setTarget(isStartFromBig?mSmallFrontText:mBigFrontText);
mBigFrontRotation.setTarget(isStartFromBig?mBigFrontText:mSmallFrontText);
mSmallFrontRotation.setTarget(isStartFromBig?mSmallFrontText:mBigFrontText);
}
//开始进行切换
public void runSwitchAnimation() {
setCircleAnimValueTarget();
mAnimatorSet.play(mBigFrontRotation).with(mBig2SmallAnim);
mAnimatorSet.play(mSmallFrontRotation).with(mSmall2BigAnim);
mAnimatorSet.start();
ObjectAnimator.ofFloat(mDevider,"rotation",0,-180).setDuration(300).start();
}
//自定义插值器,属性动画的扩展性的体现,在evaluate方法中可以针对动画进行的进度来处理原始数据,从而实现对动画过程的监听,就可以实现对原始数据的处理。
//切换动画自定义TypeEvaluator1
private class Big2SmallTypeEvaluator implements TypeEvaluator {
private intangle;
private floatradius;
private floatfromAngle;
public Big2SmallTypeEvaluator(int angle, float radius, int fromAngle) {
this.angle= angle;
this.radius= radius;
this.fromAngle= fromAngle;
}
@Override
public Object evaluate(float fraction,Object startX,Object startY) {
PointF point =newPointF();
//旋转的角度,根据旋转的角度计算View的坐标值
float angle = fraction *this.angle;
float floatY =mY- (float) (radius* Math.sin(Math.toRadians(angle +fromAngle)));
float floatX =mX+ (float) (radius* Math.cos(Math.toRadians(angle +fromAngle)));
point.set(floatX,floatY);
return point;
}
}
//切换动画自定义TypeEvaluator2
private class Small2BigTypeEvaluator implements TypeEvaluator {
private intangle;
private floatradius;
private intfromAngle;
public Small2BigTypeEvaluator(int angle, float radius, int fromAngle) {
this.angle= angle;
this.radius= radius;
this.fromAngle= fromAngle;
}
@Override
public Objecte valuate(float fraction,Object startX,Object startY) {
PointF point =newPointF();
//旋转的角度
float angle = fraction *this.angle;
float floatY =mY- (float) (radius* Math.sin(Math.toRadians(angle +fromAngle)));
float floatX =mX- (float) (radius* Math.cos(Math.toRadians(angle +fromAngle)));
point.set(floatX,floatY);
return point;
}
}
public void cancelAllAnim() {
mAnimatorSet.cancel();
}
public void addRotationAnimatorListener(RotationAnimatorListener rotationAnimatorListener) {
mRotationAnimatorListener= rotationAnimatorListener;
}
public interface RotationAnimatorListener {
void onAnimFinish();
void onAnimStart();
void onAnimCancel();
}
}
下面我们来分析下自定义插值器类TypeEvaluator
public interface TypeEvaluator {
/**
* This function returns the result of linearly interpolating the start and end values, with
*fractionrepresenting the proportion between the start and end values. The
* calculation is a simple parametric calculation:result = x0 + t * (x1 - x0),
* wherex0isstartValue,x1isendValue,
* andtisfraction.
*
*@paramfractionThe fraction from the starting to the ending values
*@param startValue The start value.
*@param endValue The end value.
*@return A linear interpolation between the start and end values, given the
*fractionparameter.
*/
public T evaluate(float fraction,T startValue,T endValue);
}
开发者可以实现这个接口对插值器进行自定义处理。
public T evaluate(float fraction,T startValue,T endValue)
这个方法的第一个参数是一个进度,取值0到1,后面两个参数从名字上就很容易区分。是在创建动画是传入的ValueAnimator.ofObject(newSmall2BigTypeEvaluator(-180,mRdius,225),mX,mY);
第一个值对应startValue,最后一个对应endValue。因此可以很好地处理动画运行过程中的细节问题。
总结一下吧:主要涉及到ValueAnimator 、ObjectAnimator、AnimatorSet三个属性动画的类,其中ObjectAnimator是ValueAnimator的子类,包装了一些常用的动画类型。AnimatorSet是动画的集合,可以利用它组合多个动画,进行统一管理。文章中重点涉及的TypeEvaluator是个自定义插值器。可以根据动画的进行程度,对传入的参数做处理,然后返回处理后的数据。
文章没有涉及具体的使用方法,具体使用方法可以参考API或者代码。

0 0