大部分代码在源码中已删,并优化到新版中。
新版简介可到:http://www.jianshu.com/p/6649f5239aef
注意!由于版本更新,下方的代码大部分已不存在。
项目地址:https://github.com/xujiaji/DMView
大纲、效果图
简介
最近公司项目需求要求实现弹幕,正是这次我写这个弹幕demo的原因。目前已经实现弹幕的添加,对实现部分的简单封装。可以通过调用addBarrage(String name, String msg, String pic)
传递名字、消息、头像地址添加一个弹幕。弹幕重下至上添加一次(默认十行),填充完总行数后,优先填充下方已经滑动完的行。
思路
- 由于RecyclerView可以添加item动画
- 每一个弹幕是一个对象,初始化时
isLive = true
表示活动状态 - 当弹幕结束后
isLive = false
表示未活动状态(它的值由动画结束监听赋值) - 当初始化十个弹幕后(默认十行),循环检测
isLive
是否是false
,如果是那么重置内容,然后更新对应的行。
实现
1. 首先是动画怎么来
- 动画实现拷贝了这个项目的几个类:https://github.com/wasabeef/recyclerview-animators
- 主要是里面的动画父类:
BaseItemAnimator
。 - 然后创建了一个
BaseItemAnimator
的子类OverTotalLengthAnimator
,实现从右到左的动画效果,添加了动画结束监听,详细代码如下所示:
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...........................");
}
//当添加数据后,调用notifyItemInserted会先执行这个方法,将item头部移动至右侧边缘
@Override
protected void preAnimateAddImpl(RecyclerView.ViewHolder holder) {
Log.e("TAG", "preAnimateAddImpl...........................");
ViewCompat.setTranslationX(holder.itemView, holder.itemView.getRootView().getWidth());
}
//当执行完preAnimateAddImpl后,随后执行这个方法调用startAnimation实现从右至左的动画效果
@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结束监听
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.填充数据
- 初始化RecyclerView,垂直布局,从下至上添加。
- 判断是否能继续添加(是否大于10行),如不能则循环检测是否有动画结束的item,有则更新这个item。没有则添加到缓存list中,当每次动画结束后继续添加缓存list中的弹幕对象。
- 之前实现部分都是实现在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;//保存当前出现的弹幕下标
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,有一定几率闪退,所以判断如果正在计算布局,那么则直接跳出循环
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实现这样的动画更加容易写和理解。希望能帮助大家多一条实现弹幕思路。
网络图片加载使用了Glide:compile 'com.github.bumptech.glide:glide:3.7.0'