欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android自制精彩弹幕效果

程序员文章站 2023-12-03 11:44:10
好久没有写过文章,最近发现直播特别的火,很多app都集成了直播的功能,发现有些直播是带有弹幕的,效果还不错,今天心血来潮,特地写了篇制作弹幕的文章. 今天要实现的效果如下...

好久没有写过文章,最近发现直播特别的火,很多app都集成了直播的功能,发现有些直播是带有弹幕的,效果还不错,今天心血来潮,特地写了篇制作弹幕的文章.

今天要实现的效果如下:

1.弹幕垂直方向固定

Android自制精彩弹幕效果

2.弹幕垂直方向随机

Android自制精彩弹幕效果

上面效果图中白色的背景就是弹幕本身,是一个自定义的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);
   }
  });
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。