ObjectAnimator属性动画源码分析篇

又和大家见面了,这几天一直在忙大创项目,所以没有更新博客,而且我发现看源码这个东西必须写个博客或者笔记啊,这之前一段时机笔者已经看了ValueAnimator和ObjectAnimator的源码了,但是这才过了几天,搞了会别的事情就忘得几乎一干二净了。现在又要重头看一遍很痛苦额-。+。

另外,笔者已经在简书写了关于属性动画的比较系统的详细的文章,之后会陆续在CSDN上重新写的(是重新写,不是复制过去哦,因为第一次写的实在是太烂了-。=)

好了不继续扯皮了,我们看来一下今天想要讲的东西——ObjectAnimator的源码分析(使用部分)。

ObjectAnimator使用部分源码

我们都知道属性动画使用分为三部分:创建、添加属性、启动。而我们今天要讲的就是关于创建和添加属性。首先来看创建的源码吧:

创建

首先看一下今天所有用到的背景:

写了一个自定义的View——PointView,用来实现一个小球的移动效果,PointView代码如下(可以不用看-。+):

public class PointView extends View {
  private Point mCurrentPoint;
  private Paint paint;
  /**
   * 两个构造方法
   **/
  public PointView(Context context) {
    super(context);
    paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setColor(Color.CYAN);
    paint.setStrokeWidth(5);
    paint.setStyle(Paint.Style.STROKE);
  }
  public PointView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setColor(Color.CYAN);
    paint.setStrokeWidth(5);
    paint.setStyle(Paint.Style.STROKE);
  }
  /**
   * onDraw开始使用画笔,如果mCurrentPoint为空,就创建Point对象,
   * 否则就直接调用drawPoint方法
   **/
  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (mCurrentPoint == null) {
      mCurrentPoint = new Point(500, 500);
      drawPoint(canvas);
    } else {
      drawPoint(canvas);
    }
  }
  //设置一个属性添加的方法
  public void setMove(Point point) {
    mCurrentPoint = point;
    invalidate();
  }
  //启动动画
  private void startAnimation() {
    ObjectAnimator animator = ObjectAnimator.ofObject(PointView.this, "Move", new BallEvaluator(),
        new Point(500, 500),
        new Point(500, 800), new Point(700, 800));
    animator.setInterpolator(new LinearInterpolator());
    animator.setDuration(5000);
    animator.start();
  }
  //在外部调用的方法,通过此方法,开启小球动作的动画。
  public void moveBall() {
    startAnimation();
  }
  private void drawPoint(Canvas canvas) {
    canvas.drawCircle(mCurrentPoint.getX(), mCurrentPoint.getY(), 30, paint);
  }
  //小球对象的估值器
  public class BallEvaluator implements TypeEvaluator<Point> {
    float x;
    float y;
    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {
      float startX = startValue.getX();
      float startY = startValue.getY();
      float endX = endValue.getX();
      float endY = endValue.getY();
      x = fraction * (endX - startX);
      y = fraction * (endY - startY);
      Log.e("ASDSAD", "x = " + x + "y = " + y);
      return new Point(startX + x, startY + y);
    }
  }
}

代码可能有点多,不过这不是主要的,我们今天关注的只是属性动画,所以我们只需要看里面的startAnimation方法和setMove方法就好:

  • setMove:由于我们知道属性动画ObjectAnimator类是通过将propertyName拼接成对应的set方法,然后通过反射机制去调用该方法,所以我们需要有一个对应的set方法。
  • startAnimation:这个方法我们用来设置我们的动画以及启动动画。setMove方法,很简单,我们只是将传入的新的小球对象赋值给了mCurrentBall,然后调用invalidate方法重新绘制。下面看一下startAnimation方法:
ObjectAnimator animator = ObjectAnimator.ofObject(PointView.this, "Move", new BallEvaluator(),
        new Point(500, 500),
        new Point(500, 800), new Point(700, 800));
    animator.setInterpolator(new LinearInterpolator());
    animator.setDuration(5000);
    animator.start();

我们看一下先进入ofObject方法(相信ofObject里面的参数大家都看得懂):

public static ObjectAnimator ofObject(Object target, String propertyName,
      TypeEvaluator evaluator, Object... values) {
    ObjectAnimator anim = new ObjectAnimator(target, propertyName);
    anim.setObjectValues(values);
    anim.setEvaluator(evaluator);
    return anim;
  }

我们发现ofObject是一个静态方法:他在里面创建了一个ObjectAnimator对象。然后调用了setObjeectValues和setEvaluator方法,分别添加了数据和估值器。

也就是这个ofObject里面有三个入口等着我们进去:

  • ObjectAnimator的构造方法
  • setObjectValues方法
  • setEvaluator方法

那我们先从ObjectAnimator的构造方法开始看吧。

ObjectAnimator构造方法:

  private ObjectAnimator(Object target, String propertyName) {
    setTarget(target);
    setPropertyName(propertyName);
  }

又是两个方法,一个一个去看,先进入setTarget:

  @Override
  public void setTarget(@Nullable Object target) {
    final Object oldTarget = getTarget();
    if (oldTarget != target) {
      if (isStarted()) {
        cancel();
      }
      mTarget = target == null ? null : new WeakReference<Object>(target);
      // New target should cause re-initialization prior to starting
      mInitialized = false;
    }
  }

这些代码都能够知道什么意思,这个方法我们只需要注意两点:

  • 我们将传入的target传给了mTarget这个弱引用。
  • mInitialized = false;这个属性我们可以这么理解,在他的ObjectAnimator没有准备就绪(初始化过程尚未完成时),他一直都是false。

这个方法跳过。

然后我们看setPropertyName方法:

  public void setPropertyName(@NonNull String propertyName) {
    // mValues could be null if this is being constructed piecemeal. Just record the
    // propertyName to be used later when setValues() is called if so.
    if (mValues != null) {
      PropertyValuesHolder valuesHolder = mValues[0];
      String oldName = valuesHolder.getPropertyName();
      valuesHolder.setPropertyName(propertyName);
      mValuesMap.remove(oldName);
      mValuesMap.put(propertyName, valuesHolder);
    }
    mPropertyName = propertyName;
    // New property/values/target should cause re-initialization prior to starting
    mInitialized = false;
  }

首先介绍一下mValues和mValuesMap这两个属性,他们都是存储PropertyValueHolder的属性,而且储存的都一样,只是mValuesMap可以让我们通过propertyName来查找对应的PropertyValueHolder。

PropertyValuesHolder[] mValues;
HashMap<String, PropertyValuesHolder> mValuesMap;

这个方法只是将propertyName放入PropertyValueHolder中(具体逻辑如上,先判断mValues是否为空,如果不为空就将propertyName放入mValues和mValuesMap中,最后将propertyName赋值给mPropertyName),可以过了。

现在我们的ObjectAnimator构造方法看完了,我们接着看setObjectValues方法:

anim.setObjectValues:

  @Override
  public void setObjectValues(Object... values) {
    if (mValues == null || mValues.length == 0) {
      // No values yet - this animator is being constructed piecemeal. Init the values with
      // whatever the current propertyName is
      if (mProperty != null) {
        setValues(PropertyValuesHolder.ofObject(mProperty, (TypeEvaluator) null, values));
      } else {
        setValues(PropertyValuesHolder.ofObject(mPropertyName,
            (TypeEvaluator) null, values));
      }
    } else {
      super.setObjectValues(values);
    }
  }

这段代码的总体逻辑只有一个:如果mValues没有值,那么就调用setValues方法,否则就调用父类的setObjectValues方法。

感觉很乱啊,稳住!   我们这是第一次创建的对象,所以肯定是为空的,所以我们只需要看setValues方法就好了,但是注意,这里还有PropertyValueHolder,所以我们决定先看一下PropertyValueHolder的ofObject方法:

PropertyValueHolder.ofObject:

  public static <V> PropertyValuesHolder ofObject(Property property,
      TypeEvaluator<V> evaluator, V... values) {
    PropertyValuesHolder pvh = new PropertyValuesHolder(property);
    pvh.setObjectValues(values);
    pvh.setEvaluator(evaluator);
    return pvh;
  }

跟上面ObjectAnimator的ofObject差异不多,我们就不多说了,有两条路可以选:

  • setObjectValues
  • setEvaluator

先看setObjectValues:

PropertyValueHolder.setObjectValues:

  public void setObjectValues(Object... values) {
    mValueType = values[0].getClass();
    mKeyframes = KeyframeSet.ofObject(values);
    if (mEvaluator != null) {
      mKeyframes.setEvaluator(mEvaluator);
    }
  }

我知道大家都快吐了,现在KeyFrames又出来了,头皮发麻对吧,稳住,我们坚持住!

这个KeyFrames是KeyFrameSet的接口,我们看一下KeyframeSet的ofObject方法:

KeyframeSet.ofObject方法:

  public static KeyframeSet ofObject(Object... values) {
    int numKeyframes = values.length;
    ObjectKeyframe keyframes[] = new ObjectKeyframe[Math.max(numKeyframes,2)];//创建了一个至少为两位的ObjectKeyFrame对象
    if (numKeyframes == 1) {
      keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f);
      keyframes[1] = (ObjectKeyframe) Keyframe.ofObject(1f, values[0]);
    } else {
      keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f, values[0]);
      for (int i = 1; i < numKeyframes; ++i) {
        keyframes[i] = (ObjectKeyframe) Keyframe.ofObject((float) i / (numKeyframes - 1), values[i]);
      }
    }
    return new KeyframeSet(keyframes);
  }

我们终于看到了一个比较熟悉的类,KeyFrame,这个叫做关键帧的类终于出现了,我们简单分析一下这个方法:

首先创建了一个至少为两位的ObjectKeyFrame对象,然后对values的长度进行判断,如果只有一个值,那么就将唯一的一个值添加到最后一位(此时也就是第二位),否则就依次添加。最后将ObjectKeyFrame的数组转换成KeyFrameSet类型返回。

现在我们回到PropertyValueHolder的setObjectValues方法中,接下来我们要看一下setEvaluator方法(需要在KeyFrameSet中查看)

KeyFrameSet.setEvaluator方法:

  public void setEvaluator(TypeEvaluator evaluator) {
    mEvaluator = evaluator;
  }

这个不用多说,直接过。

现在我们的PropertyValueHolder的ofObject方法已经看完了,我们跳回anim.setObjectValues方法,看一下setValues方法:

setValues:

  public void setValues(PropertyValuesHolder... values) {
    int numValues = values.length;
    mValues = values;
    mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
    for (int i = 0; i < numValues; ++i) {
      PropertyValuesHolder valuesHolder = values[i];
      mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
    }
    // New property/values/target should cause re-initialization prior to starting
    mInitialized = false;
  }

这个完全就是我们刚才说的储存PropertyValueHolder的两个属性mValues和mValuesMap添加数据的过程,过。

现在我们只差最后一个大方法了,anim.setEvaluator了,激动!!!

anim.setEvaluator方法:

  public void setEvaluator(TypeEvaluator value) {
    if (value != null && mValues != null && mValues.length > 0) {
      mValues[0].setEvaluator(value);
    }
  }

这个方法是给每个PropertyValueHolder对象都执行setEvaluator方法,我们点进去这个方法看一下:

PropertyValueHolder.setEvaluator方法:

  public void setEvaluator(TypeEvaluator evaluator) {
    mEvaluator = evaluator;
    mKeyframes.setEvaluator(evaluator);
  }

又进入了Keyframes的setEvaluator,我们接着看一下KeyFrameSet的setEvaluator方法:

KeyFrameSet.setEvaluator方法:

  public void setEvaluator(TypeEvaluator evaluator) {
    mEvaluator = evaluator;
  }

这个方法不用说了。

最后我们返回了创建的anim的对象,到现在为止,我们得到我们想要的ObjectAnimator对象了。

这个过程是有点繁琐,我们现在屡一下思路:

调用了ObjectAnimator.ofObject之后

  1. 首先new一个ObjectAnimator对象,进入ObjectAnimator的构造方法中:在构造方法中,我们执行了两个方法:setTarget和setPropertyName。
  2. 然后调用了ObjectAnimator的setObjectValues方法:在这个方法中我们首先实例化了PropertyValueHolder对象,然后调用setValues方法将PropertyValueHolder传入。
  3. 之后调用了ObjectAnimator的setEvaluator方法:添加了估值器。
  4. 最后返回了ObjectAnimator对象。

到这里我们就完成了ObjectAnimator对象实例的创建。

到这里创建部分就全部完成了,接下来我们看一下添加属性,这个就很简单了。

添加属性

我们就举一个方法为例吧,拿setInterpolator方法为例:

  public void setInterpolator(TimeInterpolator value) {
    if (value != null) {
      mInterpolator = value;
    } else {
      mInterpolator = new LinearInterpolator();
    }
  }

这个方法是我们添加插值器的方法,我们注意到他只是给mInterpolator赋值而已,如果传入为空,则添加线性插值器。

其他的添加属性的方法,像setDuration、setRepeatCount等都是如此,大家下去就自己看一下吧。

由于还不会用 starUML,所以现在还没发画一张时序图(只是手画的,估计大家也不想看哈哈),等学完UML之后会给大家补上的,希望这篇文章大家喜欢。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。如果你想了解更多相关内容请查看下面相关链接

(0)

相关推荐

  • Android实现百度地图两点画弧线

    本文实例为大家分享了Android实现百度地图两点画弧线的具体代码,供大家参考,具体内容如下 import android.support.annotation.NonNull; import com.baidu.mapapi.map.ArcOptions; import com.baidu.mapapi.map.OverlayOptions; import com.baidu.mapapi.model.LatLng; /** * * http://lbsyun.baidu.com/index.

  • Android自定义View仿腾讯TIM下拉刷新View

    一 概述 自定义 View 是 Android 开发里面的一个大学问.偶然间看到 TIM 邮箱界面的刷新 View 还挺好玩的,于是就自己动手实现了一个,先看看 TIM 里边的效果图: 二 需求分析 看到上面的动图,大概也知道我们需要实现的功能: 根据拖动的进度来移动小球的位置 小球移动过程的动画 三 功能实现 新建一个 RefreshView 类继承自 View ,然后我们再在 RefreshView 里面新建一个内部实体类: Circle 来看一下 Circle类的代码 #Cirlce.ja

  • Android自定义View实现简单炫酷的球体进度球实例代码

    前言 最近一直在研究自定义view,正好项目中有一个根据下载进度来实现球体进度的需求,所以自己写了个进度球,代码非常简单.先看下效果: 效果还是非常不错的. 准备知识 要实现上面的效果我们只要掌握两个知识点就好了,一个是Handler机制,用于发消息刷新我们的进度球,一个是clipDrawable.网上关于Handler的教程很多,这里重点介绍一下clipDrawable,进度球的实现全靠clipDrawable. clipDrawable 如下图所示:ClipDrawable和InsertDr

  • Android中WindowManager与WMS的解析

    最近在改bug的时候发现在windowManager.addView的时候会发生莫名其妙的崩溃,那个崩溃真的是让你心态爆炸,潜心研究了两天window相关的东西,虽然不是很深奥的东西,本人也只是弄清楚了window的添加逻辑,在此分享给大家: 一.悬浮窗的概念 在android中,无论我们的app界面,还是系统桌面,再或者是手机下方的几个虚拟按键和最上方的状态栏,又或者是一个吐司...我们所看到的所有界面,都是由一个个悬浮窗口组成的. 但是这些窗口有不同的级别: 系统的是老大,是最高级别,你没见

  • Android高性能日志写入方案的实现

    前言 公司目前在做一款企业级智能客服系统,对于系统稳定性要求很高,不过难保用户在使用中不会出现问题,而 Android SDK 集成在客户的 APP 中,同时由于 Android 碎片化的问题,对于 SDK 的问题排查就显得尤为困难,因此记录下用户的操作日志就显得极为重要. 初始方案 一开始,SDK 记录日志的方式是直接通过写文件,当有一条日志要写入的时候,首先,打开文件,然后写入日志,最后关闭文件.这样做的问题就在于频繁的IO操作,影响程序的性能,而且 SDK 为了保证消息的及时性,还维护了一

  • Android动态修改应用图标与名称的方法实例

    遇到的坑 这里我把做这个功能中遇到的一些问题写在前面,是为了大家能先了解有什么问题存在,遇到这些问题的时候就不慌了,这里我把应用图标和名称先统一使用icon代替进行说明. 1.动态替换icon,只能替换内置的icon,无法从服务器端获取来更新icon: 2.动态替换icon以后,应用内更新的时候必须要切换到原始icon),否则可能导致更新安装失败(AS上表现为adb运行会失败),或者升级后应用图标出现多个甚至应用图标都不显示的情况(这些问题都可以通过下面我推荐的开发规则解决掉,所以这是一个坑点,

  • Android百度地图定位、显示用户当前位置

    本文实例为大家分享了Android百度地图定位.显示用户当前位置的工具类,供大家参考,具体内容如下 1.构建定位Option的工具类 import com.baidu.location.LocationClientOption; /** * 建造 LocationClientOption 项 * * @author peter 2018-12-21 10:58 */ public class LocationClientOptionBuilder { private LocationClient

  • Android利用ObjectAnimator实现ArcMenu

    本文介绍利用ObjectAnimator简单地实现ArcMenu,直接使用本文的ArcMenu类即可快捷地实现菜单功能. 最终使用效果: 先看下最终的使用效果: private int[] imageRes = {R.id.img_menu, R.id.img_menu1, R.id.img_menu2, R.id.img_menu3, R.id.img_menu4, R.id.img_menu5}; private ArcMenu arcMenu; ... //初始化,参数为资源图片id ar

  • Android自定义动态壁纸开发(时钟)

    看到有些手机酷炫的动态壁纸,有没有好奇过他们是如何实现的,其实我们自己也可以实现. 先看效果 上图是动态壁纸钟的一个时钟. 我们先来看看 Livewallpaper(即动态墙纸)的实现,Android的动态墙纸并不是GIF图片,而是一个标准的Android应用程序,也就是APK.既然是应用程序,当然意味着天生具有GIF图片不具备的功能--能与用户发生交互,而且动态的背景变化绝不仅仅局限于GIF图片那般只能是固定的几张图片的循环播放.但是我们在这里没有加入与用户交互的动作,只是加入一个时钟(当然时

  • Android获取其他应用中的assets资源

    最近有这样一个需求:A应用在一定条件下出发某个逻辑后,需要从B应用中获取一些资源(assets下的mp4视频.还有drawable下的一些图片用作背景),具体需求就不说啦哈哈,用一张图来表示应该更明白: A和B应用其实是1对多的关系,不同的B应用需要从他们自己的地方获取到资源给A. 一般我们获取app内的资源肯定是要获取到Resource这个类,而Resource是通过Context类的getResource获取到了,所以我们只需要获取到B应用的Context类就可以了. 可是其他App的Con

随机推荐