android 属性动画使用

分类


安卓动画大致可以分为3大类。
帧动画,类似于放电影,一帧一帧的进行播放,需要多张图片资源进行支撑,会加大客户端的体积,而且扩展性差,还容易造成OOM,较少使用。

View动画,具备平移、缩放、旋转属性,组合后可以实现一些酷炫的动画效果,但是由于没有改变View的属性,对于一些特殊的动画执行不能很好的控制。

属性动画,具备补间动画的属性,同时可以改变View的真实属性(位置、大小等),可以实现各种复杂的动画效果。文章通过一个具体的案例对属性动画进行一个分析。

 产品效果


来源:产品需要实现一个页面的切换按钮的动画效果,设计按钮的旋转和字体大小的渐变。


具体实现


效果分析


对动画进行分解:分割条的旋转、文字旋转以及文字大小的改变。


自定义ViewGroup

SarrsVersionAnimationSwitcher extends FrameLayout

通过组合来实现子元素填充和联动,具体为 分割线、文字、背景。

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 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();
  }

  public void setBigFrontText(CharSequence text) {
    mBigFrontText.setText(text);
  }

  public void setSmallFrontText(CharSequence text) {
    mSmallFrontText.setText(text);
  }

  @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();
  }

  public void cancelAllAnim() {
    mAnimatorSet.cancel();
  }

  public void addRotationAnimatorListener(RotationAnimatorListener rotationAnimatorListener) {
    mRotationAnimatorListener = rotationAnimatorListener;
  }

  /**
  * 对外暴露的接口
  */
  public interface RotationAnimatorListener {
    void onAnimFinish();

    void onAnimStart();

    void onAnimCancel();
  }
}


属性动画差值器


属性动画中使用较多的差值器有线性匀速、线性加速度、线性减速等加速器...

本例中使用的是线性匀速差值器,来达到匀速旋转的目的:

LinearInterpolator


属性动画估值器

下面我们来分析下自定义估值器类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。因此可以很好地处理动画运行过程中的细节问题。

本例中,要求TextView在动画执行期间进行旋转以及字体大小变化。因此需要进行自定义估值器来获取TextView下一帧动画的属性值(主要是旋转中心的坐标)

本例中的实现了两个估值器。
字体缩小+旋转估值器:

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;
    }
  }

字体放大+旋转估值器:

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;
    }
  }

总结一下


主要涉及到ValueAnimator 、ObjectAnimator、AnimatorSet三个类,其中ObjectAnimator是ValueAnimator的子类,包装了一些常用的动画类型。AnimatorSet是动画的集合,可以利用它组合多个动画,进行统一管理。文章中重点涉及的TypeEvaluator是个估值器接口。可以根据动画的进行程度,对传入的参数做处理,然后返回处理后的数据。
文章没有涉及具体的使用方法,具体使用方法可以参考API或者代码。


​​​

发布了36 篇原创文章 · 获赞 9 · 访问量 4895

猜你喜欢

转载自blog.csdn.net/lotty_wh/article/details/70862948