nineoldandroids.jar怎么让绕着画圆

Android中通过ViewHelper.setTranslationY实现View移动控制(NineOldAndroids开源项目) - 移动开发 - 程序员之家论坛 -
Powered by Discuz! Archiver
Android中通过ViewHelper.setTranslationY实现View移动控制(NineOldAndroids开源项目)
&p&&span style=&font-family:SimH font-size:18px&&我们知道有不少开源工程,能实现很多不错的效果。前几天,我看了一个效果,刚好项目中也用到了这个jar包。没事挖一挖 学一学(一说到挖一挖,那么问题来了,挖掘机技术到底哪家强&img alt=&大笑& src=&http://img.it-home.org/data/attachment/forum/2014pic/laugh.gif&& ),看看不错的效果怎么实现的呢?函数的具体意义有是什么呢?很多效果,&strong&我们不需要重新造轮子,只需要装配轮子,开车走起就可以&/strong&了,你没有那么多时间造轮子&span style=&color:#ff0000&&,我们可以选择自己喜欢的轮子,开我们的兰博基尼,开我们的保时捷概念车,开起来&/span&!&/span&&/p&
&p&&span style=&font-family:SimH font-size:18px&&先看一下效果吧!挖自开源项目中的(&strong&&span style=&background-color:rgb(0,153,0)&&NineOldAndroids开源项目&/span&&/strong&)&/span&&/p&
&p&&span style=&font-family:SimH font-size:18px&&& &&img alt=&& src=&http://img.it-home.org/data/attachment/forum/2014pic/16600.gif&&&/span&&/p&
&p&&span style=&font-family:SimH font-size:18px&&不废话。我们解释下相关函数。&/span&&/p&
&span style=&font-family:SimH font-size:18px&&&/span&& & & & private View header_
& & & & private float moveDistanceY = 25;// logo初始移动距离为10
& & & & private float moveDistanceX = 25;// logo初始移动距离为10
& & & & public void onClick(View v) {
& & & & & & & & switch (v.getId()) {
& & & & & & & & & & & & case R.id.btn_moveDown :
& & & & & & & & & & & & & & & & moveDistanceY += 20;
& & & & & & & & & & & & & & & & ViewHelper.setTranslationY(header_logo, moveDistanceY);
& & & & & & & & & & & & & & & &
& & & & & & & & & & & & case R.id.btn_moveUp :
& & & & & & & & & & & & & & & & moveDistanceY -= 20;
& & & & & & & & & & & & & & & & ViewHelper.setTranslationY(header_logo, moveDistanceY);
& & & & & & & & & & & & & & & &
& & & & & & & & & & & & case R.id.btn_moveLeft :
& & & & & & & & & & & & & & & & moveDistanceX -= 20;
& & & & & & & & & & & & & & & & ViewHelper.setTranslationX(header_logo, moveDistanceX);
& & & & & & & & & & & & & & & &
& & & & & & & & & & & & case R.id.btn_moveRight :
& & & & & & & & & & & & & & & & moveDistanceX += 20;
& & & & & & & & & & & & & & & & ViewHelper.setTranslationX(header_logo, moveDistanceX);
& & & & & & & & & & & & & & & &
& & & & & & & & & & & & default :
& & & & & & & & & & & & & & & &
& & & & & & & & }
&p&& &关于&span style=&color:#ff0000&& ViewHelper.setTranslationY(view,float)&/span&函数的解释。这里的view 是您要移动哪个View 就是哪个东西你要将他在界面上进行活动呢? float是指你移动的距离 ,(假定参考坐标 最开始的位置中心位置为0)。那么ViewHelper.setTranslationY(view,100)就是把view向下(比最原始的位置)移动100,那么120那 130那 140 150呢&/p&
&p&就是比最原始的位置多10、多20、多30、多40。。。。所以&strong&&span style=&color:#ff0000&&ViewHelper.setTranslationX 还是&/span&&/strong&ViewHelper.setTranslationY都可以实现了。还是上源代码吧。要不你们看不到效果是不是要骂我了。嘎嘎。&/p&
&p&要用到&strong&&span style=&color:#ff0000; background-color:rgb(255,255,51)&&ViewHelper&/span&&/strong&,需要引用jar包。开源工程的jar包&/p&
&p&&img alt=&& src=&http://img.it-home.org/data/attachment/forum/2014pic/28796.png&&&/p&
&p&& & &a target=&_blank&target=&_blank&& 本工程源码下载地址 :猛击这里,以后保证 每周必须更新一篇最新的技术文章,每天要努力 挖掘技术点(不是开挖掘机,开挖掘机还是去蓝翔开去)。要不,你们以为我这么长时间都不出气,是不是挂了?还是被车撞了,趟医院起不来了。。。。。&img alt=&大笑& src=&http://img.it-home.org/data/attachment/forum/2014pic/laugh.gif&&&/a&&/p&
查看完整版本:打造简易NineoldAndroids动画库,深入理解Android动画原理
Nineolds是Github上一个著名的动画库,简单来说,NineOldAndroids是一个向下兼容的动画库,主要是使低于API 11的也能够使用View的属性动画。
网上已经有一些文章,介绍了这个库的设计,包括类结构和思想,例如
NineOldAnimations 解析
NineoldAndroids动画库源码分析
上面两篇文章都比较详细的介绍了NineoldAndroids的源码,可以说为大家看源码带来很大的方便。
那为什么我还要写这篇文章呢?
我们来看NineoldAndroids的类结构图:
因为NineoldAndroids的类结构比较复杂,即使单纯看上面两篇文章,也可能把人搞糊涂。
本篇文章将剥离NineoldAndroids的具体细节,尝试只是显示其核心功能,也就是说写出一个简易的NineoldAndroids,并且在这个过程当中,了解Android实现动画的原理和思想。
一理通百理明,与君共勉。
1、本动画库以Int类型的属性值为例子,实现了Android库中ValueAnimator的功能,不了解ValueAnimator使用方式的朋友,可以参考这篇文章
2、大部分代码由NineoldAndroids中抽取,剥去一些不必要的实现细节,例如delayStart()方法等
3、ValueAnimator与ObjectAnimator有所区别,本库还没有实现ObjectAnimator。ObjectAnimator是继承自ValueAnimator,本质是通过ValueAnimator计算出的值,去更新View的属性。
现在我们先来看看该动画库的使用:
public class MainActivity extends Activity {
TextView mTextV
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.mtext);
//Value动画,设置目标值为3000
CValueAnimator valueAnimator = CValueAnimator.ofInt(00);
//设置动画时间
valueAnimator.setDuration(4000);
valueAnimator.addUpdateListener(new CValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(CValueAnimator animation) {
//将动画值,更新到textView
mTextView.setText(animation.getAnimatedValue() + );
mTextView.setTranslationY((Integer) animation.getAnimatedValue());
mTextView.invalidate();
//启动动画
valueAnimator.start();
使用方式和NineoldAndroids完全一样,也和Android原生的方式一样,对于使用过动画效果的朋友来说,应该非常简单。
首先来看类设计图,这图相比原来的NineoldAndroids,做了很多精简,只是希望大家更加容易看懂NineoldAndroids的本质。
在进行下一步的分析之前,我们先来了解一下一些核心的类以及它们的作用。
CValueAnimator : 该类是 Animator 的子类,实现了动画的整个处理逻辑,也是最为核心的类;TimeInterpolator : 时间插值器,它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有 LinearInterpolator(线性插值器:匀速动画)、AccelerateDecelerateInterpolator(加速减速插值器:动画两头慢中间快)和 DecelerateInterpolator(减速插值器:动画越来越慢)等;CTypeEvaluator : TypeEvaluator 的中文翻译为类型估值算法,它的作用是根据当前属性改变的百分比来计算改变后的属性值,系统预置的有 IntEvaluator(针对整型属性)、FloatEvaluator(针对浮点型属性);CPropertyValuesHolder : PropertyValuesHolder 是持有目标属性 Property、setter 和 getter 方法、以及 KeyFrameSet 的类;CKeyFrame : 一个 keyframe 对象由一对 time / value 的键值对组成,可以为动画定义某一特定时间的特定状态,Animator 传入的一个个参数映射为一个个 keyframe,存储相应的动画的触发时间和属性值;CKeyFrameSet : 存储一个动画的关键帧集合;
动画流程解析
1、动画初始化
在调用start()方法之前,我们使用
CValueAnimator valueAnimator = CValueAnimator.ofInt(00);
做了动画的初始化工作,那么我们具体做了上面呢?
1.1、关键帧
简单而言,就是把传入的属性值,例如例子中是00,封装成关键帧对象CKeyFrame。
所谓关键帧,就是在动画过程中一定要出现的帧。
我们知道,所谓动画也不可能是完全连续的,肯定会有一些间隔,只是间隔小于人眼视觉暂留时间,所以看起来就是连续的了。
所以从这个过程,也不可能是完全连续的,也许是,&
其中一些帧就被丢失了,绝对不能丢失的帧,称为关键帧。
关键帧保留两个属性,一个是该帧所在的时间(其实是一个百分比),一个是帧值。
public abstract class CKeyframe implements Cloneable {
* 属性值类型
Class mValueT
private /*Time*/Interpolator mInterpolator =
public static CKeyframe ofInt(float fraction, int value) {
return new IntCKeyframe(fraction, value);
public static CKeyframe ofInt(float fraction) {
return new IntCKeyframe(fraction);
public abstract Object getValue();
* INT类型值得关键帧
public static class IntCKeyframe extends CKeyframe {
* 关键帧的值
IntCKeyframe(float fraction, int value) {
mFraction =
mValueType = int.
IntCKeyframe(float fraction){
mFraction =
mValueType = int.
public int getIntValue() {
public void setValue(Object value) {
if (value != null && value.getClass() == Integer.class) {
mValue = ((Integer)value).intValue();
public Object getValue() {
public float getFraction() {
public /*Time*/Interpolator getInterpolator() {
1.2、关键帧集合
我们生成关键帧对象以后,将关键帧存入一个集合,称为关键帧集合,也就是CKeyFrameSet类。
CKeyFrameSet类中有一个CTypeEvaluator成员对象,这对象可以通过当前动画进行的百分比,计算出两个关键帧之间的值。
public interface CTypeEvaluator {
public T evaluate(float fraction, T startValue, T endValue);
public class IntCEvaluator implements CTypeEvaluator {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startV
return (int)(startInt + fraction * (endValue - startInt));
可以看到,IntCEvaluator其实就是一个线性计算,fraction是百分比,startValue是起始值,endValue是目标值,不同的fraction会产生不同的结果。
显然对于两个关键帧来说,前一个关键帧的值,就是起始值,后一个关键帧的值,就是目标值。
在CKeyFrameSet中是这样调用这个方法:
public int getIntValue(float fraction) {
if (mNumKeyframes == 2) {//只有两个关键帧的情况
if (firstTime) {
firstTime =
firstValue = ((CKeyframe.IntCKeyframe) mKeyframes.get(0)).getIntValue();
lastValue = ((CKeyframe.IntCKeyframe) mKeyframes.get(1)).getIntValue();
deltaValue = lastValue - firstV
if (mInterpolator != null) {
fraction = mInterpolator.getInterpolation(fraction);
if (mEvaluator == null) {
return firstValue + (int)(fraction * deltaValue);
return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).intValue();
CKeyframe.IntCKeyframe prevKeyframe = (CKeyframe.IntCKeyframe) mKeyframes.get(0);
for (int i = 1; i & mNumK ++i) {//多个关键帧
CKeyframe.IntCKeyframe nextKeyframe = (CKeyframe.IntCKeyframe) mKeyframes.get(i);
if (fraction & nextKeyframe.getFraction()) {
final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
int prevValue = prevKeyframe.getIntValue();
int nextValue = nextKeyframe.getIntValue();
return mEvaluator == null ?
prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
intValue();
prevKeyframe = nextK
// shouldn't get here
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue();
方法有点长,大家可以看只有两个关键帧的情况是怎么计算的,就比较简单,其实就是线性计算。
1.3、非线性方法
问题是,难道每次我们都希望直接是线性增长的吗?如果我希望先快后慢呢?
了解插值器的朋友,应该就明白,这个功能我们可以通过Interpolator去做,但是Interpolator改变的是fraction的增长速度,也就是加速度(勉强可以这样理解)。
从而实现非线性效果,所以显然CKeyframeSet要持有一个Interpolator对象。
1.4、CPropertyValuesHolder类,持有属性名称和CKeyFrameSet
顾名思义,CPropertyValuesHolder就是持有属性和值得一个类。
CPropertyValuesHolder是动画库中的一个核心类,但是在本简易库削减了其功能,因为我们只需要实现值得变化,没有针对具体的属性,例如scale,rotate等,所以不需要提供View属性修改的方法。
其实这个类,是为ObjectAnimator做了比较大的准备,但是本篇文章不涉及。
我们关注的是,这个类持有CKeyFrameSet。
public class CPropertyValuesHolder implements Cloneable {
* 属性名称
String mPropertyN
* 关键帧集合
CKeyframeSet mKeyframeSet =
private static final CTypeEvaluator sIntEvaluator = new IntCEvaluator();
private static final CTypeEvaluator sFloatEvaluator = new FloatCEvaluator();
private CTypeEvaluator mEvaluator = sIntE
private Object mAnimatedV
* 属性值类型
Class mValueT
private CPropertyValuesHolder(String propertyName) {
mPropertyName = propertyN
* 返回一个属性值类型为int的CPropertyValuesHolder
* @param propertyName
* @param values
public static CPropertyValuesHolder ofInt(String propertyName, int... values) {
return new IntCPropertyValuesHolder(propertyName, values);
* 属性值类型为int的CPropertyValuesHolder
static class IntCPropertyValuesHolder extends CPropertyValuesHolder {
IntCKeyframeSet mIntKeyframeS
public IntCPropertyValuesHolder(String propertyName, int... values) {
super(propertyName);
setIntValues(values);
public void setIntValues(int... values) {
super.setIntValues(values);
mIntKeyframeSet = (IntCKeyframeSet) mKeyframeS
* 返回当前属性值
Object getAnimatedValue() {
return mAnimatedV
* 设置属性值类型,这里具体到int类型
* 对于每个Int类型值,例如100,200,1000
* 生成一个CKeyFrame关键帧对象,并且将这些对象包装成一个set集合
* @param values
public void setIntValues(int... values) {
mValueType = int.
mKeyframeSet = CKeyframeSet.ofInt(values);
void init() {
if (mEvaluator == null) {
// We already handle int and float automatically, but not their Object
// equivalents
mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
(mValueType == Float.class) ? sFloatEvaluator :
if (mEvaluator != null) {
// KeyframeSet knows how to evaluate the common types - only give it a custom
// evaluator if one has been set on this class
mKeyframeSet.setEvaluator(mEvaluator);
* 让CKeyframeSet通过关键帧计算属性值
* @param fraction
void calculateValue(float fraction) {
mAnimatedValue = mKeyframeSet.getValue(fraction);
1.5、CValueAnimator.ofInt(00);到底做了什么?
* CPropertyValuesHolder是一个包装类
* 可以看做是,需要动画的属性或者值的对象实例
CPropertyValuesHolder[] mV
* 属性值类型为int的动画
* @param values
public static CValueAnimator ofInt(int... values) {
CValueAnimator anim = new CValueAnimator();
anim.setIntValues(values);
* 根据一系列属性值,生成CPropertyValuesHolder
* CPropertyValuesHolder这个类的意义就是,持有某个属性的一系列值,
* 例如scale(缩放属性),其若干个值为100,200,1000等。
* 也就是说在规定时间内,scale的值会从100增长到1000
* @param values
public void setIntValues(int... values) {
if (values == null || values.length == 0) {
if (mValues == null || mValues.length == 0) {
//属性名称为,说明只是数值改变,和具体属性无关
setValues(new CPropertyValuesHolder[]{CPropertyValuesHolder.ofInt(, values)});
CPropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setIntValues(values);
// New property/values/target should cause re-initialization prior to starting
mInitialized =
public void setValues(CPropertyValuesHolder... values) {
mInitialized =
原理就是根据00,生成了一个CPropertyValuesHolder对象,并且将它保存了起来。
2、调用start(),开始动画!
目前万事俱备,只等调用start()方法开始动画了。
直接来看简化过后的start()方法
public void
if (Looper.myLooper() == null) {//当前线程必须调用了Looper.loop()方法
throw new AndroidRuntimeException(Animators may only be run on Looper threads);
mPlayingState = STOPPED;
sPendingAnimations.get().add(this);
* 初始化动画时间,也就是设置起始运行时间为0
* 并且计算起始属性值值
setCurrentPlayTime(getCurrentPlayTime());
mPlayingState = STOPPED;
mRunning =
//通知监听器,动画开始
if (mListeners != null) {
ArrayList tmpListeners =
(ArrayList) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i & numL ++i) {
tmpListeners.get(i).onAnimationStart(this);
//Handler,通过自己给自己发送消息,实现不断进行动画
AnimationHandler animationHandler = sAnimationHandler.get();
if (animationHandler == null) {
animationHandler = new AnimationHandler();
sAnimationHandler.set(animationHandler);
animationHandler.sendEmptyMessage(ANIMATION_START);
动画过程我们可以这样想:
1、获取当前时间为startTime,即动画起始时间,并且初始化动画状态,例如00,那么setCurrentPlayTime()方法的其中一个工作就是初始化状态为1000
2、通知动画开始监听器,动画开始
3、使用AnimationHandler实现循环,首先给AnimationHandler发送了一条ANIMATION_START信息
显然,主要工作就是在AnimationHandler里面进行的
* 该handler用于处理两个消息
* ANIMATION_START也就是动画开始
* ANIMATION_FRAME也就是运行某一帧
private static class AnimationHandler extends Handler {
public void handleMessage(Message msg) {
boolean callAgain =
//当前运行动画队列
ArrayList animations = sAnimations.get();
switch (msg.what) {
case ANIMATION_START:
//当前等候动画队列
ArrayList pendingAnimations = sPendingAnimations.get();
if (animations.size() & 0) {
callAgain =
while (pendingAnimations.size() & 0) {//如果等候的动画大于0
ArrayList pendingCopy =
(ArrayList) pendingAnimations.clone();
pendingAnimations.clear();
int count = pendingCopy.size();
for (int i = 0; i & ++i) {
CValueAnimator anim = pendingCopy.get(i);
// If the animation has a startDelay, place it on the delayed list
anim.startAnimation();//启动这些动画
case ANIMATION_FRAME:
//当前时间
long currentTime = AnimationUtils.currentAnimationTimeMillis();
//当前已经结束的动画队列
ArrayList endingAnims = sEndingAnims.get();
//正在运行的对话数量
int numAnims = animations.size();
int i = 0;
while (i & numAnims) {
CValueAnimator anim = animations.get(i);
if (anim.animationFrame(currentTime)) {//更新每个运行动画的数值,如果已经结束,加入endingAnims对象
endingAnims.add(anim);
if (animations.size() == numAnims) {
//在动画运行过程中,可能有些动画被取消
endingAnims.remove(anim);
if (endingAnims.size() & 0) {
for (i = 0; i & endingAnims.size(); ++i) {
endingAnims.get(i).endAnimation();
endingAnims.clear();
// If there are still active or delayed animations, call the handler again
// after the frameDelay
//如果还有活动的动画,在默认每帧间隔时间以后,再次调用,更新属性值
if (callAgain && (!animations.isEmpty())) {
sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay -
(AnimationUtils.currentAnimationTimeMillis() - currentTime)));
这个类做了这些工作:
1、ANIMATION_START状态:
其实就是调用了等待队列pendingAnimations中CValueAnimator对象的startAnimation()方法
* 启动动画
private void startAnimation() {
//初始化动画
initAnimation();
//将等候队列中的动画,加入运行对象
sAnimations.get().add(this);
在该方法中,初始化了动画(其实这里调用initAnimation()没有实际作用,因为之前已经初始化过了);
然后就是将动画放入运行队列。
2、ANIMATION_FRAME状态:
我们注意到ANIMATION_START状态以后,并没有使用break,所以会接着执行ANIMATION_FRAME
对每个运行动画,调用其了animationFrame()方法
* 根据当前时间,计算运行百分比,然后调用animateValue更新当前属性值
* @param currentTime
boolean animationFrame(long currentTime) {
boolean done =
if (mPlayingState == STOPPED) {
mPlayingState = RUNNING;
if (mSeekTime & 0) {
mStartTime = currentT
mStartTime = currentTime - mSeekT
// Now that we're playing, reset the seek time
mSeekTime = -1;
switch (mPlayingState) {
case RUNNING:
case SEEKED:
float fraction = mDuration & 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
if (fraction &= 1f) {//百分比大于1,结束动画
fraction = Math.min(fraction, 1.0f);
animateValue(fraction);
这个方法会根据当前时间,判断动画是否已经结束,如果是,返回true,这些动画就会进入sEndingAnims队列,做最后的结束通知工作。
否则,其实就是调用了自己的CPropertyValuesHolder计算当前属性值
* 根据百分比,更新属性值
* @param fraction
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction =
int numValues = mValues.
for (int i = 0; i & numV ++i) {
mValues[i].calculateValue(fraction);//让CPropertyValuesHolder计算属性值
//通知监听器,属性值更新
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i & numL ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
3、新的属性值计算结束
走完两个case,新的属性值就计算出来了,我们通过getAnimatedValue()就可以拿到
* 获取当前属性值
public Object getAnimatedValue() {
if (mValues != null && mValues.length & 0) {
return mValues[0].getAnimatedValue();
// Shouldn' should always have values unless ValueAnimator was set up wrong
那么怎么让动画继续计算下一个属性值呢?
// If there are still active or delayed animations, call the handler again
// after the frameDelay
//如果还有活动的动画,在默认每帧间隔时间以后,再次调用,更新属性值
if (callAgain && (!animations.isEmpty())) {
sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay -
(AnimationUtils.currentAnimationTimeMillis() - currentTime)));
也就是在规定的帧间隔以后,AnimationHandler给自己再次发送一个ANIMATION_FRAME消息,进行下一次属性值的计算。
最后,如上面所说,animationFrame()返回true,才真正结束动画!
(window.slotbydup=window.slotbydup || []).push({
id: '2467140',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467141',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467142',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467143',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467148',
container: s,
size: '1000,90',
display: 'inlay-fix'}

我要回帖

更多关于 nineoldandroids使用 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信