大部分代碼在源碼中已刪造虏,并優(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)滑動完的行狮荔。
思路
- 由于RecyclerView可以添加item動畫
- 每一個彈幕是一個對象,初始化時
isLive = true
表示活動狀態(tài) - 當彈幕結束后
isLive = false
表示未活動狀態(tài)(它的值由動畫結束監(jiān)聽賦值) - 當初始化十個彈幕后(默認十行)介粘,循環(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'