(二十一)Animator 实例 —— 刷鲜花效果

版权声明:本文为博主原创文章,未经博主允许不得转载。

本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

一、效果

这里写图片描述

爱心在底部中心慢慢变大出现,然后按随机的轨迹上升。

二、分析

这边有两种实现思路:

1.自绘控件,绘制的花(可以是 SVG or 很小的图片)按照贝塞尔曲线曲线路径绘制,Path+PathMeasure (小船的案例)http://blog.csdn.net/qq_18983205/article/details/76785251

2.控制View的动画,每一朵鲜花都是一个ImageView,可以通过属性动画控制曲线运动, ObjectAnimator。

如果说,在鲜花移动的过程中,还需要点击监听事件,进行人机交互的话,采用方法1则需要不停的计算鲜花区域来判断是否点击到鲜花。直接采用动画效果则没有这个问题。

三、添加图片

这边以自定义控件来实现。先来实现点击按钮添加爱心,且颜色随机。

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.xiaoyue.animatordemo.LoveLayout
        android:id="@+id/loveLayout"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="start"
        android:text="赞一个" />

</RelativeLayout>

MainActivity

public class MainActivity extends ActionBarActivity {

    private LoveLayout loveLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        loveLayout = (LoveLayout)findViewById(R.id.loveLayout);
    }

    public void start(View v){
        loveLayout.addLoveIcon();
    }

}

LoveLayout

public class LoveLayout extends RelativeLayout {

    //图片的宽高
    private int dWidth;
    private int dHeight;
    //图片资源数组
    private Drawable[] drawables = new Drawable[3];

    //图片布局参数
    private LayoutParams layoutParams;
    //获取随机数
    private Random random;

    public LoveLayout(Context context) {
        this(context, null);
    }

    public LoveLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LoveLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public LoveLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init() {

        //加载图片数据
        drawables[0] = getResources().getDrawable(R.drawable.blue);
        drawables[1] = getResources().getDrawable(R.drawable.red);
        drawables[2] = getResources().getDrawable(R.drawable.yellow);
        //得到图片的原始高度
        dWidth = drawables[0].getIntrinsicWidth();
        dHeight = drawables[0].getIntrinsicHeight();

        //初始化图片布局参数,添加到父容器底部、水平居中位置
        layoutParams = new LayoutParams(dWidth, dHeight);
        layoutParams.addRule(CENTER_HORIZONTAL);
        layoutParams.addRule(ALIGN_PARENT_BOTTOM);

        random = new Random();
    }

    public void addLoveIcon() {
        ImageView imageView = new ImageView(getContext());
        imageView.setImageDrawable(drawables[random.nextInt(3)]);
        //将 imageView 添加到父容器底部、水平居中位置
        imageView.setLayoutParams(layoutParams);
        addView(imageView);
    }
}

爱心是使用三张相同大小,不同颜色的图片,通过随机数来随机获取一个颜色。这些图片也比较简单,对性能有要求的也可以换成 SVG。

效果:
这里写图片描述

四、添加时的动画

在添加爱心的首,爱心是有小变到正常大小,在 X、Y 方向上都进行了缩放,采用 AnimatorSet。

    private AnimatorSet getAnimator(ImageView imageView) {

        ObjectAnimator alpha = ObjectAnimator.ofFloat(imageView, "alpha", 0.3f, 1f);
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(imageView, "scaleX", 0.3f, 1f);
        ObjectAnimator scaleY = ObjectAnimator.ofFloat(imageView, "scaleY", 0.3f, 1f);

        AnimatorSet addAnimator= new AnimatorSet();
        addAnimator.playTogether(alpha, scaleX, scaleY);
        addAnimator.setDuration(500);

        return addAnimator;
    }

在 addLoveIcon()中调用并启动:

       //开始属性动画:平移、透明度渐变、缩放动画
        AnimatorSet set = getAnimator(imageView);
        set.start();

效果:
这里写图片描述

五、添加移动

估值器是通过改变算法从而改变值,插值器是控制计算的速度。所以,这边采用估值器进行实现移动的实现。

移动的轨迹使用贝三阶塞尔曲线,公式为:b(t)=p0*(1-t)(1-t)(1-t)+3*p1*t*(1-t)(1-t)+3*p2*t*t(1-t)+p3*t*t*t

1.自定义估值器

public class BezierEvaluator implements TypeEvaluator<PointF>{
    //贝塞尔曲线的两个折点
    private PointF pointF1;
    private PointF pointF2;
    //避免重复创建造成内存抖动
    PointF point;

    public BezierEvaluator(PointF pointF1, PointF pointF2) {
        this.pointF1 = pointF1;
        this.pointF2 = pointF2;
        point = new PointF();
    }

    @Override
    public PointF evaluate(float t, PointF point0, PointF point3) {
        //t百分比:0~1
        // b(t)=p0*(1-t)*(1-t)*(1-t)+3*p1*t*(1-t)*(1-t)+3*p2*t*t*(1-t)+p3*t*t*t
        point.x = point0.x*(1-t)*(1-t)*(1-t)
                +3*pointF1.x*t*(1-t)*(1-t)
                +3*pointF2.x*t*t*(1-t)
                +point3.x*t*t*t;
        point.y = point0.y*(1-t)*(1-t)*(1-t)
                +3*pointF1.y*t*(1-t)*(1-t)
                +3*pointF2.y*t*t*(1-t)
                +point3.y*t*t*t;
        return point;
    }
}

这边用到的估值器在计算的时候需要用到中间两个折点的坐标,为了引入这两个折点,这边直接新建一个类,而不再是使用匿名类。

2.移动动画

    public ValueAnimator getMoveAnimator(final ImageView imageView){

        //根据贝塞尔公式确定四个点(起始点p0,拐点1p1,拐点2p2,终点p3)
        PointF pointF0 = new PointF((mWidth-dWidth)/2, mHeight-dHeight);
        PointF pointF3 = new PointF(random.nextInt(mWidth), 0);
        PointF pointF1 = getPointF(1);
        PointF pointF2 = getPointF(2);

        ValueAnimator valueAnimator = new ValueAnimator();
        valueAnimator.setDuration(5000);
        //设置起始点和终止点
        valueAnimator.setObjectValues(pointF0, pointF3);

        //设置估值器
        BezierEvaluator bezierEvaluator = new BezierEvaluator(pointF1, pointF2);
        valueAnimator.setEvaluator(bezierEvaluator);

        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                PointF f = (PointF) animation.getAnimatedValue();
                imageView.setX(f.x);
                imageView.setY(f.y);
                //1~0 百分比
                imageView.setAlpha(1-animation.getAnimatedFraction());
            }
        });
        return valueAnimator;
    }

    private PointF getPointF(int i) {
        PointF pointF = new PointF();
        pointF.x = random.nextInt(mWidth);
        //为了好看,尽量保证point2.y>point1.y
        if(i==1){
            pointF.y = random.nextInt(mHeight/2)+mHeight/2;
        }else{
            pointF.y = random.nextInt(mHeight/2);
        }
        return pointF;
    }

这边估值器的使用就是上一篇讲的直接使用,只是这边使用的自定义估值器。在获取两个折点的时候,为了好看的效果,保证第一个折点在下半个屏幕,第二个折点在上半个屏幕。

3.添加到 Animator

在 getAnimator 方法里面获取到移动的动画,并使用 AnimatorSet 的混合执行,让移动动画在添加动画之后执行。

        ValueAnimator moveAnimator = getMoveAnimator(imageView);

        AnimatorSet set = new AnimatorSet();
        //按序列执行
        set.playSequentially(addAnimator, moveAnimator);

4.效果

这里写图片描述

六、添加回收

在上面只是把爱心的透明度变为了0,这时候如果不停点击,爱心会不停的堆积造成,而且在这边爱心是使用图片,会占用较大的内存。

    public void addLoveIcon() {
        final ImageView imageView = new ImageView(getContext());
        imageView.setImageDrawable(drawables[random.nextInt(3)]);
        //将 imageView 添加到父容器底部、水平居中位置
        imageView.setLayoutParams(layoutParams);
        addView(imageView);

        //开始属性动画:平移、透明度渐变、缩放动画
        AnimatorSet set = getAnimator(imageView);
        //监听动画执行完毕,将iv移除或者复用
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                removeView(imageView);
            }
        });
        set.start();
    }

简单点就是添加监听,当动画执行完之后直接移除。如果说频率很高的话,可以考虑参考 ListView 的复用思想,做一个回收池进行复用。ListView 的复用思想:http://blog.csdn.net/qq_18983205/article/details/77995810

七、添加插值器

每一个爱心速度都一样,为了有更好的效果,可以添加一些插值器,让爱心的速度不一样。

在 init 方法里添加四个系统的插值器。

    private void init() {

        interpolators = new Interpolator[4];
        interpolators[0] = new LinearInterpolator();//线性;
        interpolators[1] = new AccelerateInterpolator();//加速;
        interpolators[2] = new DecelerateInterpolator();//减速;
        interpolators[3] = new AccelerateDecelerateInterpolator();//先加速后减速;

        //加载图片数据
        drawables[0] = getResources().getDrawable(R.drawable.blue);
        drawables[1] = getResources().getDrawable(R.drawable.red);
        drawables[2] = getResources().getDrawable(R.drawable.yellow);
        //得到图片的原始高度
        dWidth = drawables[0].getIntrinsicWidth();
        dHeight = drawables[0].getIntrinsicHeight();

        //初始化图片布局参数,添加到父容器底部、水平居中位置
        layoutParams = new LayoutParams(dWidth, dHeight);
        layoutParams.addRule(CENTER_HORIZONTAL);
        layoutParams.addRule(ALIGN_PARENT_BOTTOM);

        random = new Random();
    };

在 getAnimator 方法里面添加设置随机插值器。

        AnimatorSet set = new AnimatorSet();
        //加速因子,使用插值器
        set.setInterpolator(interpolators[random.nextInt(4)]);
        //按序列执行
        set.playSequentially(addAnimator, moveAnimator);

效果:
这里写图片描述

下面是完整的 LoveLayout 代码。

LoveLayout

public class LoveLayout extends RelativeLayout {

    //自定义控件的宽高
    private int mWidth;
    private int mHeight;

    //图片的宽高
    private int dWidth;
    private int dHeight;
    //图片资源数组
    private Drawable[] drawables = new Drawable[3];

    //图片布局参数
    private LayoutParams layoutParams;
    //获取随机数
    private Random random;

    //插值器数组
    private Interpolator[] interpolators ;

    public LoveLayout(Context context) {
        this(context, null);
    }

    public LoveLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LoveLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public LoveLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);
    }

    private void init() {

        interpolators = new Interpolator[4];
        interpolators[0] = new LinearInterpolator();//线性;
        interpolators[1] = new AccelerateInterpolator();//加速;
        interpolators[2] = new DecelerateInterpolator();//减速;
        interpolators[3] = new AccelerateDecelerateInterpolator();//先加速后减速;

        //加载图片数据
        drawables[0] = getResources().getDrawable(R.drawable.blue);
        drawables[1] = getResources().getDrawable(R.drawable.red);
        drawables[2] = getResources().getDrawable(R.drawable.yellow);
        //得到图片的原始高度
        dWidth = drawables[0].getIntrinsicWidth();
        dHeight = drawables[0].getIntrinsicHeight();

        //初始化图片布局参数,添加到父容器底部、水平居中位置
        layoutParams = new LayoutParams(dWidth, dHeight);
        layoutParams.addRule(CENTER_HORIZONTAL);
        layoutParams.addRule(ALIGN_PARENT_BOTTOM);

        random = new Random();
    }

    public void addLoveIcon() {
        final ImageView imageView = new ImageView(getContext());
        imageView.setImageDrawable(drawables[random.nextInt(3)]);
        //将 imageView 添加到父容器底部、水平居中位置
        imageView.setLayoutParams(layoutParams);
        addView(imageView);

        //开始属性动画:平移、透明度渐变、缩放动画
        AnimatorSet set = getAnimator(imageView);
        //监听动画执行完毕,将iv移除或者复用
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                removeView(imageView);
            }
        });
        set.start();
    }

    private AnimatorSet getAnimator(ImageView imageView) {

        ObjectAnimator alpha = ObjectAnimator.ofFloat(imageView, "alpha", 0.3f, 1f);
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(imageView, "scaleX", 0.3f, 1f);
        ObjectAnimator scaleY = ObjectAnimator.ofFloat(imageView, "scaleY", 0.3f, 1f);

        AnimatorSet addAnimator = new AnimatorSet();
        addAnimator.setDuration(500);

        ValueAnimator moveAnimator = getMoveAnimator(imageView);

        addAnimator.playTogether(alpha, scaleX,scaleY);

        AnimatorSet set = new AnimatorSet();
        //加速因子,使用插值器
        set.setInterpolator(interpolators[random.nextInt(4)]);
        //按序列执行
        set.playSequentially(addAnimator, moveAnimator);
        return set;
    }

    public ValueAnimator getMoveAnimator(final ImageView imageView){

        //根据贝塞尔公式确定四个点(起始点p0,拐点1p1,拐点2p2,终点p3)
        PointF pointF0 = new PointF((mWidth-dWidth)/2, mHeight-dHeight);
        PointF pointF3 = new PointF(random.nextInt(mWidth), 0);
        PointF pointF1 = getPointF(1);
        PointF pointF2 = getPointF(2);

        ValueAnimator valueAnimator = new ValueAnimator();
        valueAnimator.setDuration(3000);
        //设置起始点和终止点
        valueAnimator.setObjectValues(pointF0, pointF3);

        //设置估值器
        BezierEvaluator bezierEvaluator = new BezierEvaluator(pointF1, pointF2);
        valueAnimator.setEvaluator(bezierEvaluator);

        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                PointF f = (PointF) animation.getAnimatedValue();
                imageView.setX(f.x);
                imageView.setY(f.y);
                //1~0 百分比
                imageView.setAlpha(1-animation.getAnimatedFraction());
            }
        });
        return valueAnimator;
    }

    private PointF getPointF(int i) {
        PointF pointF = new PointF();
        pointF.x = random.nextInt(mWidth);
        //为了好看,尽量保证point2.y>point1.y
        if(i==1){
            pointF.y = random.nextInt(mHeight/2)+mHeight/2;
        }else{
            pointF.y = random.nextInt(mHeight/2);
        }
        return pointF;
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值