Android自制精彩弹幕效果
程序员文章站
2023-12-03 11:44:10
好久没有写过文章,最近发现直播特别的火,很多app都集成了直播的功能,发现有些直播是带有弹幕的,效果还不错,今天心血来潮,特地写了篇制作弹幕的文章.
今天要实现的效果如下...
好久没有写过文章,最近发现直播特别的火,很多app都集成了直播的功能,发现有些直播是带有弹幕的,效果还不错,今天心血来潮,特地写了篇制作弹幕的文章.
今天要实现的效果如下:
1.弹幕垂直方向固定
2.弹幕垂直方向随机
上面效果图中白色的背景就是弹幕本身,是一个自定义的framelayout,我这里是为了更好的展示弹幕的位置才设置成了白色,当然如果是叠加在videoview上的话,就需要设置成透明色了.
制作弹幕需要考虑以下几点问题:
1.弹幕的大小可以随意调整
2.弹幕内移动的item(或者称字幕)出现的位置,水平方向是从屏幕右边移动到屏幕左边,垂直方向是不能超出弹幕本身的高度的.
3.字幕移除屏幕后,需要将对应item(字幕)从其父容器(弹幕)中移除.
4.如果字幕出现的垂直方向的高度是随机的,那么还需要避免字幕重叠的情况.
ok,下面是弹幕自定义view的代码:
/** * created by dell on 2016/9/28. */ public class danmuview extends framelayout { private static final string tag = "danmuview"; private static final long default_anim_duration = 6000; //默认每个动画的播放时长 private static final long default_query_duration = 3000; //遍历弹幕的默认间隔 private linkedlist<view> mviews = new linkedlist<>();//弹幕队列 private boolean isquerying; private int mwidth;//弹幕的宽度 private int mheight;//弹幕的高度 private handler muihandler = new handler(); private boolean topdirectionfixed;//弹幕顶部的方向是否固定 private handler mqueryhandler; private int mtopgravity = gravity.center_vertical;//顶部方向固定时的默认对齐方式 public void setheight(int height) { mheight = height; } public void setwidth(int width) { mwidth = width; } public void settopgravity(int gravity) { this.mtopgravity = gravity; } public void add(list<danmu> danmulist) { for (int i = 0; i < danmulist.size(); i++) { danmu danmu = danmulist.get(i); adddanmutoqueue(danmu); } } public void add(danmu danmu) { adddanmutoqueue(danmu); } public danmuview(context context) { this(context, null); } public danmuview(context context, attributeset attrs) { this(context, attrs, 0); } public danmuview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); handlerthread thread = new handlerthread("query"); thread.start(); //循环取出弹幕显示 mqueryhandler = new handler(thread.getlooper()) { @override public void handlemessage(message msg) { final view view = mviews.poll(); if (null != view) { muihandler.post(new runnable() { @override public void run() { //添加弹幕 showdanmu(view); } }); } sendemptymessagedelayed(0, default_query_duration); } }; } /** * 将要展示的弹幕添加到队列中 * * @param danmu */ private void adddanmutoqueue(danmu danmu) { if (null != danmu) { final view view = view.inflate(getcontext(), r.layout.layout_danmu, null); textview usernametv = (textview) view.findviewbyid(r.id.tv_username); textview infotv = (textview) view.findviewbyid(r.id.tv_info); imageview headeriv = (imageview) view.findviewbyid(r.id.iv_header); usernametv.settext(danmu.getusername());//昵称 infotv.settext(danmu.getinfo());//信息 glide.with(getcontext()).//头像 load(danmu.getheaderurl()). transform(new cropcircletransformation(getcontext())).into(headeriv); view.measure(0, 0); //添加弹幕到队列中 mviews.offerlast(view); } } /** * 播放弹幕 * * @param topdirectionfixed 弹幕顶部的方向是否固定 */ public void startplay(boolean topdirectionfixed) { this.topdirectionfixed = topdirectionfixed; if (mwidth == 0 || mheight == 0) { getviewtreeobserver().addongloballayoutlistener(new viewtreeobserver.ongloballayoutlistener() { @suppresslint("newapi") @override public void ongloballayout() { getviewtreeobserver().removeongloballayoutlistener(this); if (mwidth == 0) mwidth = getwidth() - getpaddingleft() - getpaddingright(); if (mheight == 0) mheight = getheight() - getpaddingtop() - getpaddingbottom(); if (!isquerying) { mqueryhandler.sendemptymessage(0); } } }); } else { if (!isquerying) { mqueryhandler.sendemptymessage(0); } } } /** * 显示弹幕,包括动画的执行 * * @param view */ private void showdanmu(final view view) { isquerying = true; log.d(tag, "mwidth:" + mwidth + " mheight:" + mheight); final layoutparams lp = new layoutparams(view.getmeasuredwidth(), view.getmeasuredheight()); lp.leftmargin = mwidth; if (topdirectionfixed) { lp.gravity = mtopgravity | gravity.left; } else { lp.gravity = gravity.left | gravity.top; lp.topmargin = getrandomtopmargin(view); } view.setlayoutparams(lp); view.settag(lp.topmargin); //设置item水平滚动的动画 valueanimator animator = valueanimator.ofint(mwidth, -view.getmeasuredwidth()); animator.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { lp.leftmargin = (int) animation.getanimatedvalue(); view.setlayoutparams(lp); } }); addview(view);//显示弹幕 animator.setduration(default_anim_duration); animator.setinterpolator(new linearinterpolator()); animator.start();//开启动画 animator.addlistener(new animatorlisteneradapter() { @override public void onanimationend(animator animation) { view.clearanimation(); existmarginvalues.remove(view.gettag());//移除已使用过的顶部边距 removeview(view);//移除弹幕 animation.cancel(); } }); } //记录当前仍在显示状态的弹幕的垂直方向位置(避免重复) private set<integer> existmarginvalues = new hashset<>(); private int linescount; private int range = 10; private int getrandomtopmargin(view view) { //计算可用的行数 linescount = mheight / view.getmeasuredheight(); if (linescount <= 1) { linescount = 1; } log.d(tag, "linescount:" + linescount); //检查重叠 while (true) { int randomindex = (int) (math.random() * linescount); int marginvalue = randomindex * (mheight / linescount); //边界检查 if (marginvalue > mheight - view.getmeasuredheight()) { marginvalue = mheight - view.getmeasuredheight() - range; } if (marginvalue == 0) { marginvalue = range; } if (!existmarginvalues.contains(marginvalue)) { existmarginvalues.add(marginvalue); log.d(tag, "marginvalue:" + marginvalue); return marginvalue; } } } }
弹幕实体类:
/** * created by dell on 2016/9/28. */ public class danmu { private string headerurl;//头像 private string username;//昵称 private string info;//信息 public string getheaderurl() { return headerurl; } public void setheaderurl(string headerurl) { this.headerurl = headerurl; } public string getusername() { return username; } public void setusername(string username) { this.username = username; } public string getinfo() { return info; } public void setinfo(string info) { this.info = info; } } 测试类,mainactivity public class mainactivity extends appcompatactivity { danmuview mdanmuview; edittext mmsgedt; button msendbtn; handler mdanmuaddhandler; boolean continueadd; int counter; @override protected void onresume() { super.onresume(); mdanmuview.startplay(true);//true表示弹幕的垂直方向是固定的,false则随机 continueadd = true; mdanmuaddhandler.sendemptymessagedelayed(0, 6000); } @override protected void onpause() { super.onpause(); continueadd = false; mdanmuaddhandler.removemessages(0); } @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); initview(); initdata(); initlistener(); } private void initview() { mdanmuview = (danmuview) findviewbyid(r.id.danmuview); mmsgedt = (edittext) findviewbyid(r.id.edt_msg); msendbtn = (button) findviewbyid(r.id.btn_send); } private void initdata() { list<danmu> danmulist = new arraylist<>(); for (int i = 0; i < 3; i++) { danmu danmu = new danmu(); danmu.setheaderurl("http://tupian.qqjay.com/tou3/2016/0725/cb00091099ffbf09f4861f2bbb5dd993.jpg"); danmu.setusername("mr.chen" + i); danmu.setinfo("我是弹幕啊,不要问我为什么不可以那么长!!!"); danmulist.add(danmu); } mdanmuview.add(danmulist); //下面是模拟每秒添加一个弹幕的过程 handlerthread ht = new handlerthread("send danmu"); ht.start(); mdanmuaddhandler = new handler(ht.getlooper()) { @override public void handlemessage(message msg) { runonuithread(new runnable() { @override public void run() { danmu danmu = new danmu(); danmu.setheaderurl("http://tupian.qqjay.com/tou3/2016/0803/87a8b262a5edeff0e11f5f0ba24fb22f.jpg"); danmu.setusername("mr.new" + (counter++)); danmu.setinfo("新的弹幕啊!!!新的弹幕啊!!!新的弹幕啊!!!新的弹幕啊!!!"); mdanmuview.add(danmu); } }); //继续添加 if (continueadd) { sendemptymessagedelayed(0, 1000); } } }; } private void initlistener() { //手动添加 msendbtn.setonclicklistener(new view.onclicklistener() { @override public void onclick(view v) { string msg = mmsgedt.gettext().tostring().trim(); if (textutils.isempty(msg)) { toast.maketext(mainactivity.this, "亲,你想发送什么啊?", toast.length_short).show(); return; } mmsgedt.settext(""); danmu danmu = new danmu(); danmu.setheaderurl("http://img0.imgtn.bdimg.com/it/u=2198087564,4037394230&fm=11&gp=0.jpg"); danmu.setusername("i'am good man"); danmu.setinfo("我是新人:" + msg); mdanmuview.add(danmu); } }); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: ToolBar使用方法详解
下一篇: 深入理解python函数递归和生成器