属性动画之纷繁的星星

属性动画之纷繁的星星


效果就是你手点哪,就会生成一个星星然后弹射出去
这个效果很酷炫吧,这个是我看了郭神的属性动画后写的,里面将大部分的属性动画都用上了包括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;
    }

搞定!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Android自定义View 星球运动在dribbble闲逛的时候发现的一个有意思的星球运动的动画,刚好最近时间尚可,就简单实现了一下中间运动的部分,又是因为时间的原因,开头位移的部分没有完成. 这是在dribbble中发现的动画 这是我自己实现的效果... 总觉得我这个星球有点胖... 因为胖所以转的慢么这是.速度等细节还有优化的余地设计过程老办法,先分解动画的构成.整个动画可以看做是一个自旋的星球从右上角由小变大的移动到屏幕的中央的.星球的位移及缩放不说(其实是最近有需求,暂时没时间完善),主要完善了星球的旋转及尾部的处理.最底层是背景的星星闪烁,每次在星球一定范围内随机出现,并缩放就好最开始设计尾部效果的时候,是在没列中设计了两端线.再不断的运行及移动.但是实现起来很乱.最后采用了先绘制所有尾部展示的内容,然后在用和背景一样的颜色部分遮盖并移动此部分形成视觉上的效果的方法.(也可以设置PorterDuff模式来展示).设计过程中的效果如下星球的设计,星球的本身使用简单的遮盖和贝塞尔曲线就能完成一个较为满意的星球背景.重点是星球地表的设计以及星球自转下的地表样式的移动.解决的方法是是先绘制三个重复并连续的地表样式,通过移动整个地表样式模拟星球的转动.最后通过PorterDuff来控制展示的部分和星球的位置重合.未开启PorterDuff模式时绘制的样式如下:开启PorterDuff模式后再指定位置展示指定形状的图形如下:最后再移动设置好的星球地貌就可以模拟出星球转动的效果了代码实现背景的星星private fun drawStarts(canvas: Canvas, perIndexInAll: Float) {     //背景的星星在星球附近的一定范围内随机出现     val maxRand = 800     canvas.translate(-maxRand / 2F , -maxRand / 2F)     val Random = Random(perIndexInAll.toInt().toLong())     //绘制背景的星星     for (index in 0..4){         drawStart(canvas ,  Random.nextFloat() * maxRand , Random.nextFloat() * maxRand , perIndex)     }     canvas.translate(maxRand / 2F , maxRand / 2F) } //绘制背景的星星内容 //绘制背景的星星内容 private fun drawStart(canvas: Canvas, x: Float, y: Float, per: Float) {     var per = per     //这个部分是为了让星星实现从小到大后再从大到小的变动     if (per >= 1.0F){         per -= 1F     }     if (per <= 0.5F){         per *= 2     }else{         per = (1 - per) * 2     }     canvas.save()     canvas.translate(x , y)     canvas.scale(per , per)     val paint = Paint()     paint.color = 0xff78D8DF.toInt()     val startLength = 30F     val startOffset = startLength / 3F     //通过路径描绘星星的形状     val path = Path()     path.moveTo(0F , startLength)     path.lineTo(startOffset , startOffset )     path.lineTo(startLength , 0F)     path.lineTo(startOffset  , -startOffset )     path.lineTo(0F , -startLength)     path.lineTo(-startOffset  , -startOffset )     path.lineTo(-startLength , 0F)     path.lineTo(-startOffset  , startOffset )     path.lineTo(0F , startLength)     canvas.drawPath(path , paint)     paint.color = viewBackgroundColor     //通过缩小绘制星星内部形状     canvas.scale(0.3F , 0.3F)     canvas.drawPath(path , paint)     canvas.restore() }星球外部private fun drawGas(canvas: Canvas, index: Float) {     canvas.save()     canvas.rotate(45F)     val gasWidth = 18F     val baseR = baseR * 0.7F     val absBaseR = baseR / 5F     val paint = Paint()     paint.strokeWidth = gasWidth     paint.style = Paint.Style.STROKE     paint.color = 0xff2F3768.toInt()     val paintArc = Paint()     paintArc.color = 0xff2F3768.toInt()     val gasLength = baseR * 2F     canvas.save()     val gsaL = gasWidth / 2F * 3     var maxGasLength = (gasLength   gsaL ) / 2     var index = index     canvas.scale(1F , -1F)     //绘制星球后面的气流情况     //舍不得那么多定义好的变量     //又不想写个参数很多的函数,就这么实现了     canvas.save()     canvas.translate(baseR , baseR * 1.2F)     canvas.translate(0F , absBaseR)     //drawLines函数一个绘制两头带半圆的线段     drawLines(0F, maxGasLength, canvas, paint)     drawWhite( maxGasLength * index, gasWidth , gsaL * 2 , canvas)     drawWhite( maxGasLength * (index - 1 ) * 1.1F, gasWidth , gsaL * 2 , canvas)     drawWhite( maxGasLength * (index   1 ) * 1.1F, gasWidth , gsaL * 2 , canvas)     canvas.restore()     index = index   0.3F     //.....没有写函数就不上重复的代码了     val rectf = RectF(-baseR , -baseR , baseR ,baseR)     canvas.drawArc(rectf , 0F , 180F , false , paint)     canvas.drawLine(baseR ,0F ,  baseR ,  -baseR, paint)     canvas.drawLine(-baseR ,0F ,  -baseR ,  -baseR, paint)     canvas.restore() } //绘制尾部空白部分 private fun drawWhite(offset: Float, gasWidth: Float, gsaL : Float , canvas: Canvas) {     val r = gasWidth / 2F     canvas.save()     canvas.translate( 0F , offset - 2 * gsaL )     val pointPaint = Paint()     pointPaint.strokeWidth = 20F     pointPaint.color = Color.RED     //通过贝塞尔曲线绘制半圆效果     val path = Path()     path.moveTo(-r , gsaL)     path.cubicTo(             - r * C ,  gsaL - r,             r * C ,  gsaL - r,             r , gsaL     )     path.lineTo(r , - gsaL)     path.cubicTo(             r * C ,  - gsaL   r,             -r * C ,  - gsaL   r,             -r , - gsaL     )     path.lineTo(-r , gsaL * 1.5F)     val paint = Paint()     paint.color = viewBackgroundColor     canvas.drawPath(path , paint)     canvas.restore() }星球private fun drawPlanet(canvas: Canvas , index : Float) {     //设置原图层     val srcB = makeSrc(index)     //设置遮罩层     //遮罩层只有一和星球大小一样的圆     val dstB = makeDst(index)     val paint = Paint()     canvas.saveLayer(-baseR, -baseR, baseR , baseR, null, Canvas.ALL_SAVE_FLAG)     //绘制遮罩层     canvas.drawBitmap(dstB,  -baseR / 2F, -baseR / 2F , paint)     //设置遮罩模式为SRC_IN显示原图层中原图层与遮罩层相交部分     paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)     canvas.drawBitmap(srcB, width / -2F, height / -2F , paint)     paint.xfermode = null } //设置源图层 fun makeSrc(index :Float): Bitmap {     val bm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)     val canvas = Canvas(bm)     canvas.translate(width.toFloat() / 2F , height.toFloat() / 2F)     val paint = Paint()     paint.color = 0xff57BEC6.toInt()     paint.style = Paint.Style.FILL     val rectf = RectF(-baseR / 2F, -baseR / 2F, baseR / 2F, baseR / 2F)     canvas.drawArc(rectf , 0F , 360F , true , paint)     canvas.save()     //绘制星球背景     paint.color = 0xff78D7DE.toInt()     var baseR = baseR * 0.9.toFloat()     val rectf2 = RectF(-baseR / 2F, -baseR / 2F, baseR / 2F, baseR / 2F)     canvas.translate(baseR / 6F , baseR / 6F)     canvas.drawArc(rectf2 , 0F , 360F , true , paint)     canvas.restore()     canvas.rotate(-45F)     canvas.save()     val bottomBaseR = baseR / 0.9F / 2     val path = Path()     path.moveTo(-bottomBaseR , 0F)     path.cubicTo(-bottomBaseR , bottomBaseR * 2, bottomBaseR  , bottomBaseR * 2, bottomBaseR , 0F)     path.cubicTo(             bottomBaseR * C,bottomBaseR ,             -bottomBaseR * C,bottomBaseR ,             -bottomBaseR , 0F     )     //绘制星球背景的阴影效果     paint.color = 0xffAAEEF2.toInt()     paint.style = Paint.Style.FILL     canvas.drawPath(path , paint)     //绘制星球的地貌     drawPoints(index , canvas)     canvas.restore()     paint.strokeWidth = 30F     paint.color = 0xff2F3768.toInt()     paint.style = Paint.Style.STROKE     canvas.drawArc(rectf , 0F , 360F , true , paint)     return bm } private fun drawPoints(index: Float, canvas: Canvas) {         val paintB = Paint()         val paintS = Paint()         paintS.style = Paint.Style.FILL         paintS.color = 0xffE7F2FB.toInt()         paintB.style = Paint.Style.FILL         paintB.color = 0xff2F3768.toInt()         val baseRB = baseR / 2F / 3         val baseRS = baseR / 2F / 3 / 3         val rectfB = RectF(-baseRB, -baseRB, baseRB, baseRB)         val rectfS = RectF(-baseRS, -baseRS, baseRS, baseRS)         val pointPaint = Paint()         pointPaint.color = Color.BLACK         pointPaint.strokeWidth = 50F         val coverWidth = baseR         //通过移动坐标原点模拟星球的自转效果         canvas.translate(-coverWidth / 2F , coverWidth * 1.5F)         val index = index         canvas.translate(0F , coverWidth * index )         //重复绘制三次星球的地貌使得星球的自转无缝连接         for (i in 0..2){             canvas.save()             canvas.translate(coverWidth / 3F / 2  , -coverWidth / 3F * 2)             canvas.drawArc(rectfB , 0F , 360F , true , paintB)             canvas.drawArc(rectfS , 0F , 360F , true , paintS)             canvas.restore()             canvas.save()             canvas.translate(coverWidth / 3F *2 , -coverWidth / 3F)             canvas.drawArc(rectfB , 0F , 360F , true , paintB)             canvas.drawArc(rectfS , 0F , 360F , true , paintS)             canvas.restore()             canvas.save()             canvas.translate(coverWidth / 3F *2 , -coverWidth / 8F * 7   -coverWidth / 10F )             canvas.drawArc(rectfS , 0F , 360F , true , paintB)             canvas.restore()             canvas.save()             canvas.translate(coverWidth / 3F *2 , -coverWidth / 8F * 7  - -coverWidth / 10F )             canvas.drawArc(rectfS , 0F , 360F , true , paintB)             canvas.restore()             canvas.translate(0F , -coverWidth)         }     }
Android属性动画是一种用于在Android应用程序中创建动画效果的框架。它允许您对任意对象的属性进行动画处理,而不仅仅是视图对象。使用属性动画,您可以平滑地改变对象的属性,如位置、大小、颜色等,从而创建流畅的动画效果。 属性动画的基本概念是通过改变对象的属性值来实现动画效果,而不是通过改变视图的位置或绘制来实现。这使得属性动画比传统的补间动画更加灵活和强大。 要使用属性动画,您需要创建一个Animator对象,并指定要动画化的目标对象和属性。然后,您可以定义动画的持续时间、插值器和监听器等属性。最后,启动动画并观察目标对象的属性值的平滑变化。 以下是一个简单的示例,演示如何使用属性动画来平滑地改变视图对象的透明度: ```java // 创建一个属性动画对象,指定要改变的属性为视图对象的透明度 ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha",***.setDuration(1000); // 设置插值器,控制动画的变化速率 animator.setInterpolator(new AccelerateDecelerateInterpolator()); // 启动动画 animator.start(); ``` 在上面的示例中,我们创建了一个ObjectAnimator对象,指定要改变的属性为视图对象的透明度。然后,我们设置了动*** 除了ObjectAnimator,Android还提供了一些其他类型的属性动画,如ValueAnimator和AnimatorSet。这些类可用于更复杂的动画效果,如同时播放多个动画或在动画中修改多个属性。 希望这个简介能帮助您理解Android属性动画的基本概念和用法。如果您有更多问题,请随时提问!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值