属性动画之纷繁的星星
效果就是你手点哪,就会生成一个星星然后弹射出去
这个效果很酷炫吧,这个是我看了郭神的属性动画后写的,里面将大部分的属性动画都用上了包括Evaluator和Interpolator
不过Interpolator是用的系统的,希望各位大神能自己写,然后还用到个数学公式贝塞尔曲线
整体就是一个RelativeLayout,我为大家剖析下这个动画的实现
如果大家不清楚属性动画的基础请转看郭神文章http://blog.csdn.net/guolin_blog/article/details/43536355
首先请大家看下布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<cn.edu.nuc.animator.StarView
android:id="@+id/star"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="0dp"
android:layout_centerInParent="true">
</cn.edu.nuc.animator.StarView>
</RelativeLayout>
什么都没,只有一个StarView,这个就是今天咱们自定义的布局喽
public class StarView extends RelativeLayout {
private Drawable star1; //第一张图片
private Drawable star2; //第二张图片
private Drawable star3; //第三张图片
private Drawable[] drawables;//一个图片数组用了存储图片
private int dHeight,dWight; //记录图片的宽和高
private int mHeight,mWight; //记录布局的宽和高
private Random random=new Random();//先做个随机数一会用到的地方很多
private PointF curPointF; //点击屏幕的点
public StarView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
}
这就是咱们今天的主角,可以看到它继承自一个RelativeLayout,成员变量已经加上注释
然后就是构造方法,其中有个init()方法进行初始化,看看这个init()方法
private void init() {
drawables=new Drawable[3];
star1=getResources().getDrawable(R.drawable.star1);
star2=getResources().getDrawable(R.drawable.star2);
star3=getResources().getDrawable(R.drawable.star3);
drawables[0]=star1;
drawables[1]=star2;
drawables[2]=star3;
//得到图片的实际宽高
dHeight=star1.getIntrinsicHeight();
dWight=star1.getIntrinsicWidth();
}
这个init()方法是中将各个图片从资源文件获得到,并加入到图片数组中,然后获得到了每张图片的宽和高,因为我这里选的图片宽高
一样,所以按第 一张图片获取即可
然后我要获得布局的宽高,因为OnCreat()方法布局还没显示所以无法测量,
所以调用onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//得到布局的宽高
mHeight=getMeasuredHeight();
mWight=getMeasuredWidth();
}
初始化完成,然后我要进行点击肯定要重写onTouchEvent(MotionEvent event)方法
@Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction()==0){//判断是否是按下屏幕
PointF pointF=new PointF();
pointF.x=event.getX();
pointF.y=event.getY();
if(pointF.y<dHeight){//做个判断,如果屏幕上端边缘就不出现动画,因为后面有一些计算,避免crash
return true;
}
addStar(pointF);
}
return true;
}
这里获得我点击屏幕的位置然后作为参数传给addStar(pointF)这个方法,好的,这个动画的核心代码开始
public void addStar(PointF clickPointF){
curPointF=clickPointF;
final ImageView iv=new ImageView(getContext());//定义并初始化一个ImageView控件
iv.setX(clickPointF.x-dWight/2); //设置ImageView的初始x坐标
iv.setY(clickPointF.y-dHeight/2); //设置ImageView的初始y坐标
iv.setImageDrawable(drawables[random.nextInt(3)]); //随机选取图像数组中的三张图片中的一种作为这个ImageView的显示图片
addView(iv); //将这个ImageView动态添加到这个布局中
Animator set=getAnimator(iv); //获得这个动画
set.setInterpolator(new OvershootInterpolator());//为这个动画设置Interpolator,这个方法主要是更改动画对象的速度变动
//为动画设置一个监听,当一个动画执行完毕后,马上将这个控件回收掉,不然你点多了,其实这个布局就会有
//上千个ImageView
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
removeView(iv);
}
});
set.start();//开始执行动画
}
这个方法首先是给这个布局进行动态添加了ImageView,然后获得一个动画对象,设置一个Interpolator,设置了一个动画的监听
最后开始执行动画,其关键还是如何获得那个动画对象,接着看下getAnimator(ImageView iv)方法
private Animator getAnimator(ImageView iv) {
//1.alpha动画
ObjectAnimator alpha=ObjectAnimator.ofFloat(iv,"alpha",0.3f,1f);
//2.缩放动画
ObjectAnimator scaleX=ObjectAnimator.ofFloat(iv,"scaleX",0.2f,1f);
ObjectAnimator scaleY=ObjectAnimator.ofFloat(iv,"scaleY",0.2f,1f);
AnimatorSet enter=new AnimatorSet();
enter.setDuration(500);//设置动画时间为0.5秒
//3个动画同时执行
enter.playTogether(alpha, scaleX, scaleY);
enter.setTarget(iv);//设置动画目标
//贝赛尔曲线动画(核心,不断修改ImageView的坐标,PointF(X,Y))
ValueAnimator bezierAnimator=getbezierAnimator(iv);
AnimatorSet bezierSet = new AnimatorSet();
bezierSet.playSequentially(enter, bezierAnimator);
bezierSet.setTarget(iv);
return bezierSet;
}
这个方法用ObjectAnimator.ofFloat先对这个ImageView做了一些出现动画的处理,并且将这些动画添加到一个动画集合AnimatorSet里,
并且让他们同时执行,出现动画完成,然后开始一个运动的动画,获得一个运动的贝赛尔曲线动画如何将这个和刚刚的那个动画集合
再放到一个大的集合里面,然后先让出现动画执行,再让运动动画执行,然后将这个动画返回,接下来看下
getbezierAnimator(final ImageView iv)这个运动动画是如何实现的getbezierAnimator(final ImageView iv)这个运动动画是如何实现的
getbezierAnimator(final ImageView iv)这个运动动画是如何实现的
private ValueAnimator getbezierAnimator(final ImageView iv) {
//贝赛尔曲线动画(核心,不断修改ImageView的坐标,PointF(X,Y))
PointF pointF2=getPointF(2);
PointF pointF1=getPointF(1);
PointF pointF0=new PointF(curPointF.x-dWight/2,curPointF.y-dHeight/2);
PointF pointF3=new PointF(random.nextInt(mWight),0);
BezierEvaluator evaluator=new BezierEvaluator(pointF1,pointF2);
ValueAnimator animator=ValueAnimator.ofObject(evaluator,pointF0,pointF3);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF pointF= (PointF) animation.getAnimatedValue();
iv.setX(pointF.x);
iv.setY(pointF.y);
iv.setAlpha(1-animation.getAnimatedFraction());
}
});
animator.setTarget(iv);
animator.setDuration(3000);
return animator;
}
先给大家发下贝赛尔曲线动画的公式
再给大家一张原理图
原理图中可以看到贝赛尔曲线的p0是起点,p3是终点,p1和p2是中间2个点,t是一个[0,1]之间的参数
原理明白了开始写喽,p0当然就是我点击点,不过得做下处理因为图片的点是按左上角算得,所以要让我们点击位置成为中心那就
都减去图片宽高的一半,P3的话飞到上边就行,y设为0,x随机,只要屏幕内就行
p1和p2随机获得,但也不能太随机,p1要在p2的下边,2者y值要在P0和p3的y值之间,这样才好看一些,p1和p2的方法最后贴出
然后为了让动画按这个贝赛尔曲线必须得构造一个BezierEvaluator,然后给这个动画设置一个更新监听,运动过程中透明度也要变化
public class BezierEvaluator implements TypeEvaluator<PointF> {
private PointF pointF1;
private PointF pointF2;
public BezierEvaluator(PointF pointF1, PointF pointF2) {
this.pointF1 = pointF1;
this.pointF2 = pointF2;
}
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
PointF pointF=new PointF();
pointF.x=startValue.x*(1-fraction)*(1-fraction)*(1-fraction)+
3*pointF1.x*fraction*(1-fraction)*(1-fraction)+
3*pointF2.x*fraction*fraction*(1-fraction)+
endValue.x*fraction*fraction*fraction;
pointF.y=startValue.y*(1-fraction)*(1-fraction)*(1-fraction)+
3*pointF1.y*fraction*(1-fraction)*(1-fraction)+
3*pointF2.y*fraction*fraction*(1-fraction)+
endValue.y*fraction*fraction*fraction;
return pointF;
}
}
这个是我写的一个BezierEvaluator,其实就是抄写下公式
最后贴下获取P1和P2点的方法
private PointF getPointF(int i) {
PointF pointF=new PointF();
pointF.x=random.nextInt(mWight);
if(i==2){
pointF.y=random.nextInt((int) ((curPointF.y)/2));
}else {
pointF.y=random.nextInt((int) (curPointF.y/2))+curPointF.y/2;
}
return pointF;
}
搞定!