首页 > 代码库 > 直播送花特效

直播送花特效

引言

    这是第一个CSDN的文章,写的不好的地方,请大家多多提提意见哈。今天写的文章是关于动画的,前几天公司要求我写一个观看直播点击送花与主播收到花朵的效果。

技术分享
主播接到花朵的效果
技术分享
观众送花效果

思路

    好了看见了效果之后想必大家都应该知道肯定想到要用Animator(4.0增加的动画类),想必很多人都接触到了,但是刚接触android的兄弟可能就没怎么使用这个。网上有很多文章对这个类的介绍,所以这里就不注重解释了。
    主要的思路如下:
        1.确定起点(主播是随机起点)与终点位置;
        2.为其添加动画特效,从起点运动到终点,然后销毁View;
    是不是很简单,哈哈。好了,开始撸吧!!!

撸码

    我就不拿以前的代码了,我一边敲一边说,首先创建个项目(不多说了哈),然后我们创建一个工具类FlowerAnimation.java。我们就对这个类疯狂撸吧。
固定起点->固定终点特效实现
    private static final String TAG = "FlowerAnimation";
    //上下文
    private Context mContext;
    //生成的View添加在的ViewGroup
    private ViewGroup rootView;
    private ViewGroup.LayoutParams layoutParams;

    //资源文件
    private Drawable[] drawables;
    //插值器
    private Interpolator[] interpolators;

    上面代码的注释很清楚了,大家一看就明白了    ,TGA这是为了测试打印log使用的(AS快捷键-logt+enter)。
    public FlowerAnimation(Context mContext, ViewGroup rootView) {
         this.mContext = mContext;
         this.rootView = rootView;
         init();
    }   



    private void init() {
        drawables = new Drawable[8];
        drawables[0] = mContext.getResources().getDrawable(R.mipmap.flower_01);
        drawables[1] = mContext.getResources().getDrawable(R.mipmap.flower_02);
        drawables[2] = mContext.getResources().getDrawable(R.mipmap.flower_03);
        drawables[3] = mContext.getResources().getDrawable(R.mipmap.flower_04);
        drawables[4] = mContext.getResources().getDrawable(R.mipmap.flower_05);
        drawables[5] = mContext.getResources().getDrawable(R.mipmap.flower_06);
        drawables[6] = mContext.getResources().getDrawable(R.mipmap.flower_07);
        drawables[7] = mContext.getResources().getDrawable(R.mipmap.flower_08);

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

        layoutParams = new ViewGroup.LayoutParams(DensityUtil.dip2px(mContext, 50), DensityUtil.dip2px(mContext, 50));
    }
    上面就是一些初始化的东西了,大家看看就好。

    准备工作做好了,我们写最主要的方法,那就是生成动画了,上代码
/**
    /**
     * 开启动画
     *
     * @param view   执行动画的view
     * @param startP 起点 如果传null 默认从view位置开始
     * @param stopP  终点
     */
    public void startAnim(@NonNull final View view, @Nullable PointF startP, @NonNull PointF stopP) {
        if (startP == null) {
            startP = new PointF(view.getX(), view.getY());
        }
        //透明度变化
        ObjectAnimator animatorAlpha = ObjectAnimator.ofFloat(view, "alpha", 0, 1);
        animatorAlpha.setDuration(200);
        //位移动画
        ObjectAnimator animatorX = ObjectAnimator.ofFloat(view, "translationX", startP.x, stopP.x);
        animatorX.setDuration(1000);
        ObjectAnimator animatorY = ObjectAnimator.ofFloat(view, "translationY", startP.y, stopP.y);
        animatorY.setDuration(1000);
        //生成动画集合
        AnimatorSet set = new AnimatorSet();
        //开启透明度动画然后执行位移动画
        set.play(animatorAlpha).before(animatorX).with(animatorY);
        //加入植入器
        set.setInterpolator(interpolators[rand.nextInt(interpolators.length)]);
        //添加动画监听事件 为了移除view 防止造成oom
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                rootView.removeView(view);
            }
        });
        set.start();
    }

    /**
     * 开启动画
     *
     * @param view  执行动画的view
     * @param stopP 终点
     */
    public void startAnim(@NonNull final View view, @NonNull PointF stopP) {
        startAnim(view, null, stopP);
    }

    /**
     * 添加花朵
     *
     * @param startPoint
     */
    public void addFlower(@NonNull PointF startPoint, @NonNull PointF stopP) {
        ImageView flower = new ImageView(mContext);
        flower.setX(startPoint.x);
        flower.setY(startPoint.y);
        Drawable drawable = drawables[rand.nextInt(drawables.length)];
        flower.setBackground(drawable);
        rootView.addView(flower, layoutParams);
        startAnim(flower, startPoint, stopP);
    }
    好了,我们看到,生成这个动画需要View(这是必然的)终点也是必然,起点就无所谓了(要么你指定 要么是自身,哈哈,已经重载了)每一步的注释都写的很清楚,ObjectAnimator这个类功能很强大(4.0+),很强大,很强大。下面开始写个界面来看看效果啦。界面代码我直接上代码 不讲解了!对了有关于view位置的问题大家直接去看别的文章吧,很多的哈,我这里贴个我认为不错的

http://blog.csdn.net/jason0539/article/details/42743531

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    //终点坐标imageView
    private ImageView endFlowerIv;
    //开启动画按钮 也是起点坐标
    private Button startFlowerBt;
    private FlowerAnimation flowerAnimation;

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

    private void initView() {
        endFlowerIv = (ImageView) findViewById(R.id.main_end_flower_iv);
        startFlowerBt = (Button) findViewById(R.id.main_start_flower_bt);
        startFlowerBt.setOnClickListener(this);
        flowerAnimation = new FlowerAnimation(this, (ViewGroup) findViewById(R.id.activity_main));
    }


    @Override
    public void onClick(View v) {
        flowerAnimation.addFlower(new PointF(v.getX(), v.getY()), new PointF(endFlowerIv.getX(), endFlowerIv.getY()));
    }
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/main_end_flower_iv"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_gravity="center_horizontal"
        android:background="@mipmap/flower_01" />


    <Button
        android:id="@+id/main_start_flower_bt"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_gravity="center_horizontal|bottom"
        android:background="@mipmap/flower_01" />
</FrameLayout>
下面是效果图 好啦 完成了

技术分享

随即起点->固定终点特效实现
这个问题想必大家在上面的基础上就完全可以写出来 直接把代码贴上来与效果
     /**
     * 添加花朵 随即生成起点(rootView范围)
     *
     * @param stopP 终点
     */
    public void addFlowerByScope(@NonNull PointF stopP) {
        float x = rand.nextFloat() * rootView.getWidth();
        float y = rand.nextFloat() * rootView.getHeight();
        addFlower(new PointF(x, y), stopP);
    }

     /**
     * 添加花朵 随即生成起点
     *
     * @param stopP  终点
     * @param scopeP 范围  随即生成的点将会按照此范围随即取值
     */
    public void addFlowerByScope(@NonNull PointF stopP, @NonNull PointF scopeP) {
        float x = rand.nextFloat() * scopeP.x;
        float y = rand.nextFloat() * scopeP.y;
        addFlower(new PointF(x, y), stopP);
    }
界面的点击事件换成对应的方法
@Override
    public void onClick(View v) {
        flowerAnimation.addFlowerByScope(new PointF(endFlowerIv.getX(), endFlowerIv.getY()));
    }

技术分享

到此,你会发现已经完成主播接到花朵的效果(就是随即从各个地方出现花朵飞到花朵出 嘻嘻)以上就是主播界面显示的效果了。代码比较简单,下面实现观众点击送花的效果

实现观众送花效果

    思路呢?其实跟上面差不多,观众送花的效果类似固定点到固定点的效果(类似哈哈),为什么说类似呢?因为从图上可以看到,路径是不同的,很明显发现 观众送花的效果的路径是随即的(乱飘)。这里就引出来了ValueAnimator 这个东西你会发现他是ObjectAnimator的父类(其实当你看到他们名字的时候 就知道啦 哈哈)
    ValueAnimator 顾名思义哈 就是针对数值的动画,他能帮我完成什么呢?
    比如我我想让一个数值从0-10 时间是10s,我们来写写看
    我们新建一个ValueAnimActivity.java来实现观众的界面,顺便在里面谢谢测试demo哈哈
public class ValueAnimActivity extends AppCompatActivity implements View.OnClickListener {
    private TextView countTv;
    private Button startBt;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_value);
        initView();
    }

    private void initView() {
        countTv = (TextView) findViewById(R.id.value_count_tv);
        startBt = (Button) findViewById(R.id.value_start_bt);
        startBt.setOnClickListener(this);
    }


    @Override
    public void onClick(View v) {
        startValueAnim();
    }

    private void startValueAnim() {
        //从0-10 时间10s
        ValueAnimator countAnim = ValueAnimator.ofInt(0, 10)
                .setDuration(10000);
        countAnim.setInterpolator(new LinearInterpolator());
        countAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                countTv.setText(animation.getAnimatedValue().toString());
            }
        });
        countAnim.start();
    }

}
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/value_count_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textSize="30dp"  />

    <Button
        android:id="@+id/value_start_bt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal|bottom"
        android:text="start" />
</FrameLayout>
代码跟布局都很简单 我们简单写了个ValueAnimator的例子 直接看效果

技术分享
哈哈 就是那么叼,那怎么实现我们的效果呢?这里就用到一个方法

public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)
    他能让一个对象进行改变,那怎么改变呢?其实随便是什么?都要跟时间挂钩(重要),为什么跟时间挂钩,这还要解释么?onAnimationUpdate回调的方法你打印log你会发现他会调用N多次,10S回调了496次,可想而知我们对象改变也会跟时间有关系,那么我们看看TypeEvaluator(类型评估者)这是一个接口,我们来看看它要我们实现的方法
    public T evaluate(float fraction, T startValue, T endValue);
    这个方法看到之后,后面2个我肯定知道是什么意思,动画的起始值,那第一个是什么?(英文翻译是分数)咱们说过肯定跟时间有关的,那么这个是不是就是时间呢?看了官方解释之后,这个意思就是当前完成动画的百分比。
    还不懂?那好我们还看看官方有没有默认给我们实现的类,一看,有很多,我们直接拿来一个用看看效果,上代码
private void startValueAnim1() {
        //从0-10 时间10s
        ValueAnimator countAnim = ValueAnimator.ofObject(new IntEvaluator() {
            @Override
            public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
                Log.e(TAG, "evaluate() called with: fraction = [" + fraction + "], startValue = http://www.mamicode.com/[" + startValue + "], endValue = http://www.mamicode.com/[" + endValue + "]");
                return super.evaluate(fraction, startValue, endValue);
            }
        }, 0, 10)
                .setDuration(10000);
        countAnim.setInterpolator(new LinearInterpolator());
        countAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                countTv.setText(animation.getAnimatedValue().toString());
            }
        });
        countAnim.start();
    }

技术分享
哈哈 一目了然的。(时间也很好算出来了吧?fraction = t / duration)
下面重点来了,如果要实现这种效果,那就要一个公式(贝塞尔曲线)这个当初我也是一头雾水啊,先不管直接套用公式就行了(先上一个图片)
技术分享
这个公式只要理解我们给出4个点,他就算出当前的点。这里有4个点,但是我们只有2个点,另外2个点是为了控制曲线的走向,我随即取就可以咯。好了,我们先不管点,先把TypeEvaluator写好

 /**
     * 自定义的估值器
     */
    public static class MyTypeEvaluator implements TypeEvaluator<PointF> {
        private PointF pointF1, pointF2;

        public MyTypeEvaluator(PointF pointF1, PointF pointF2) {
            this.pointF1 = pointF1;
            this.pointF2 = pointF2;
        }

        @Override
        public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
            float timeLeft = 1.0f - fraction;
            PointF pointF = new PointF();//结果
            pointF.x = timeLeft * timeLeft * timeLeft * (startValue.x)
                    + 3 * timeLeft * timeLeft * fraction * (pointF1.x)
                    + 3 * timeLeft * fraction * fraction * (pointF2.x)
                    + fraction * fraction * fraction * (endValue.x);

            pointF.y = timeLeft * timeLeft * timeLeft * (startValue.y)
                    + 3 * timeLeft * timeLeft * fraction * (pointF1.y)
                    + 3 * timeLeft * fraction * fraction * (pointF2.y)
                    + fraction * fraction * fraction * (endValue.y);
            return pointF;
        }
    }
    只是简单的套用公式,就不用多讲了直接复制就好,要不然能看的眼花(撸多了)。
    下面放上取中间控制点的代码
 /**
     * 获取中间控制点
     * 取rootView范围
     *
     * @param i =0为上方控制点 !=0 为下方
     * @return
     */
    public PointF getPointF(int i) {
        return getPointF(i, new PointF(rootView.getWidth(), rootView.getHeight()));
    }

    /**
     * 获取中间控制点
     *
     * @param i      =0为上方控制点 !=0 为下方
     * @param scopeP 范围
     * @return
     */
    public PointF getPointF(int i, @NonNull PointF scopeP) {
        PointF p = new PointF();
        //随即范围[0,scopeP.x]
        p.x = rand.nextFloat() * scopeP.x;
        float height = scopeP.y / 2;
        //随即范围[0,height]
        float y = rand.nextFloat() * height;
        if (i != 0) {
            //随即范围[height,scopeP.y]
            y = y + height;
        }
        p.y = y;
        return p;
    }
注释也简单,我觉得很好弄懂,下面上主要代码
/**
     * 开启贝塞尔曲线动画
     * 根据rootView 在上下方向分别随即取1个点 作为中间控制点
     * 本身的位置作为起点
     *
     * @param startP 起点
     * @param stopP  终点
     */
    public void addFlowerByValueAnim(@NonNull PointF startP, @NonNull PointF stopP) {
        ImageView flower = new ImageView(mContext);
        flower.setX(startP.x);
        flower.setY(startP.y);
        Drawable drawable = drawables[rand.nextInt(drawables.length)];
        flower.setBackground(drawable);
        rootView.addView(flower, layoutParams);
        addFlowerByValueAnim(flower, getPointF(0), getPointF(1), startP, stopP);
    }


    /**
     * 开启贝塞尔曲线动画
     * 根据rootView 在上下方向分别随即取1个点 作为中间控制点
     * 本身的位置作为起点
     *
     * @param view  动画view
     * @param stopP 终点
     */
    public void addFlowerByValueAnim(@NonNull final View view, @NonNull PointF stopP) {
        addFlowerByValueAnim(view, getPointF(0), getPointF(1), startP, stopP);
    }

    /**
     * 开启贝塞尔曲线动画
     *
     * @param view    动画view
     * @param startP1 中间控制点1
     * @param startP2 中间控制点2
     * @param startP  起点
     * @param stopP   终点
     */
    public void addFlowerByValueAnim(@NonNull final View view, @Nullable PointF startP1, @Nullable PointF startP2, @Nullable PointF startP, @NonNull PointF stopP) {
        //透明度变化
        ObjectAnimator animatorAlpha = ObjectAnimator.ofFloat(view, "alpha", 0, 1);
        animatorAlpha.setDuration(200);
        //生成动画集合
        AnimatorSet set = new AnimatorSet();
        //开启透明度动画然后执行位移动画
        set.play(animatorAlpha).before(getValueAnimator(view, startP1, startP2, startP, stopP));
        //加入植入器
        set.setInterpolator(interpolators[rand.nextInt(interpolators.length)]);
        //添加动画监听事件 为了移除view 防止造成oom
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                rootView.removeView(view);
            }
        });
        set.start();
    }

    /**
     * 获取贝塞尔曲线动画
     *
     * @param view    动画view
     * @param startP1 中间控制点1
     * @param startP2 中间控制点2
     * @param startP  起点
     * @param stopP   终点
     */
    private ValueAnimator getValueAnimator(@NonNull final View view, @Nullable PointF startP1, @Nullable PointF startP2, @Nullable PointF startP, @NonNull PointF stopP) {
        ValueAnimator valueAnimator = ValueAnimator.ofObject(new MyTypeEvaluator(startP1, startP2), startP, stopP);
        valueAnimator.setDuration(2000);
        valueAnimator.setInterpolator(interpolators[rand.nextInt(interpolators.length)]);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                PointF pointF = (PointF) animation.getAnimatedValue();
                view.setX(pointF.x);
                view.setY(pointF.y);
            }
        });
        return valueAnimator;
    }
@Override
    public void onClick(View v) {
        flowerAnimation.addFlowerByValueAnim(new PointF(v.getX(), v.getY()), new PointF(endFlowerIv.getX(), endFlowerIv.getY()));
    }
好了下面是最终效果啦

技术分享
到这里差不多可以结束了。想必大家可能会问,如果动画还没执行完就退出了,那就内存泄漏了啊。嗯,说的很对,所以我再来个方法

 /**
     * 销毁方法
     */
    public void onDestroy() {
         //标记已经取消了
        isOnDestory = true;
        //对所有的动画set进行取消
        for (AnimatorSet set : animatorSets) {
            if (set != null && set.isRunning()) {
                set.cancel();
            }
        }
        animatorSets.clear();
        animatorSets = null;
        //对Drawable回调设置null
        for (Drawable drawable : drawables) {
            if (drawable != null) {
                drawable.setCallback(null);
                drawable = null;
            }
        }
        drawables = null;
        //手动调用gc
        System.gc();
    }
@Override
    protected void onDestroy() {
        if (flowerAnimation != null)
            flowerAnimation.onDestroy();
        super.onDestroy();
    }

好了 跑起来没有问题了,大家下期再见!!!
忘了,源码地址还没给

https://github.com/CFlingchen/CSDN1

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    直播送花特效