使用三阶贝塞尔曲线实现直播中点赞效果

前言

完整代码,请查看我的github:https://github.com/shuaijia/LiveLike,喜欢的话就给点个赞喽_

视频直播想必大家都不谋生,从2015年左右开始,视频直播开始大量普及,市面上的大中型APP基本上都有直播功能,比如专做直播的斗鱼、花椒等。大家都可能看过别人直播甚至参与过直播,那么对精彩的内容总忍不住点赞、送礼物!

那作为开发的我们,总是以技术的角度看待世界,看到酷炫的点赞效果,当然也免不了自己实现一下子。

先看效果:


这里写图片描述

根据效果先分析一波:

这里写图片描述

根据效果,确定解决思路和关键技术:

这里写图片描述
  • 自定义View当然少不了,这是基础
  • 多种爱心随机出现、路径也都不同,所以随机数也是必要的
  • 每个爱心的运动速度、变化快慢是不同的,所以用到了插值器
  • 爱心的运动轨迹是平滑的曲线,而且曲线都不一样,所以我们想到了使用贝塞尔函数
  • 应用贝塞尔函数计算运动中点的位置,就需要使用估值器来实现平滑的动画效果

这些很重要!

有了实现思路,那么接下来我们根据分析的它的特点,一步步得来实现:


一、创建基础View,爱心出现在底部并居中

这样使用RelativeLayout最为合适,所以自定义View需继承RelativeLayout:

public class FavorLayout extends RelativeLayout {

    private static final String TAG = "FavorLayout";

    // 实现随机效果
    private Random random = new Random();

    // 爱心高度
    private int iHeight = 120;
    // 爱心宽度
    private int iWidth = 120;
    // FavorLayout高度
    private int mHeight;
    // FavorLayout宽度
    private int mWidth;

    // 来控制子view的位置
    private LayoutParams lp;
   
}

当然了,构造也少不了

public FavorLayout(Context context) {
    super(context);
    init();
}

public FavorLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

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

其中init()方法进行一些初始化,接下来的过程中我们会慢慢讲解和一步步完善init方法。

首先在init方法中设置子View的LayoutParams,使其能够实现底部居中。

//底部 并且 水平居中
lp = new LayoutParams(iWidth, iHeight);
lp.addRule(CENTER_HORIZONTAL, TRUE); //这里的TRUE 要注意 不是true
lp.addRule(ALIGN_PARENT_BOTTOM, TRUE);

注意:
控件的宽度高度应在onMeasure方法中获取

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    // 获取控件宽高(应在onMeasure方法中获取)
    mWidth = getMeasuredWidth();
    mHeight = getMeasuredHeight();
}

二、爱心类型实现随机

在自定义的View中创建 爱心 Drawable对象和数组

private Drawable aLove;
private Drawable bLove;
private Drawable cLove;
private Drawable dLove;
private Drawable eLove;

private Drawable[] loves;

在init方法中,将爱心创建并存入数组

//初始化显示的图片
loves = new Drawable[5];
aLove = getResources().getDrawable(R.mipmap.love_a);
bLove = getResources().getDrawable(R.mipmap.love_b);
cLove = getResources().getDrawable(R.mipmap.love_c);
dLove = getResources().getDrawable(R.mipmap.love_d);
eLove = getResources().getDrawable(R.mipmap.love_e);

//赋值给loves
loves[0] = aLove;
loves[1] = bLove;
loves[2] = cLove;
loves[3] = dLove;
loves[4] = eLove;

默认提供了五种不同颜色的爱心,当然,爱心数组可有开发者由外部设置,进行拓展。


三、爱心进入时候有一个缩放并渐变的动画

先看效果:


这里写图片描述

说到Android动画,我们以前常用Animation,它通常情况下能满足我们的需求,但是它的功能比较弱,并不是很好用。好在3.0后,强大的属性动画的出现,让动画在Android中实现起来变得非常容易。如果你还不知道属性动画怎么使用,赶紧去了解一下吧!

上代码

/**
 * 设置初始动画
 * 渐变 并且横纵向放大
 *
 * @param target
 * @return
 */
private AnimatorSet getEnterAnimtor(final View target) {

    ObjectAnimator alpha = ObjectAnimator.ofFloat(target, View.ALPHA, 0.2f, 1f);
    ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, View.SCALE_X, 0.2f, 1f);
    ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, View.SCALE_Y, 0.2f, 1f);
    AnimatorSet enter = new AnimatorSet();
    enter.setDuration(500);
    enter.setInterpolator(new LinearInterpolator());
    enter.playTogether(alpha, scaleX, scaleY);
    enter.setTarget(target);
    return enter;
}

给传进来的target(就是爱心的ImageView)设置属性动画集,渐变的同时横纵向放大。

对外提供点赞的方法(其实是创建爱心ImageView并添加)

    /**
     * 点赞
     * 对外暴露的方法
     */
    public void addFavor() {
        ImageView imageView = new ImageView(getContext());
        // 随机选一个
        imageView.setImageDrawable(loves[random.nextInt(loves.length)]);
        // 设置底部 水平居中
        imageView.setLayoutParams(lp);

        addView(imageView);
        Log.e(TAG, "addFavor: " + "add后子view数:" + getChildCount());

        Animator set = getAnimator(imageView);
        set.addListener(new AnimEndListener(imageView));
        set.start();
    }

点赞其实就是:在爱心数组中随机抽取一个创建ImageView,添加给付控件并设置渐变和放大动画。

那么这样我们在按钮的点击事件中调用addFavor方法就可以实现如上图的爱心效果了。


四、使用贝塞尔函数实现曲线运动轨迹

我们怎么让爱心按照曲线移动?而且还有随机呢?

接下来就是本文的主角贝塞尔曲线登场的时刻啦,这也是我实现这个效果学到的最重要的知识。不了解贝塞尔曲线的可以阅读我写的另一篇文章开发中的动效设计与实现 —— 贝塞尔曲线动画的插值法

简单来说:就是给定一个起点,一个终点,一个及一个以上的控制点,计算出一个曲线.

简单了解贝塞尔曲线后,发现 三次方贝塞尔曲线 符合我们的要求。

公式:


这里写图片描述

公式中需要四个P、P0是我们的起点,P3是终点,P1、P2是曲线的两个控制点。而t是一个因子,取值范围是0-1,熟悉动画的同学应该就明白,0-1,对动画的作用有多么重大。

因为需要自己实现贝塞尔,所以想到了属性动画中的TypeEvaluator,它就是我们需要的。

上代码:

/**
 * Description: 动画估值器,以实现平滑动画
 * Created by jia on 2017/10/13.
 * 人之所以能,是相信能
 */
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 time, PointF startValue, PointF endValue) {
        float timeLeft = 1.0f - time;

        //结果
        PointF point = new PointF();

        PointF point0 = (PointF)startValue;//起点
        PointF point3 = (PointF)endValue;//终点

        // 贝塞尔公式
        point.x = timeLeft * timeLeft * timeLeft * (point0.x)
                + 3 * timeLeft * timeLeft * time * (pointF1.x)
                + 3 * timeLeft * time * time * (pointF2.x)
                + time * time * time * (point3.x);

        point.y = timeLeft * timeLeft * timeLeft * (point0.y)
                + 3 * timeLeft * timeLeft * time * (pointF1.y)
                + 3 * timeLeft * time * time * (pointF2.y)
                + time * time * time * (point3.y);

        return point;
    }
}

先认识一下两个类:

  • TypeEvaluator:在获取动画对象时只需要传入起始和结束值系统就会自动完成值的平滑过渡,这个平滑过渡的完成就是靠TypeEvaluator这个类
  • PointF:点类,与Point一样,区别是其x和y值是float类型

由于我们view的移动需要控制x y 所以就传入PointF 作为参数。

核心就是在动画变化过程中,实时根据贝塞尔三阶方程计算点的位置并返回。

到这一步,只要我们传入两个PonitF就能得到一个贝塞尔曲线了。

接下来我们在FavorLayout中定义获取一个贝塞尔动画的方法:

    /**
     * 获取一条路径的两个控制点
     * @param scale
     */
    private PointF getPointF(int scale) {

        PointF pointF = new PointF();
        //减去100 是为了控制 x轴活动范围
        pointF.x = random.nextInt((mWidth - 100));
        //再Y轴上 为了确保第二个控制点 在第一个点之上,我把Y分成了上下两半
        pointF.y = random.nextInt((mHeight - 100)) / scale;
        return pointF;
    }

根据贝塞尔曲线方程可知:两个控制点才是真正控制曲线路径的关键!为了使爱心的运动轨迹不同,所以我们随机生成两个控制点,就可以使得曲线轨迹随机

    /**
     * 获取贝塞尔曲线动画
     * @param target
     * @return
     */
    private ValueAnimator getBezierValueAnimator(View target) {

        //初始化一个BezierEvaluator
        BezierEvaluator evaluator = new BezierEvaluator(getPointF(2), getPointF(1));

        // 起点固定,终点随机
        ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF((mWidth - iWidth) / 2, mHeight - iHeight), new PointF(random.nextInt(getWidth()), 0));
        animator.addUpdateListener(new BezierListener(target));
        animator.setTarget(target);
        animator.setDuration(3000);
        return animator;
    }

可能你已经发现,我给曲线动画设置了一个监听BezierListener

/**
 * Description: 动画监听,这里控制位置,真正实现动画
 * Created by jia on 2017/10/13.
 * 人之所以能,是相信能
 */
public class BezierListener implements ValueAnimator.AnimatorUpdateListener {

    private View target;

    public BezierListener(View target) {
        this.target = target;
    }
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        //这里获取到贝塞尔曲线计算出来的的x y值 赋值给view 这样就能让爱心随着曲线走啦
        PointF pointF = (PointF) animation.getAnimatedValue();
        target.setX(pointF.x);
        target.setY(pointF.y);
        // 这里偷个懒,顺便做一个alpha动画,这样alpha渐变也完成啦
        target.setAlpha(1-animation.getAnimatedFraction());
    }
}

只有在回调里使用了计算的值,才能真正做到曲线运动,否则没有效果哦。

我们在位置更新时给爱心的ImageView设置x、y值,使其按计算的贝塞尔路径运动起来。

并且同时设置了逐渐变淡动画,也就是在运动过程中逐渐消失的效果。

修改一下addFavor方法:将动画更换为 贝塞尔动画

    public void addFavor() {

        ImageView imageView = new ImageView(getContext());
        //随机选一个
        imageView.setImageDrawable(drawables[random.nextInt(3)]);
        imageView.setLayoutParams(lp);
        addView(imageView);
        Log.v(TAG, "add后子view数:"+getChildCount());
        getBezierValueAnimator(imageView).start();
    }

看下效果:

这里写图片描述

五、收尾,效果合成

1、实现变速

// 为了实现 变速效果 挑选了几种插补器
private Interpolator line = new LinearInterpolator();//线性
private Interpolator acc = new AccelerateInterpolator();//加速
private Interpolator dce = new DecelerateInterpolator();//减速
private Interpolator accdec = new AccelerateDecelerateInterpolator();//先加速后减速

// 在init中初始化
private Interpolator[] interpolators;

在init方法中:

// 初始化插值器
interpolators = new Interpolator[4];
interpolators[0] = line;
interpolators[1] = acc;
interpolators[2] = dce;
interpolators[3] = accdec;

随机选用插值器,使得爱心运动有变化。

2、动画合并

    /**
     * 设置动画
     *
     * @param target
     * @return
     */
    private Animator getAnimator(View target) {
        AnimatorSet set = getEnterAnimtor(target);

        ValueAnimator bezierValueAnimator = getBezierValueAnimator(target);

        AnimatorSet finalSet = new AnimatorSet();
        finalSet.playSequentially(set);
        finalSet.playSequentially(set, bezierValueAnimator);
        finalSet.setInterpolator(interpolators[random.nextInt(4)]);//实现随机变速
        finalSet.setTarget(target);
        return finalSet;
    }

3、修改点赞方法

    /**
     * 点赞
     * 对外暴露的方法
     */
    public void addFavor() {
        ImageView imageView = new ImageView(getContext());
        // 随机选一个
        imageView.setImageDrawable(loves[random.nextInt(loves.length)]);
        // 设置底部 水平居中
        imageView.setLayoutParams(lp);

        addView(imageView);
        Log.e(TAG, "addFavor: " + "add后子view数:" + getChildCount());

        Animator set = getAnimator(imageView);
        set.addListener(new AnimEndListener(imageView));
        set.start();
    }

聪明的伙伴可能又看出来了,我给动画集设置了结束监听,又是为什么呢?

4、设置消失监听

private class AnimEndListener extends AnimatorListenerAdapter {
    private View target;

    public AnimEndListener(View target) {
        this.target = target;
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        //因为不停的add 导致子view数量只增不减,所以在view动画结束后remove掉
        removeView((target));
        Log.v(TAG, "removeView后子view数:" + getChildCount());
    }
}

我们之前代码其实已经实现点赞效果,但每次点击都在创建新的爱心的ImageView并且添加到父布局中,所以增加了一个监听,目的是为了在动画结束后,把爱心移除,不然,子view只增不减!


六、总结

总结没想好说什么,由于时间仓促,不免有bug或不足的地方,大家发现可以告诉我,有好的建议也可以告诉我,我们一起进步哦!如果您喜欢我的文章,可以去https://github.com/shuaijia/LiveLike点个赞哦!_

邮箱:819418850@qq.com

更多精彩内容,请关注我的微信公众号——Android机动车

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,321评论 6 30
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 5,001评论 5 13
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,615评论 4 59
  • 早就应该学习网络了,一直拖延,读了《自品牌》这本书我决定要让自己成为即用型专家。没有开始就用远没有进步。阅读就是为...
    自在心灵空间阅读 1,753评论 4 5