弹幕
需求:
1.弹幕从右向左
2.弹幕内容包括文字、表情
3.一条弹幕内容有好几种颜色
4.前后弹幕不会重叠,一共三行,新弹幕会智能添加到侯时最短的那行(思路:每一行设置一个list和标识符,当标识符打开时,可以动画,否则往list中增加弹幕内容)
注意:当弹幕内容TextView长度超出屏幕宽度时,会被系统自动截取,建议显示省略号。或者重写一个类继承TextView
import android.widget.LinearLayout;
/**
* Created by Administrator on 2016/10/19.
*/
public class BarrageItem {
public LinearLayout linearLayout;//弹幕消息布局
public int moveSpeed;//移动速度
public int textMeasuredWidth;//字体显示占据的宽度
public int moveLong;//移动总距离
public int time;//完全展示所需时间
public int playLine;//动画所在的行数(共三行)
}
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Handler;
import android.text.Spannable;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.xuehu365.xuehu.R;
import com.xuehu365.xuehu.model.BarrageItem;
import com.xuehu365.xuehu.model.Message;
import com.xuehu365.xuehu.model.MessageType;
import com.xuehu365.xuehu.utils.EmojiHelp;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Administrator on 2016/10/19.
*/
public class BarrageView extends RelativeLayout {
private Context mContext;
private int speed = 80;//每秒20像素
private final int TEXTSIZE = 15;
private static final int LINEHEIGHT = 40;//一行的顶部补白
private boolean lineFlag0 = false;//第一行开始动画标识
private boolean lineFlag1 = false;//第二行开始动画标识
private boolean lineFlag2 = false;//第三行开始动画标识
private List<BarrageItem> list0;//第一行保存的弹幕
private List<BarrageItem> list1;//第二行保存的弹幕
private List<BarrageItem> list2;//第三行保存的弹幕
private Handler mHandler = new Handler();
public BarrageView(Context context) {
this(context, null);
}
public BarrageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BarrageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
list0 = new ArrayList<>();
list1 = new ArrayList<>();
list2 = new ArrayList<>();
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
}
public void generateItem(Message message) {
BarrageItem item = new BarrageItem();
item.linearLayout = (LinearLayout) LayoutInflater.from(mContext).inflate(R.layout.danmu_item, null);
//布局
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
int msgType = message.getMsgType();
//评论或进入直播间
if (msgType == MessageType.textMessage.ordinal() || msgType == MessageType.systemMessage.ordinal()) {
//姓名
TextView nameView = new TextView(mContext);
String userName = message.getUserName();
nameView.setText(userName);
nameView.setTextColor(Color.YELLOW);
setViewStyle(nameView);
//空格
TextView emtpyView = new TextView(mContext);
String empty = " ";
emtpyView.setText(empty);
setViewStyle(emtpyView);
//消息体
TextView msgView = new TextView(mContext);
Spannable msg = EmojiHelp.getSmiledText(mContext, message.getMsg());
msgView.setText(msg);
msgView.setTextColor(Color.WHITE);
setViewStyle(msgView);
item.linearLayout.addView(nameView, params);
item.linearLayout.addView(emtpyView, params);
item.linearLayout.addView(msgView, params);
item.textMeasuredWidth = (int) (getTextWidth(nameView, userName) + getTextWidth(emtpyView, empty) + getTextWidth(msgView, message.getMsg()) + 20);
} else if (msgType == MessageType.giftMessage.ordinal()) {
//解析打赏字符串
String giftMsg = message.getMsg();
String[] giftText = giftMsg.split("<");
for (int i = 0; i < giftText.length; i++) {
String itemStr = giftText[i].toString();
if (null != itemStr && !TextUtils.isEmpty(itemStr)) {
String itemColor = itemStr.substring(0, 7);
String itemContent = itemStr.substring(8);
TextView itemView = new TextView(mContext);
itemView.setText(itemContent);
itemView.setTextColor(Color.parseColor(itemColor));
setViewStyle(itemView);
item.linearLayout.addView(itemView, params);
item.textMeasuredWidth += (int) getTextWidth(itemView, itemContent);
}
}
} else if (msgType == MessageType.flowerMessage.ordinal()) {
//姓名
TextView nameView = new TextView(mContext);
String userName = message.getUserName();
nameView.setText(userName);
nameView.setTextColor(Color.YELLOW);
setViewStyle(nameView);
//空格
TextView emtpyView = new TextView(mContext);
String empty = " ";
emtpyView.setText(empty);
setViewStyle(emtpyView);
//鲜花
TextView flowerView = new TextView(mContext);
Spannable flower = EmojiHelp.getSmiledText(mContext, message.getMsg());
flowerView.setText(flower);
setViewStyle(flowerView);
item.linearLayout.addView(nameView, params);
item.linearLayout.addView(emtpyView, params);
item.linearLayout.addView(flowerView, params);
item.textMeasuredWidth = (int) (getTextWidth(nameView, userName) + getTextWidth(emtpyView, empty) + getTextWidth(flowerView, message.getMsg()));
}
/*
避免重复动画
*/
item.moveLong = getResources().getDisplayMetrics().widthPixels + item.textMeasuredWidth;
item.time = item.textMeasuredWidth / speed * 1000;
item.moveSpeed = speed;
startLoop(item);
}
//开始循环
private void startLoop(BarrageItem item) {
if (lineFlag0 == false) {
lineFlag0 = true;
item.playLine = 0;
//如果已经没有保存的弹幕
if (list0.size() > 0) {
showBarrageItem(list0.get(0));
list0.remove(0);
} else {
showBarrageItem(item);
}
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
lineFlag0 = false;
if (list0.size() > 0) {
startLoop(list0.get(0));
}
}
}, item.time);
} else if (lineFlag1 == false) {
lineFlag1 = true;
item.playLine = 1;
//如果已经没有保存的弹幕
if (list1.size() > 0) {
showBarrageItem(list1.get(0));
list1.remove(0);
} else {
showBarrageItem(item);
}
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
lineFlag1 = false;
if (list1.size() > 0) {
startLoop(list1.get(0));
}
}
}, item.time);
} else if (lineFlag2 == false) {
lineFlag2 = true;
item.playLine = 2;
//如果已经没有保存的弹幕
if (list2.size() > 0) {
showBarrageItem(list2.get(0));
list2.remove(0);
} else {
showBarrageItem(item);
}
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
lineFlag2 = false;
if (list2.size() > 0) {
startLoop(list2.get(0));
}
}
}, item.time);
} else {
int time0 = 0;//第一行未显示总时间
int time1 = 0;//第二行未显示总时间
int time2 = 0;//第三行未显示总时间
//加到侯时最短的list中去
for (int i = 0; i < list0.size(); i++) {
time0 += list0.get(i).time;
}
for (int i = 0; i < list1.size(); i++) {
time1 += list1.get(i).time;
}
for (int i = 0; i < list2.size(); i++) {
time2 += list2.get(i).time;
}
//算出最小时间
int time;
int minTime = (time = (time0 > time1 ? time1 : time0)) > time2 ? time2 : time;
if (minTime == time0) {
list0.add(item);
} else if (minTime == time1) {
list1.add(item);
} else if (minTime == time2) {
list2.add(item);
}
}
}
private void showBarrageItem(final BarrageItem item) {
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
//不同行数,在弹幕中显示的位置不同
if (0 == item.playLine) {
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
} else if (1 == item.playLine) {
params.addRule(RelativeLayout.CENTER_VERTICAL);
} else if (2 == item.playLine) {
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
}
this.addView(item.linearLayout, params);
Animation anim = generateTranslateAnim(item);
anim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
item.linearLayout.clearAnimation();
BarrageView.this.removeView(item.linearLayout);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
item.linearLayout.startAnimation(anim);
}
private TranslateAnimation generateTranslateAnim(BarrageItem item) {
TranslateAnimation anim = new TranslateAnimation(getResources().getDisplayMetrics().widthPixels, -item.textMeasuredWidth, 0, 0);
anim.setDuration(item.moveLong / item.moveSpeed * 1000);//匀速动画
anim.setInterpolator(new AccelerateDecelerateInterpolator());
anim.setFillAfter(true);
return anim;
}
/**
* 计算TextView中字符串的长度
*
* @param text 要计算的字符串
* @return TextView中字符串的长度
*/
public float getTextWidth(TextView textView, String text) {
Rect bounds = new Rect();
TextPaint paint;
paint = textView.getPaint();
paint.getTextBounds(text, 0, text.length(), bounds);
return bounds.width();
}
/*
设置一条弹幕文字只显示一行,超出部分显示省略号
*/
public void setViewStyle(TextView textView) {
textView.setTextSize(TEXTSIZE);
textView.setSingleLine();
textView.setEllipsize(TextUtils.TruncateAt.END);
}
}
danmu_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:orientation="horizontal"></LinearLayout>
import android.content.Context;
import android.net.Uri;
import android.text.Spannable;
import android.text.Spannable.Factory;
import android.text.style.ImageSpan;
import com.xuehu365.xuehu.data.EmojiDatas;
import com.xuehu365.xuehu.model.EmojiEntity;
import com.xuehu365.xuehu.ui.widget.EaseUI;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class EmojiHelp {
public static final String DELETE_KEY = "em_delete_delete_expression";
public static final String ciya = "[龇牙]";
public static final String tiaopi = "[调皮]";
public static final String liuhan = "[流汗]";
public static final String touxiao = "[偷笑]";
public static final String zaijian = "[再见]";
public static final String qiaoda = "[敲打]";
public static final String cahan = "[擦汗]";
public static final String liulei = "[流泪]";
public static final String daku = "[大哭]";
public static final String xu = "[嘘]";
public static final String ku = "[酷]";
public static final String zhuakuang = "[抓狂]";
public static final String weiqu = "[委屈]";
public static final String keai = "[可爱]";
public static final String se = "[色]";
public static final String haixiu = "[害羞]";
public static final String deyi = "[得意]";
public static final String tu = "[吐]";
public static final String weixiao = "[微笑]";
public static final String nu = "[怒]";
public static final String ganga = "[尴尬]";
public static final String jingkong = "[jingkong]";
public static final String lenghan = "[冷汗]";
public static final String baiyan = "[白眼]";
public static final String aoman = "[傲慢]";
public static final String nanguo = "[难过]";
public static final String jingya = "[惊讶]";
public static final String yiwen = "[疑问]";
public static final String kun = "[困]";
public static final String memeda = "[么么哒 ]";
public static final String hanxiao = "[憨笑]";
public static final String shuai = "[衰]";
public static final String piezui = "[撇嘴]";
public static final String yinxian = "[阴险]";
public static final String fendou = "[奋斗]";
public static final String fadai = "[发呆]";
public static final String zuohengheng = "[左哼哼]";
public static final String youhengheng = "[右哼哼]";
public static final String baobao = "[抱抱]";
public static final String huaixiao = "[坏笑]";
public static final String bishi = "[鄙视]";
public static final String yun = "[晕]";
public static final String dabing = "[大兵]";
public static final String kelian = "[可怜]";
public static final String jier = "[饥饿]";
public static final String bizui = "[闭嘴]";
public static final String shuijiao = "[睡觉]";
public static final String zhouma = "[咒骂]";
public static final String zhemo = "[折磨]";
public static final String koubi = "[抠鼻]";
public static final String guzhang = "[鼓掌]";
public static final String choudale = "[糗大了]";
public static final String dahaqian = "[打哈欠]";
public static final String kuaikule = "[快哭了]";
public static final String xia = "[吓]";
public static final String zhutou = "[猪头]";
public static final String meigui = "[玫瑰]";
public static final String bianbian = "[便便]";
public static final String zhadan = "[炸弹]";
public static final String caidao = "[菜刀]";
public static final String aixin = "[爱心]";
public static final String shiai = "[示爱]";
public static final String aiqing = "[爱情]";
public static final String feiwen = "[飞吻]";
public static final String qiang = "[强]";
public static final String ruo = "[弱]";
public static final String woshou = "[握手]";
public static final String shengli = "[胜利]";
public static final String baoquan = "[抱拳]";
public static final String diaoxie = "[凋谢]";
public static final String mifan = "[米饭]";
public static final String dangao = "[蛋糕]";
public static final String xigua = "[西瓜]";
public static final String pijiu = "[啤酒]";
public static final String piaochong = "[瓢虫]";
public static final String gouyin = "[勾引]";
public static final String ok = "[ok]";
public static final String aini = "[爱你]";
public static final String kafei = "[咖啡]";
public static final String yueliang = "[月亮]";
public static final String dao = "[刀]";
public static final String fadou = "[发抖]";
public static final String chajin = "[差劲]";
public static final String quantou = "[拳头]";
public static final String xinsuile = "[心碎了]";
public static final String taiyang = "[太阳]";
public static final String liwu = "[礼物]";
public static final String piqiu = "[皮球]";
public static final String kulou = "[骷髅]";
public static final String huishou = "[挥手]";
public static final String shandian = "[闪电]";
public static final String lanqiu = "[篮球]";
public static final String pingpang = "[乒乓]";
public static final String no = "[no]";
public static final String tiaotiao = "[跳跳]";
public static final String ouhuo = "[怄火]";
public static final String zhuanquan = "[转圈]";
public static final String ketou = "[磕头]";
public static final String huitou = "[回头]";
public static final String tiaosheng = "[跳绳]";
public static final String jidong = "[激动]";
public static final String jiewu = "[街舞]";
public static final String xianwen = "[献吻]";
public static final String zuotaiji = "[左太极]";
public static final String youtaiji = "[右太极]";
public static final String maomi = "[猫咪]";
public static final String hongshuangxi = "[红双喜]";
public static final String bianpao = "[鞭炮]";
public static final String hongdenglong = "[红灯笼]";
public static final String majiang = "[麻将]";
public static final String maikefeng = "[麦克风]";
public static final String lipindai = "[礼品袋]";
public static final String xinfeng = "[信封]";
public static final String xiangqi = "[象棋]";
public static final String caidai = "[彩带]";
public static final String lazhu = "[蜡烛]";
public static final String baojin = "[爆筋]";
public static final String bangbangtang = "[棒棒糖]";
public static final String naiping = "[奶瓶]";
public static final String miantiao = "[面条]";
public static final String xiangjiao = "[香蕉]";
public static final String feiji = "[飞机]";
public static final String qiche = "[汽车]";
public static final String zuochetou = "[左车头]";
public static final String chexiang = "[车厢]";
public static final String youchetou = "[右车头]";
public static final String duoyun = "[多云]";
public static final String xiayu = "[下雨]";
public static final String chaopiao = "[钞票]";
public static final String xiongmao = "[熊猫]";
public static final String dengpao = "[灯泡]";
public static final String fengche = "[风车]";
public static final String naozhong = "[闹钟]";
public static final String yusan = "[雨伞]";
public static final String caiqiu = "[彩球]";
public static final String zuanjie = "[钻戒]";
public static final String shafa = "[沙发]";
public static final String zhijin = "[纸巾]";
public static final String yao = "[药]";
public static final String shouqiang = "[手枪]";
public static final String qingwa = "[青蛙]";
public static final String danmu_xianhua ="[鲜花]";
public static final String danmu_zhangsheng ="[掌声]";
private static final Factory spannableFactory = Factory
.getInstance();
private static final Map<Pattern, Object> emoticons = new HashMap<Pattern, Object>();
static {
EmojiEntity[] emojicons = EmojiDatas.getData();
for(int i = 0; i < emojicons.length; i++){
addPattern(emojicons[i].getEmojiText(), emojicons[i].getIcon());
}
EaseUI.EaseEmojiconInfoProvider emojiconInfoProvider = EaseUI.getInstance().getEmojiconInfoProvider();
if(emojiconInfoProvider != null && emojiconInfoProvider.getTextEmojiconMapping() != null){
for(Entry<String, Object> entry : emojiconInfoProvider.getTextEmojiconMapping().entrySet()){
addPattern(entry.getKey(), entry.getValue());
}
}
}
/**
* 添加文字表情mapping
* @param emojiText emoji文本内容
* @param icon 图片资源id或者本地路径
*/
public static void addPattern(String emojiText, Object icon){
emoticons.put(Pattern.compile(Pattern.quote(emojiText)), icon);
}
/**
* replace existing spannable with smiles
* @param context
* @param spannable
* @return
*/
public static boolean addSmiles(Context context, Spannable spannable) {
boolean hasChanges = false;
for (Entry<Pattern, Object> entry : emoticons.entrySet()) {
Matcher matcher = entry.getKey().matcher(spannable);
while (matcher.find()) {
boolean set = true;
for (ImageSpan span : spannable.getSpans(matcher.start(),
matcher.end(), ImageSpan.class))
if (spannable.getSpanStart(span) >= matcher.start()
&& spannable.getSpanEnd(span) <= matcher.end())
spannable.removeSpan(span);
else {
set = false;
break;
}
if (set) {
hasChanges = true;
Object value = entry.getValue();
if(value instanceof String && !((String) value).startsWith("http")){
File file = new File((String) value);
if(!file.exists() || file.isDirectory()){
return false;
}
spannable.setSpan(new ImageSpan(context, Uri.fromFile(file)),
matcher.start(), matcher.end(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}else{
spannable.setSpan(new ImageSpan(context, (Integer)value),
matcher.start(), matcher.end(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
}
return hasChanges;
}
public static Spannable getSmiledText(Context context, CharSequence text) {
Spannable spannable = spannableFactory.newSpannable(text);
addSmiles(context, spannable);
return spannable;
}
public static boolean containsKey(String key){
boolean b = false;
for (Entry<Pattern, Object> entry : emoticons.entrySet()) {
Matcher matcher = entry.getKey().matcher(key);
if (matcher.find()) {
b = true;
break;
}
}
return b;
}
public static int getSmilesSize(){
return emoticons.size();
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现