美文网首页Android精选自定义控件
安卓自定义差值器的属性动画

安卓自定义差值器的属性动画

作者: lotty_wh | 来源:发表于2017-04-20 16:35 被阅读64次

    安卓动画大致可以分为3大类,主要是针对View。

    帧动画,类似于放电影,一帧一帧的进行播放,需要多张图片资源进行支撑,会加大客户端的体积,而且扩展性差,几乎不使用。

    补间动画,具备平移、缩放、旋转属性,组合后可以实现一些酷炫的动画效果,但是由于没有改变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, intdefStyleAttr) {

    super(context,attrs,defStyleAttr);

    this.initView();

    }

    @Override

    protected void onLayout(booleanchanged, intl, intt, intr, intb) {

    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=newAnimatorSet();

    this.mAnimatorSet.setDuration(300).setInterpolator(newLinearInterpolator());

    //选择一个对象对动画过程进行监听,动画过程中不支持点击

    this.mAnimatorSet.addListener(newAnimator.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(newValueAnimator.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(newValueAnimator.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(newLinearInterpolator());

    mBigFrontRotation.addUpdateListener(newValueAnimator.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(newSmall2BigTypeEvaluator(-180,mRdius,225),mX,mY);

    mSmallFrontRotation.setInterpolator(newLinearInterpolator());

    mSmallFrontRotation.addUpdateListener(newValueAnimator.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方法中可以针对动画进行的进度来处理原始数据,从而实现对动画过程的监听,就可以实现对原始数据的处理(g)。

    //切换动画自定义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或者代码。

    相关文章

      网友评论

        本文标题:安卓自定义差值器的属性动画

        本文链接:https://www.haomeiwen.com/subject/qlnkzttx.html