Recyclerview實現(xiàn)的彈幕 (舊)

大部分代碼在源碼中已刪造虏,并優(yōu)化到新版中柱徙。
新版簡介可到:http://www.reibang.com/p/6649f5239aef


注意!由于版本更新应闯,下方的代碼大部分已不存在。
項目地址:https://github.com/xujiaji/DMView

大綱挂捻、效果圖

RecyclerView實現(xiàn)彈幕.png

彈幕演示.gif

簡介

最近公司項目需求要求實現(xiàn)彈幕碉纺,正是這次我寫這個彈幕demo的原因。目前已經(jīng)實現(xiàn)彈幕的添加刻撒,對實現(xiàn)部分的簡單封裝骨田。可以通過調用addBarrage(String name, String msg, String pic)傳遞名字声怔、消息态贤、頭像地址添加一個彈幕。彈幕重下至上添加一次(默認十行)醋火,填充完總行數(shù)后抵卫,優(yōu)先填充下方已經(jīng)滑動完的行狮荔。

思路

  1. 由于RecyclerView可以添加item動畫
  2. 每一個彈幕是一個對象,初始化時isLive = true表示活動狀態(tài)
  3. 當彈幕結束后isLive = false表示未活動狀態(tài)(它的值由動畫結束監(jiān)聽賦值)
  4. 當初始化十個彈幕后(默認十行)介粘,循環(huán)檢測isLive是否是false殖氏,如果是那么重置內容,然后更新對應的行姻采。

實現(xiàn)

1. 首先是動畫怎么來

  • 動畫實現(xiàn)拷貝了這個項目的幾個類:https://github.com/wasabeef/recyclerview-animators
  • 主要是里面的動畫父類:BaseItemAnimator雅采。
  • 然后創(chuàng)建了一個BaseItemAnimator的子類OverTotalLengthAnimator,實現(xiàn)從右到左的動畫效果慨亲,添加了動畫結束監(jiān)聽婚瓜,詳細代碼如下所示:
package com.jiaji.dmview.recyclerview_item_anim;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;

public class OverTotalLengthAnimator extends BaseItemAnimator {
    @Override
    protected void animateRemoveImpl(RecyclerView.ViewHolder holder) {
        Log.e("TAG", "animateRemoveImpl...........................");
    }

    @Override
    protected void preAnimateRemoveImpl(RecyclerView.ViewHolder holder) {
        Log.e("TAG", "preAnimateRemoveImpl...........................");
    }
//當添加數(shù)據(jù)后,調用notifyItemInserted會先執(zhí)行這個方法刑棵,將item頭部移動至右側邊緣
    @Override
    protected void preAnimateAddImpl(RecyclerView.ViewHolder holder) {
        Log.e("TAG", "preAnimateAddImpl...........................");
        ViewCompat.setTranslationX(holder.itemView, holder.itemView.getRootView().getWidth());
    }
//當執(zhí)行完preAnimateAddImpl后巴刻,隨后執(zhí)行這個方法調用startAnimation實現(xiàn)從右至左的動畫效果
    @Override
    protected void animateAddImpl(RecyclerView.ViewHolder holder) {
        Log.e("TAG", "animateAddImpl...........................");
        startAnimation(holder);
    }
//當填充完是個item后將不會添加,而是復用之前的彈幕對象蛉签,然后更新`notifyItemChanged`時調用這個方法胡陪。
    @Override
    public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) {
        Log.e("TAG", "animateChange...........................");
//由于動畫結束后隱藏了item,所以初始化要顯示
        newHolder.itemView.setVisibility(View.VISIBLE);
        ViewCompat.setTranslationX(newHolder.itemView, newHolder.itemView.getRootView().getWidth());
        startAnimation(newHolder);
        return true;
    }

//開始動畫碍舍,整個過程默認8秒柠座,當動畫結束后調用over結束監(jiān)聽
    private void startAnimation(final RecyclerView.ViewHolder holder) {
        ViewCompat.animate(holder.itemView)
                .translationX(-holder.itemView.getRootView().getWidth())
                .setDuration(8000)
                .setListener(new ViewPropertyAnimatorListener() {
                    @Override
                    public void onAnimationStart(View view) {
                        Log.e("TAG", "onAnimationStart");
                    }

                    @Override
                    public void onAnimationEnd(View view) {
                        holder.itemView.setVisibility(View.GONE);
                        if (onAnimListener != null) {
                            onAnimListener.over();
                        }
                        Log.e("TAG", "onAnimationEnd");
                    }

                    @Override
                    public void onAnimationCancel(View view) {
                        Log.e("TAG", "onAnimationCancel");
                    }
                })
                .setStartDelay(getAddDelay(holder))
                .start();
    }
    private OnAnimListener onAnimListener;
    public void setOnAnimListener(OnAnimListener l) {
        this.onAnimListener = l;
    }
    public interface OnAnimListener {
        void over();
    }
}

2.填充數(shù)據(jù)

  • 初始化RecyclerView,垂直布局片橡,從下至上添加妈经。
  • 判斷是否能繼續(xù)添加(是否大于10行),如不能則循環(huán)檢測是否有動畫結束的item捧书,有則更新這個item吹泡。沒有則添加到緩存list中,當每次動畫結束后繼續(xù)添加緩存list中的彈幕對象经瓷。
  • 之前實現(xiàn)部分都是實現(xiàn)在MainActivity中的荞胡,后來為了方便以后(我說萬一哪天)要用這個,所以將其又寫在了BarrageUtil里面了嚎。來看看BarrageUtil:
package com.jiaji.dmview.barrage;

import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;

import com.jiaji.dmview.recyclerview_item_anim.OverTotalLengthAnimator;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Administrator on 2016/6/7.
 */
public class BarrageUtil {
    private Context context;//上下文
    private RecyclerView rvBarrage;//展示彈幕的RecyclerView
    private List<BarrageEntity> barrageList;//填充RecyclerView的list集合
    private BarrageAdapter mBarrageAdapter;//彈幕適配器
    private OverTotalLengthAnimator anim;//彈幕動畫
    private List<Integer> indexList;//保存當前出現(xiàn)的彈幕下標
    private List<BarrageEntity> barrageCache;//緩存當前屏幕滿了時泪漂,添加不上的彈幕對象
    private LinearLayoutManager layoutManager;

    public BarrageUtil(Context context, RecyclerView rvBarrage) {
        this.context = context;
        this.rvBarrage = rvBarrage;
        init();
    }

    private void init() {
        barrageList = new ArrayList<>();
        barrageCache = new ArrayList<>();
        indexList = new ArrayList<>();
        layoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true);
        rvBarrage.setLayoutManager(layoutManager);
        anim = new OverTotalLengthAnimator();
        rvBarrage.setItemAnimator(anim);
        mBarrageAdapter = new BarrageAdapter(barrageList);
        rvBarrage.setAdapter(mBarrageAdapter);
//當動畫結束后就會調用
        anim.setOnAnimListener(new OverTotalLengthAnimator.OnAnimListener() {
            @Override
            public void over() {
                int index = indexList.get(0);//獲取結束這個item的下標
                barrageList.get(index).over();//獲取對應下標的對象,調用這個對象的over()方法歪泳,將這個對象的isLive設置為false
                indexList.remove(0);//刪除運動下標集合中當前結束item的下標
                if (!barrageCache.isEmpty()) {//判斷緩存的彈幕list是否有彈幕
                    BarrageEntity b = barrageCache.get(0);
                    addBarrage(b.getPname(), b.getChatStr(), b.getPic());
                    barrageCache.remove(0);
                }
            }
        });
    }

    public void addBarrage(String name, String msg, String pic) {
    //    Log.e("TAG", "visible_item_position = " + layoutManager.findFirstCompletelyVisibleItemPosition());
        boolean isAdd = false;
        if (barrageList.size() >= 10) {//如果大于10就不再添加
            for (int i = 0, len = barrageList.size(); i < len; i++) {
                BarrageEntity barrageEntity = barrageList.get(i);
                if (barrageEntity.isLive()) {
                    continue;
                }

                if (rvBarrage.isComputingLayout()) {//當RecyclerView正在計算時無法notifyItemChanged萝勤,有一定幾率閃退,所以判斷如果正在計算布局呐伞,那么則直接跳出循環(huán)
                    isAdd = false;
                    break;
                }
                barrageEntity.change(name, msg, pic);
                mBarrageAdapter.notifyItemChanged(i);
                indexList.add(i);
                isAdd = true;
                break;
            }
        } else {
            isAdd = true;
            BarrageEntity barrageEntity = new BarrageEntity(name, msg, pic);
            barrageList.add(barrageEntity);
            mBarrageAdapter.notifyItemInserted(barrageList.size() - 1);
            indexList.add(barrageList.size() - 1);
        }

        if (!isAdd) {//如果沒有添加成功就添加到彈幕緩存list中
            barrageCache.add(new BarrageEntity(name, msg, pic));
        }
    }
}

3. 彈幕對象

package com.jiaji.dmview.barrage;

/**
 * Created by Administrator on 2016/6/6.
 */
public class BarrageEntity {
    private String pname;
    private String chatStr;
    private String pic;
    private boolean isLive;

    public BarrageEntity(String pname, String chatStr, String pic) {
        this.pname = pname;
        this.chatStr = chatStr;
        this.pic = pic;
        isLive = true;
    }

    public void change(String pname, String chatStr, String pic) {
        this.pname = pname;
        this.chatStr = chatStr;
        this.pic = pic;
        isLive = true;
    }

    public void over() {
        isLive = false;
    }

    public boolean isLive() {
        return isLive;
    }

    public void setLive(boolean live) {
        isLive = live;
    }

    public String getPname() {
        return pname;
    }

    public void setPname(String pname) {
        this.pname = pname;
    }

    public String getChatStr() {
        return chatStr;
    }

    public void setChatStr(String chatStr) {
        this.chatStr = chatStr;
    }

    public String getPic() {
        return pic;
    }

    public void setPic(String pic) {
        this.pic = pic;
    }
}

4.使用

package com.jiaji.dmview;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import com.jiaji.dmview.barrage.BarrageUtil;

import java.util.Date;

public class MainActivity extends AppCompatActivity {
    private BarrageUtil mBarrageUtil;
    private RecyclerView rvBarrage;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        rvBarrage = (RecyclerView) findViewById(R.id.rvBarrage);
        mBarrageUtil = new BarrageUtil(this, rvBarrage);
    }

    public void onAddClick(View view) {
        mBarrageUtil.addBarrage(new Date().toString(), "聊天消息敌卓。。伶氢。趟径。", "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=150237755,4294706681&fm=116&gp=0.jpg");
    }
}

總結

目前沒有看到有適合的彈幕案例瘪吏,所以寫了這個demo,當時想到的就只有①自定義布局添加彈幕子布局然后添加動畫②就是這個demo蜗巧,因為想到RecyclerView實現(xiàn)這樣的動畫更加容易寫和理解掌眠。希望能幫助大家多一條實現(xiàn)彈幕思路。
網(wǎng)絡圖片加載使用了Glide:compile 'com.github.bumptech.glide:glide:3.7.0'

demo地址

https://github.com/xujiaji/DMView

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末幕屹,一起剝皮案震驚了整個濱河市蓝丙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件熊响,死亡現(xiàn)場離奇詭異,居然都是意外死亡鸥跟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門盔沫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來医咨,“玉大人,你說我怎么就攤上這事迅诬。” “怎么了婿牍?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵侈贷,是天一觀的道長。 經(jīng)常有香客問我等脂,道長俏蛮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任上遥,我火速辦了婚禮搏屑,結果婚禮上,老公的妹妹穿的比我還像新娘粉楚。我一直安慰自己辣恋,他們只是感情好,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布模软。 她就那樣靜靜地躺著伟骨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪燃异。 梳的紋絲不亂的頭發(fā)上携狭,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天,我揣著相機與錄音回俐,去河邊找鬼逛腿。 笑死稀并,一個胖子當著我的面吹牛,可吹牛的內容都是我干的单默。 我是一名探鬼主播碘举,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼雕凹!你這毒婦竟也來了殴俱?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤枚抵,失蹤者是張志新(化名)和其女友劉穎线欲,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體汽摹,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡李丰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了逼泣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趴泌。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拉庶,靈堂內的尸體忽然破棺而出嗜憔,到底是詐尸還是另有隱情,我是刑警寧澤氏仗,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布吉捶,位于F島的核電站,受9級特大地震影響皆尔,放射性物質發(fā)生泄漏呐舔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一慷蠕、第九天 我趴在偏房一處隱蔽的房頂上張望珊拼。 院中可真熱鬧,春花似錦流炕、人聲如沸澎现。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽昔头。三九已至,卻和暖如春影兽,著一層夾襖步出監(jiān)牢的瞬間揭斧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留讹开,地道東北人盅视。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像旦万,于是被迫代替她去往敵國和親闹击。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,302評論 25 707
  • 大觀園里有十二釵成艘,簡書中美麗的女子可以說成千上萬赏半。就我比較熟悉的,就隨手就可以列出很多: 至情至性的青衫濕舊 純凈...
    石竹閱讀 194評論 2 7
  • 一生吃盡二王書淆两,懷素以來君笑如断箫。 狂掃媚流終大器,世人當恨貳臣噓秋冰。 注:王鐸仲义,字覺斯。博古好學剑勾,精于詩文書畫埃撵。草書...
    大氣浩然閱讀 532評論 1 3
  • 今天答辯,總算對自己這些天的學習有了交代虽另。
    李_昀凇閱讀 223評論 0 0
  • 圖·文/大萌 生活中暂刘,我的朋友經(jīng)常說我是一個十足的“文藝青年”,而我常常會笑著和他們說...
    牛友果星球大萌閱讀 1,791評論 25 50