弹幕

需求:

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();
    }
    
    
}

 

posted @ 2016-10-19 16:28  嘉禾世兴  阅读(935)  评论(0编辑  收藏  举报