Android 仿斗鱼、映客 礼物打赏,包括连击、追加等功能

      公司之前需要对直播功能添加打赏模块,我把自己的方法以及参考的demo分享出来。

      参考demo,在这个demo中让我最受启发的是它使用给View添加tag的方式,来区别view。但它没有礼物的连击,追加等功能,在此我对它做了完善,使之成为一个可直接用到商业直播的demo。

      首先我们需要实现什么功能呢?礼物从左边滑出,做一个连击数字的动画,然后停留2-3秒后,再消失。其中需要注意的点有以下几个:

1.收到礼物的消息后,因为礼物同时只能展示3个左右,为防止消息丢失,我们需要做一个消息队列来缓存这些消息,定时来遍历消息队列。(定时器、集合)。

2.数字的连击动画。

3.当这个礼物view 正处于“连击”中 或者 连击完成但还未消失的这2-3秒内,用户又送了一次礼物,我们需要做一个追加。


      我是使用一个model AnimMessage来承载一个礼物消息的全部信息,包括送礼物的用户名,礼物数量,礼物名,动画活跃时间,是否连击完成等。

      对于问题1.使用list 来作为消息队列,使用计时器来定时清除当前已展示完毕的view,并从队列中取出消息、添加新的view。

private static void startTimer() {
    mGiftClearTimer = new Timer();
    mGiftClearTimer.schedule(
            new TimerTask() {
                @Override
                public void run() {
                    final int count = mAnimViewContainer.getChildCount();
                    // 清除礼物
                    for (int i = 0; i < count; i++) {
                        View view = mAnimViewContainer.getChildAt(i);
                        AnimMessage message = (AnimMessage) view.getTag();
                        long nowTime = System.currentTimeMillis();
                        long upTime = message.getUpdateTime();
                        if ((nowTime - upTime) >= mGiftClearInterval) {
                            removeAnimalView(i);
                            return;
                        }
                    }
                    // 添加礼物
                    if (count < mGiftMaxNumber) {
                        if (mGiftList.size() > 0) {
                            ((Activity) mContext).runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    startGiftAnim(mGiftList.get(0));
                                    mGiftList.remove(0);
                                }
                            });
                        }
                    }
                }
            }, 0, mGiftClearTimerInterval
    );
}

      uptime是指这个view的最后活跃时间,它需要不断的更新,但是更新的频率不得小于定时器的间隔,否则会被当做已展示完毕的view被清除掉。


      对于问题2. 我则是采用的上述demo中的属性动画,做了一些修改,具体看下面代码。


      对于问题3. 我们需要先检查后面的礼物消息在当前的展示中是否已有对应的view存在,如果没有,则直接重建,如果已经存在,则是需要更新AnimMessage中的礼物数量,来达到追加的目的,代码如下:

private static void startGiftAnim(final AnimMessage giftMessage) {
    View giftView = findViewByMessage(giftMessage);
    if (giftView == null) {//该用户不在礼物显示列表 或者又送了一个新的礼物
        giftView = addAnimalView(giftMessage);
        mAnimViewContainer.addView(giftView);/*将礼物的View添加到礼物的ViewGroup中*/
        mAnimViewContainer.invalidate();
    } else {
        //该用户在礼物显示列表  1. 连击动画还未结束,只更新message即可
        final AnimMessage message = (AnimMessage) giftView.getTag();// 原来的礼物view的信息
        message.setGiftNum(message.getGiftNum() + giftMessage.getGiftNum()); // 合并追送的礼物数量
        giftView.setTag(message);
        if (message.isComboAnimationOver()) {
            // 2.连击动画已完成 此时view 未消失,除了1 的操作外,还需重新启动连击动画
            final MagicTextView giftNum = (MagicTextView) giftView.findViewById(R.id.giftNum);
            giftNum.setText("x" + giftNum.getTag());
            ((LPGiftView) giftView).startComboAnim(giftNum);
        }
    }
}
private static View findViewByMessage(AnimMessage message) {
    for (int i = 0; i < mAnimViewContainer.getChildCount(); i++) {
        AnimMessage giftMessage = (AnimMessage) mAnimViewContainer.getChildAt(i).getTag();
        if (giftMessage.getUserName().equals(message.getUserName()) &&
                giftMessage.getGiftName().equals(message.getGiftName())) {
            return mAnimViewContainer.getChildAt(i);
        }
    }
    return null;
}

      通过findViewByMessage 方法来找到当前展示的礼物中是否有对应的view,如果没有则重建,如果有,则分两种情况

(1)正处于连击中,只需要更新礼物上限,即message.setGiftNum 即可。
(2)礼物连击完毕,连击动画已停止,这时候除了更新礼物上限外,还需要重启下连击动画。

    ObjectAnimator anim1 = ObjectAnimator.ofFloat(giftNumView, "scaleX", 1.8f, 1.0f);
    ObjectAnimator anim2 = ObjectAnimator.ofFloat(giftNumView, "scaleY", 1.8f, 1.0f);
    AnimatorSet animSet = new AnimatorSet();
    animSet.setDuration(300);
    animSet.setInterpolator(new OvershootInterpolator());
    animSet.playTogether(anim1, anim2);
    animSet.start();
    animSet.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {}
        }
        @Override
        public void onAnimationEnd(Animator animation) {
            ((AnimMessage) getTag()).setUpdateTime(System.currentTimeMillis());//设置时间标记
            giftNumView.setTag((Integer) giftNumView.getTag() + 1);
            //这里用((GiftMessage)giftView.getTag()) 来实时的获取GiftMessage  便于礼物的追加
            if ((Integer) giftNumView.getTag() <= ((AnimMessage) getTag()).getGiftNum()) {
                ((MagicTextView) giftNumView).setText("x" + giftNumView.getTag());
                startComboAnim(giftNumView);
            } else {
                ((AnimMessage)getTag()).setComboAnimationOver(true);
                return;
            }
        }
 
        @Override
        public void onAnimationCancel(Animator animation) {
 
        }
 
        @Override
        public void onAnimationRepeat(Animator animation) {
 
        }
    });
}

      上面代码可以看到,我们在每次执行完一次连击后,来判断是否达到数目的要求,类似一个while循环,如果执行完毕,则将执行完毕的变量设置为true 即可,否则不断的执行动画。


      最后就是移除问题了,当这个view 已经不活跃,并且被定时器检查到后,将其移除,另外如果队列中已经没有礼物消息的时候,要定时器停止,在添加消息的时候,再将之重建。

    if (index >= mAnimViewContainer.getChildCount()) {
        return;
    }
    final View removeView = mAnimViewContainer.getChildAt(index);
    mGiftLayoutOutAnim.setAnimationListener(new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {
        }
 
        @Override
        public void onAnimationEnd(Animation animation) {
            new Handler().post(new Runnable() {
                @Override
                public void run() {
                    mAnimViewContainer.removeViewAt(index);
                }
            });
 
            if (mGiftList.size() == 0 && mAnimViewContainer.getChildCount() == 0 && mGiftClearTimer != null) {
                mGiftClearTimer.cancel();
                mGiftClearTimer = null;
            }
        }
 
        @Override
        public void onAnimationRepeat(Animation animation) {
        }
    });
    ((Activity) mContext).runOnUiThread(new Runnable() {
        @Override
        public void run() {
            removeView.startAnimation(mGiftLayoutOutAnim);
        }
    });
}

      大致就是这些了,至此礼物全部功能已经完毕了,由于我是封装在一个manager 静态类里面,所以为了防止内存泄漏,最后需要对context释放。
      最后放上地址github

效果展示

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

推荐阅读更多精彩内容

  • 点击礼物按钮? ChatView向左移动、底部BottomView向下移动,礼物选择界面向上移动 选择礼物确认送礼...
    Carden阅读 475评论 0 0
  • 封装一个动画任务到操作对象之中? 所有写在操作对象NSOperation的main方法里面的代码,都是分线程执行。...
    Carden阅读 675评论 0 2
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,068评论 4 62
  • 4月14日 星期六 阴 没想到亲子已经坚持一个月了,昨晚忘带充...
    沂蒙妈妈阅读 190评论 0 0
  • 应孩子班主任的邀请,我将在下周五家长会上做一个教育孩子的分享。 我女儿11岁了,上五年级,平时除了上学,每天下午还...
    幸运的小雯阅读 194评论 0 0