Android自制精彩弹幕效果

好久没有写过文章,最近发现直播特别的火,很多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);
   }
  });
 }
}

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

(0)

相关推荐

  • Android编程实现简易弹幕效果示例【附demo源码下载】

    本文实例讲述了Android编程实现简易弹幕效果.分享给大家供大家参考,具体如下: 首先上效果图,类似于360检测到骚扰电话页面: 布局很简单,上面是一个RelativeLayout,下面一个Button. 功能: (1)弹幕生成后自动从右侧往左侧滚动(TranslateAnimation),弹幕消失后立刻被移除. (2)弹幕位置随机出现,并且不重复(防止文字重叠). (3)字体大小在一定范围内随机改变,字体颜色也可以设置. (4)自定义先减速,后加速的Interpolator,弹幕加速进入.减

  • Android弹幕框架 黑暗火焰使基本使用方法

    今天我将分享由BiliBili开源的Android弹幕框架(DanmakuFlameMaster)的学习经验. 我是将整个框架以model的形式引入项目中的,这样更方便的观察源码.也可以通过依赖的方式注入进来 dependencies { compile 'com.github.ctiao:DanmakuFlameMaster:0.5.3' } 先放一下我要做成的效果图: 页面分析 从上图来看,整个UI分成了三层.最下面是视频层,中间是弹幕层,顶层是控制层.现在市场上主流的视频直播软件大多都是这

  • 实例解析如何在Android应用中实现弹幕动画效果

    在B站或者其他视频网站看视频时,常常会打开弹幕效果,边看节目边看大家的吐槽.弹幕看起来很有意思,今天我们就来实现一个简单的弹幕效果. 从直观上,弹幕效果就是在一个ViewGroup上增加一些View,然后让这些View移动起来.所以,整体的实现思路大概是这样的: 1.定义一个RelativeLayout,在里面动态添加TextView. 2.这些TextView的字体大小.颜色.移动速度.初始位置都是随机的. 3.将TextView添加到RelativeLayout的右边缘,每隔一段时间添加一个

  • Android 实现仿网络直播弹幕功能详解及实例

    Android 网络直播弹幕                最近看好多网络电视,播放器及直播都有弹幕功能,自己周末捣鼓下并实现,以下是网上的资料,大家可以看下. 现在网络直播越来越火,网络主播也逐渐成为一种新兴职业,对于网络直播,弹幕功能是必须要有的,如下图: 首先来分析一下,这个弹幕功能是怎么实现的,首先在最下面肯定是一个游戏界面View,然后游戏界面上有弹幕View,弹幕的View必须要做成完全透明的,这样即使覆盖在游戏界面的上方也不会影响到游戏的正常观看,只有当有人发弹幕消息时,再将消息绘

  • Android仿斗鱼直播的弹幕效果

    记得之前有位朋友在我的公众号里问过我,像直播的那种弹幕功能该如何实现?如今直播行业确实是非常火爆啊,大大小小的公司都要涉足一下直播的领域,用斗鱼的话来讲,现在就是千播之战.而弹幕则无疑是直播功能当中最为重要的一个功能之一,那么今天,我就带着大家一起来实现一个简单的Android端弹幕效果. 分析 首先我们来看一下斗鱼上的弹幕效果,如下图所示: 这是一个Dota2游戏直播的界面,我们可以看到,在游戏界面的上方有很多的弹幕,看直播的观众们就是在这里进行讨论的. 那么这样的一个界面该如何实现呢?其实并

  • Android EasyBarrage实现轻量级弹幕效果

    本文介绍了Android EasyBarrage实现轻量级弹幕效果,分享给大家,具体如下: 概述 EasyBarrage是Android平台的一种轻量级弹幕效果目前支持以下设置: 自定义字体颜色,支持随机颜色: 自定义字体大小,支持随机字体大小: 支持边框显示,用于区分自己的弹幕和其他弹幕: 自定义边框颜色: 弹幕数据是否允许重复: 自定义单屏显示的最大弹幕数量: 数据不重叠: 支持动态添加弹幕: 不依赖VideoView,数据自动循环显示. github:https://github.com/

  • Android实现炫酷的网络直播弹幕功能

    现在网络直播越来越火,网络主播也逐渐成为一种新兴职业,对于网络直播,弹幕功能是必须要有的,如下图: 首先来分析一下,这个弹幕功能是怎么实现的,首先在最下面肯定是一个游戏界面View,然后游戏界面上有弹幕View,弹幕的View必须要做成完全透明的,这样即使覆盖在游戏界面的上方也不会影响到游戏的正常观看,只有当有人发弹幕消息时,再将消息绘制到弹幕的View上面就可以了,下方肯定还有有操作界面View,可以让用户来发弹幕和送礼物的功能,原理示意图如下所示: 参照原理图,下面一步一步来实现这个功能.

  • 很棒的Android弹幕效果实例

    很多项目需要用到弹幕效果,尤其是在播放视频的时候需要一起显示别人发的弹幕,也包括自己的发的. 今天就试着写了一下这个效果. 思路就是将从右往左的动画效果,字体内容,字体大小,弹幕平移速度等属性一起与TextView封装成BarrageItem,并将控制效果与BarrageItem绑定在BarrageView进行显示.思路还是比较简单的.这里没有考虑到带有表情的弹幕,我会持续更新的. 先看效果: 项目目录结构: 接下来定义Barrageitem.class : 这个类就将TextView与从右往左

  • Android实现自定义的弹幕效果

    一.效果图 先来看看效果图吧~~ 二.实现原理方案 1.自定义ViewGroup-XCDanmuView,继承RelativeLayout来实现,当然也可以继承其他三大布局类哈 2.初始化若干个TextView(弹幕的item View,这里以TextView 为例,当然也可以其他了~),然后通过addView添加到自定义View中 3.通过addView添加到XCDanmuView中,位置在坐标,为了实现 从屏幕外移动进来的效果 我们还需要修改添加进来TextView的位置,以从右向左移动方向

  • Android自制精彩弹幕效果

    好久没有写过文章,最近发现直播特别的火,很多app都集成了直播的功能,发现有些直播是带有弹幕的,效果还不错,今天心血来潮,特地写了篇制作弹幕的文章. 今天要实现的效果如下: 1.弹幕垂直方向固定 2.弹幕垂直方向随机 上面效果图中白色的背景就是弹幕本身,是一个自定义的FrameLayout,我这里是为了更好的展示弹幕的位置才设置成了白色,当然如果是叠加在VideoView上的话,就需要设置成透明色了. 制作弹幕需要考虑以下几点问题: 1.弹幕的大小可以随意调整 2.弹幕内移动的item(或者称字

  • Android Flutter实现弹幕效果

    目录 前言 通用弹幕实现方案 ListView弹幕方案实现 基本框架 轮播滚动 轮询算法 点击事件 前言 需求要点如下: 弹幕行数为3行,每条弹幕相互依靠但不存在重叠 每条弹幕可交互点击跳转 滚动速度恒定 触摸不可暂停播放 弹幕数据固定一百条且支持轮询播放 弹幕排序规则如下: 1 4 7 2 5 8 3 6 9 通用弹幕实现方案 Flutter Dev Package已有开源弹幕实现组件,这里举例barrage_page的实现方式(大多数实现底层逻辑基本一样). 基本架构采用Stack然后向布局

  • Android简单实现弹幕效果

    本文实例为大家分享了Android实现弹幕效果的具体代码,供大家参考,具体内容如下 首先分析一下,他是由三层布局来共同完成的,第一层视频布局,第二层字幕布局,第三层输入框布局,要想让这三个布局在同一页面上,必须用相对布局或帧布局. <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/

  • Android双重SurfaceView实现弹幕效果

    本文实例为大家分享了Android双重SurfaceView实现弹幕效果的具体代码,供大家参考,具体内容如下 页面布局 首先是XML的layout布局,这里的总的父布局是一个FrameLayout用于贴上两个SurfaceView,一个用来播放视频,一个用来显示弹幕 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.andro

  • 又一枚精彩的弹幕效果jQuery实现

    简易弹幕效果:将发布的内容随机显示在弹幕右侧,逐渐左移最后消失. 涉及知识点:val().random().height().css().append().remove()等,主要是元素的操作 html代码: <a href="#">弹幕技术</a> <div class="mask"> <a href="#" class="button">X</a> </di

  • 终于实现了!精彩的jquery弹幕效果

    本文实例为大家分享了jquery弹幕效果,供大家参考,具体内容如下 页面效果如下: html页面如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"

随机推荐