推荐阅读:

《Animation动画详解》

Android 属性动画:这是一篇很详细的 属性动画 总结&攻略

//属性动画
public final class ObjectAnimator extends ValueAnimator {...}

public class ValueAnimator extends Animator {...}

public final class AnimatorSet extends Animator {...}

//补间动画
public class AnimationSet extends Animation {...}

public class TranslateAnimation extends Animation {...}

public class AlphaAnimation extends Animation {...}

public class RotateAnimation extends Animation {...}

public class ScaleAnimation extends Animation {...}

3.0以前,android支持两种动画模式,tween animation,frame animation,在android3.0中又引入了一个新的动画系统:property animation,这三种动画模式在SDK中被称为property animation,view animation,drawable animation。 可通过NineOldAndroids项目在3.0之前的系统中使用Property Animation。

View Animation(Tween Animation):补间动画,给出两个关键帧,通过一些算法将给定属性值在给定的时间内在两个关键帧间渐变。它只是改变了View对象绘制的位置,而没有改变View对象本身的属性。
1、XML中定义,放置在res/anim/目录下

<set xmlns:android="http://schemas.android.com/apk/res/android"  
    android:interpolator="@android:anim/decelerate_interpolator"  
    android:shareInterpolator="true" 
    android:startOffset="500" >  
  <!--
   android:interpolator=""  //设置动画变化速率
   android:shareInterpolator="true" //组合的动画共享动画变化速率
   android:startOffset="500" 动画在500ms后启动
   android:repeatCount="infinite" 可以是整数或者infinite(无限大)
   android:repeatMode="restart" 可以是restart 或者 reverse。reverse:折返,restart:重新开始
   android:fillAfter="true"控件则保持动画结束的状态,
			加这一属性的时候必须加在<set>标签这里才起作用。
  -->
  
    <scale  
        android:duration="2000"  
        android:fromXScale="0.2"  
        android:fromYScale="0.2"  
        android:pivotX="50%"  
        android:pivotY="50%"  
        android:toXScale="1.5"  
        android:toYScale="1.5" />  
  <!-- 尺寸伸缩动画效果 scale
      浮点型值:
            fromXScale 属性为动画起始时 X坐标上的伸缩尺寸   
            toXScale   属性为动画结束时 X坐标上的伸缩尺寸    
            fromYScale 属性为动画起始时Y坐标上的伸缩尺寸   
            toYScale   属性为动画结束时Y坐标上的伸缩尺寸   
            说明:缩放比率
                 以上四种属性值   
                    0.0表示收缩到没有
                    1.0表示正常无伸缩    
                    值小于1.0表示收缩 
                    值大于1.0表示放大
         
            pivotX     属性为动画相对于物件的X坐标的开始位置
            pivotY     属性为动画相对于物件的Y坐标的开始位置
            说明:缩放中心
                    以上两个属性值 从0%-100%中取值
                    50%为物件的X或Y方向坐标上的中点位置
-->
    <rotate  
        android:duration="1000"  
        android:fromDegrees="0"  
        android:repeatCount="1"  
        android:repeatMode="reverse"  
        android:pivotX="50%"
        android:pivotY="50%"   
        android:toDegrees="360" />  
	  <!--rotate 旋转动画效果
       浮点数型值:
            fromDegrees 属性为动画起始时物件的角度   
            toDegrees   属性为动画结束时物件旋转的角度 可以大于360度  
            说明:
                     当角度为负数——表示逆时针旋转
                     当角度为正数——表示顺时针旋转             
                     (负数from——to正数:顺时针旋转)  
                     (负数from——to负数:逆时针旋转)
                     (正数from——to正数:顺时针旋转)
                     (正数from——to负数:逆时针旋转)

			pivotX     属性为动画相对于物件的X坐标的开始位置
            pivotY     属性为动画相对于物件的Y坐标的开始位置
            旋转中心的定义。
            android:toDegrees="+350" //角度为正角
			android:pivotX="50"  //使用绝对位置定位
			android:pivotX="50%" //使用相对于控件本身定位
			android:pivotX="50%p" //相对于控件的父控件定位
			
			Animation.RELATIVE_TO_PARENT,相对于父控件,1f表示整个父控件的宽度或者是高度,0.5f表示父控件的高度或者宽度的一半,XML中数值带p,
            Animation.RELATIVE_TO_SELF,相对于自身控件,前面两个参数是旋转的角度,后面四个参数用来定义旋转的圆心,xml中数值不带p
                 默认为0,0
            说明:以上两个属性值 从0%-100%中取值,带p为父控件的位置
                 50%为物件的X或Y方向坐标上的中点位置      
	-->
  
    <translate  
        android:duration="2000"  
        android:fromXDelta="0"  
        android:fromYDelta="0"  
        android:toXDelta="320"  
        android:toYDelta="0" />  
        <!--translate 位置转移动画效果
        整型值:
            fromXDelta 属性为动画起始时 X坐标上的位置   
            toXDelta   属性为动画结束时 X坐标上的位置
            fromYDelta 属性为动画起始时 Y坐标上的位置
            toYDelta   属性为动画结束时 Y坐标上的位置
			fromYDelta="48" 从起始Y坐标,偏移48个坐标 
			fromYDelta="80%p" 从80%p的位置移动 ,80%p---父组件的80%
			
		// fromXDelta:x轴上的起始点,以像素为单位
        // toXDelta:x轴上的终点
        //float fromXDelta, float toXDelta, float fromYDelta, float toYDelta
        TranslateAnimation ta1 = new TranslateAnimation(0, 100f, 0, 100f);

        // fromXType:起点时,在X轴上变化时的参照类型,如:[Animation.RELATIVE_TO_SELF,0]:
        // 控件本身所在的坐标+0*控件本身的宽度
        // fromXValue: //左(x负数)右(x正数)上(y负数)下(y正数)
        //int fromXType, float fromXValue, int toXType, float toXValue, int fromYType, float fromYValue, int toYType, float toYValue
        TranslateAnimation ta = new TranslateAnimation(
                Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF,
                2.0f, Animation.RELATIVE_TO_SELF, 0,
                Animation.RELATIVE_TO_SELF, -2.0f);
            注意:没有指定fromXType toXType fromYType toYType 时候,默认是以自己为相对参照物。       
        -->
  
    <alpha  
        android:duration="2000"  
        android:fromAlpha="1.0"  
        android:toAlpha="0.1" />  
        <!-- 透明度控制动画效果 alpha
        浮点型值:
            fromAlpha 属性为动画起始时透明度
            toAlpha   属性为动画结束时透明度
            说明:
                0.0表示完全透明
                1.0表示完全不透明
            以上值取0.0-1.0之间的float数据类型的数字
         
        长整型值:
            duration  属性为动画持续时间
            说明:    
                时间以毫秒为单位
		-->
</set>  

xml文件的引用:

Animation animation = AnimationUtils.loadAnimation(this,  
                R.anim.set_demo); 
animation.setRepeatCount(Animation.INFINITE);//循环显示 
imageView.startAnimation(animation); //启动动画

2、Java代码中使用,例如透明度:

  // 设置开始和结束的透明度(0f-1f之间)
        AlphaAnimation aa = new AlphaAnimation(0.1f, 0.5f);

        // 设置动画的持续时间
        aa.setDuration(3000);
        // 设置重复播放次数
        aa.setRepeatCount(2);
        // 设置重复播放模式:reverse:折返,restart:重新开始
        aa.setRepeatMode(Animation.REVERSE);
        // 保持动画结束时的状态
        aa.setFillAfter(true);//动画结束后不动

        // 启动动画
        imgview.startAnimation(aa);

        // 动画监听事件
        aa.setAnimationListener(new AnimationListener() {

            @Override
            public void onAnimationStart(Animation animation) {
                // TODO Auto-generated method stub

            }

            @Override
            public void onAnimationRepeat(Animation animation) {
                // TODO Auto-generated method stub

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                // TODO Auto-generated method stub

            }
        });

Drawable Animation(Frame Animation):帧动画,就像GIF图片,通过一系列Drawable依次显示来模拟动画的效果。
XML文件要放在/res/drawable/目录下定义:

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">
    <item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
    <item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
    <item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>

Java代码中引用:

ImageView rocketImage = (ImageView)findViewById(R.id.rocket_image);
rocketImage.setBackgroundResource(R.drawable.rocket_thrust);

rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
rocketAnimation.start();

特别注意,AnimationDrawable的start()方法不能在Activity的onCreate方法中调运,因为AnimationDrawable还未完全附着到window上,所以最好的调运时机是onWindowFocusChanged()方法中。

Property Animation:属性动画,这个是在Android 3.0中才引进的,以前学WPF时里面的动画机制好像就是这个,它更改的是对象的实际属性,在View Animation(Tween Animation)中,其改变的是View的绘制效果,真正的View的属性保持不变,比如无论你在对话中如何缩放Button的大小,Button的有效点击区域还是没有应用动画时的区域,其位置与大小都不变。而在Property Animation中,改变的是对象的实际属性,如Button的缩放,Button的位置与大小属性值都改变了。而且Property Animation不止可以应用于View,还可以应用于任何对象。Property Animation只是表示一个值在一段时间内的改变,当值改变时要做什么事情完全是你自己决定的。

Property Animator包括ValueAnimator和ObjectAnimator。
ValueAnimator不会对控件做任何操作,我们可以给它设定从哪个值运动到哪个值,通过监听这些值的渐变过程来自己操作控件。

参考:自定义控件三部曲之动画篇(四)——ValueAnimator基本使用
示例:

private void doAnimation(){  
    ValueAnimator animator = ValueAnimator.ofInt(0,400);  
    animator.setDuration(1000);  
  
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
        @Override  
        public void onAnimationUpdate(ValueAnimator animation) {  
            int curValue = (int)animation.getAnimatedValue();  
            tv.layout(curValue,curValue,curValue+tv.getWidth(),curValue+tv.getHeight());  
        }  
    });  
    animator.start();  
}  

ObjectAnimator 中ofFloat与ofObject方法差别:对象多了一个TypeEvaluator 参数

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {...}
//对象、属性名、变化值

public static ObjectAnimator ofObject(Object target, String propertyName,TypeEvaluator evaluator, Object... values) {...}
//对象、属性名、取值方法、变化值

ValueAnimator 中ofFloat与ofObject方法差别:对象多了一个TypeEvaluator 参数

//setTarget设置对象 setEvaluator设置取值方法 setInterpolator设置插值器
public static ValueAnimator ofFloat(float... values) {...}//变化值

public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {...}//取值方法、变化值

ValueAnimator

ValueAnimator只不过是对值进行了一个平滑的动画过渡。而ObjectAnimator(ObjectAnimator extends ValueAnimator )则就不同了,它是可以直接对任意对象的任意属性进行动画操作的,比如说View的alpha属性。与ObjectAnimator不同的就是我们自己设置元素属性的更新,通过addUpdateListener()方法来添加一个动画的监听器,在动画执行的过程中会不断地进行回调,提高灵活性。
例如:计数

    public void tvTimer(final View view) {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 100);//int类型的数值[0-100]
        valueAnimator.addUpdateListener(
                new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                ((TextView) view).setText("$ " +
                        (Integer) animation.getAnimatedValue());//int类型的数值[0-100]
            }
        });
        valueAnimator.setDuration(3000);
        valueAnimator.start();
    }

狭义而言,动画一般就是指某个View组件的某个或者某些属性值在一段时间内不断变化的过程,这个变化过程往往有个起始值、结束值和一系列的中间值,ValueAnimator就是用来反映这个属性值变化过程的重要类。每一个ValueAnimator其实就是一个的TimeInterpolator和一个TypeEvaluator的结合体。TimeInterpolator用来控制在哪里取,而TypeEvaluator用来控制取多少。setInterpolator方法可以不调用,默认是加速减速插值器AccelerateDecelerateInterpolator ,但是如果调用且传入的参数为null的话,那么就会被设置成线性插值器LinearInterpolator (暂时不清楚为什么要这样做)。setEvaluator方法也可以不调用,默认会根据属性值的类型设置一个IntEvaluator或者FloatEvaluator。
ValueAnimator是整个属性动画机制当中最核心的一个类,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。

这里写图片描述

**插值器Interpolator **

@HasNativeInterpolator
public class AccelerateDecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {
//先加速后减速插值器
    public AccelerateDecelerateInterpolator() {
    }
    
    @SuppressWarnings({"UnusedDeclaration"})
    public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
    }
    
    public float getInterpolation(float input) {
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;//input即为时间因子,已持续时间/总持续时间的比值
        //input 值【0-1】余弦函数【π-2π】值【-1,0】总结果【0,1】
//返回值为求值器Evaluator的插值因子fraction
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();
    }
}

public interface Interpolator extends TimeInterpolator {//空接口}

public interface TimeInterpolator {
//时间插值器:属性变化率,加速度
    float getInterpolation(float input);
}

求值器Evaluator

public class FloatEvaluator implements TypeEvaluator<Number> {

//参数分别为插值器的getInterpolation返回值:插值因子,开始值与结束值。
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();//将number转化成float类型
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}

public interface TypeEvaluator<T> {
    public T evaluate(float fraction, T startValue, T endValue);

}

ValueAnimator根据动画已进行的时间跟动画总时间(duration)的比计算出一个时间因子(0~1),然后根据TimeInterpolator计算出另一个因子:插值因子fraction,最后TypeAnimator通过这个因子计算出属性值,即返回值。

比如:动画持续40ms,在进行到10ms时:首先计算出时间因子,即经过的时间百分比:t=10ms/40ms=0.25。经插值计算(inteplator)后的插值因子:大约为0.15,比如用了AccelerateDecelerateInterpolator,计算公式为(input即为时间因子):
(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
最后根据TypeEvaluator计算出在10ms时的属性值,比如TypeEvaluator为FloatEvaluator:0.15*(40-0)=6pixel。

ValueAnimator包含Property Animation动画的所有核心功能,如动画时间,开始、结束属性值,相应时间属性值计算方法等。应用Property Animation有两个步聚:

1、计算属性值
2、根据属性值执行相应的动作,如改变对象的某一属性。

ValuAnimiator只完成了第一步工作,如果要完成第二步,需要实现ValueAnimator.onUpdateListener接口,这个接口只有一个函数onAnimationUpdate(),在这个函数中会传入ValueAnimator对象做为参数,通过这个ValueAnimator对象的getAnimatedValue()函数可以得到当前的属性值如:

ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.addUpdateListener(new AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        Log.i("update", ((Float) animation.getAnimatedValue()).toString());
    }
});
animation.setInterpolator(new CycleInterpolator(3));
animation.start();

ObjectAnimator

相比于ValueAnimator,ObjectAnimator可能才是我们最常接触到的类,因为ValueAnimator只不过是对值进行了一个平滑的动画过渡,但我们实际使用到这种功能的场景好像并不多。而ObjectAnimator则就不同了,它是可以直接对任意对象的任意属性进行动画操作的,比如说View的alpha属性。
**xml文件中使用:放置在res/animator/目录下 **

<set xmlns:android="http://schemas.android.com/apk/res/android"  
    android:ordering="sequentially" >  
  
    <objectAnimator  
        android:duration="2000"  
        android:propertyName="translationX"  
        android:valueFrom="-500"  
        android:valueTo="0"  
        android:valueType="floatType" >  
    </objectAnimator>  
  
    <set android:ordering="together" >  
        <objectAnimator  
            android:duration="3000"  
            android:propertyName="rotation"  
            android:valueFrom="0"  
            android:valueTo="360"  
            android:valueType="floatType" >  
        </objectAnimator>  
  
        <set android:ordering="sequentially" >  
            <objectAnimator  
                android:duration="1500"  
                android:propertyName="alpha"  
                android:valueFrom="1"  
                android:valueTo="0"  
                android:valueType="floatType" >  
            </objectAnimator>  
            <objectAnimator  
                android:duration="1500"  
                android:propertyName="alpha"  
                android:valueFrom="0"  
                android:valueTo="1"  
                android:valueType="floatType" >  
            </objectAnimator>  
        </set>  
    </set>  
  
</set> 

引用XML动画:

Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_file);  
animator.setTarget(view);  
animator.start();  

上面组合动画如果在Java代码中写,如下:

ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);  
ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);  
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);  
AnimatorSet animSet = new AnimatorSet();  
animSet.play(rotate).with(fadeInOut).after(moveIn);  
animSet.setDuration(5000);  
animSet.start();  

组合动画也可以使用PropertyValuesHolder类,但AnimatorSet:不仅能够实现PropertyValuesHolder的效果,而且可以更精确的控制动画的顺序。

private void propertyValuesHolder() {  
      PropertyValuesHolder pv1 = PropertyValuesHolder.ofFloat("translationY", 600f);  
      PropertyValuesHolder pv2 = PropertyValuesHolder.ofFloat("scaleX", 1f, 0, 0.5f);  
      PropertyValuesHolder pv3 = PropertyValuesHolder.ofFloat("scaleY", 1f, 0, 0.5f);  
      ObjectAnimator.ofPropertyValuesHolder(mImage, pv1, pv2, pv3).setDuration(1000).start();  
  }  
ObjectAnimator anim1 = ObjectAnimator.ofFloat(mBlueBall, "scaleX",
				1.0f, 2f);
		ObjectAnimator anim2 = ObjectAnimator.ofFloat(mBlueBall, "scaleY",
				1.0f, 2f);
		ObjectAnimator anim3 = ObjectAnimator.ofFloat(mBlueBall,
				"x",  cx ,  0f);
		ObjectAnimator anim4 = ObjectAnimator.ofFloat(mBlueBall,
				"x", cx);
		
		/**
		 * anim1,anim2,anim3同时执行
		 * anim4接着执行
		 */
		AnimatorSet animSet = new AnimatorSet();
		//animSet.playTogether(anim1, anim2);//两个动画同时执行
		animSet.play(anim1).with(anim2);
		animSet.play(anim2).with(anim3);
		animSet.play(anim4).after(anim3);
		animSet.setDuration(1000);
		animSet.start();

1、要使用ObjectAnimator来构造动画,要操作的对象中,必须存在对应的属性的set、get方法 (定义对象时时要写set、get方法)
2、setter 方法的命名必须以骆驼拼写法命名,即set后每个单词首字母大写,其余字母小写,即类似于setPropertyName所对应的属性为propertyName 。常用属性:backgroundColor(背景色),translationX,scaleX,rotationX(沿x轴旋转,区别于rotation是z轴旋转,即垂直xy轴),alpha等等。
3、监听

anim.addListener(new AnimatorListener() {  
    @Override  
    public void onAnimationStart(Animator animation) {  
    }  
  
    @Override  
    public void onAnimationRepeat(Animator animation) {  
    }  
  
    @Override  
    public void onAnimationEnd(Animator animation) {  
    }  
  
    @Override  
    public void onAnimationCancel(Animator animation) {  
    }  
});  

如果想选择性的实现,可以使用以下监听AnimatorListenerAdapter

anim.addListener(new AnimatorListenerAdapter() {  

});  

4、实现组合动画功能主要需要借助AnimatorSet这个类,这个类提供了一个play()方法,如果我们向这个方法中传入一个Animator对象(ValueAnimator或ObjectAnimator)将会返回一个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:

after(Animator anim)   
//将现有动画插入到传入的动画之后执行
after(long delay)   
//将现有动画延迟指定毫秒后执行
before(Animator anim)   
//将现有动画插入到传入的动画之前执行
with(Animator anim)   
//将现有动画和传入的动画同时执行

5、执行顺序:

1、playSequentially依次执行,playTogether同时执行。
2、animatorSet.play(anim1).before(anim2)
animatorSet.play(anim2).after(anim1)是完全等价的

ViewPropertyAnimator的用法

在Android 3.1系统当中补充了ViewPropertyAnimator(即在SDK11的时候,给View添加了animate方法,这个方法的返回值是一个ViewPropertyAnimator对象,也就是说拿到这个对象之后我们就可以调用它的各种方法来实现动画效果了),更加方便的实现动画效果。

textview.animate().x(500).y(500).setDuration(5000)  
        .setInterpolator(new BounceInterpolator()); 

自定义Interpolator:在哪里取值,何时取值

属性动画默认的Interpolator是先加速后减速(余弦函数)的一种方式,系统也提供了一些,当然也可以自定义:新建一个类,让它实现TimeInterpolator接口,重写getInterpolation方法(计算何时取值)。


public class DecelerateAccelerateInterpolator implements TimeInterpolator{  //先减速后加速,正弦函数
  
    @Override  
    public float getInterpolation(float input) { //input[0,1] 
        float result;  
        if (input <= 0.5) {  
            result = (float) (Math.sin(Math.PI * input)) / 2;  
        } else {  
            result = (float) (2 - Math.sin(Math.PI * input)) / 2;  
        }  
        return result;  
    }  
  
}  

自定义TypeEvaluator:取值多少,取值方法

ofInt、ofFloat、ofObject最后一组参数的类型分别为int、float、Object。
创建一个类,实现TypeEvaluator接口,重写evaluate方法。

public class PointEvaluator implements TypeEvaluator{  
  
    @Override  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        Point startPoint = (Point) startValue;  
        Point endPoint = (Point) endValue;  
        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());  
        float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());  
        Point point = new Point(x, y);  
        return point;  
    }  
  
}  

将Point1通过动画平滑过度到Point2,就可以这样写:

Point point1 = new Point(0, 0);  
Point point2 = new Point(300, 300);  
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1, point2);  
anim.setDuration(5000);  
anim.start();  

参考
Android动画学习笔记-Android Animation

郭霖
Android属性动画完全解析(下),Interpolator和ViewPropertyAnimator的用法
Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法
Android属性动画完全解析(上),初识属性动画的基本用法

当数学遇上动画:讲述 ValueAnimator、TypeEvaluator 和 TimeInterpolator 之间的恩恩怨怨

android Animation 效果控制(一)

鸿洋:
Android 属性动画(Property Animation) 完全解析 (上)
Android 属性动画(Property Animation) 完全解析 (下)

Android 群英传 Android艺术探索

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐