直播送花特效

转载请标明出处:

//www.greatytc.com/u/88ded7d85d2f
本文出自:【CF凌晨的博客】

引言

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

思路

    好了看见了效果之后想必大家都应该知道肯定想到要用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>
下面是效果图 好啦 完成了
20161102185441636.gif
随即起点->固定终点特效实现
这个问题想必大家在上面的基础上就完全可以写出来 直接把代码贴上来与效果
     /**
     * 添加花朵 随即生成起点(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 = [" + startValue + "], endValue = [" + 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容