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

Android 网络直播弹幕

               最近看好多网络电视,播放器及直播都有弹幕功能,自己周末捣鼓下并实现,以下是网上的资料,大家可以看下。

现在网络直播越来越火,网络主播也逐渐成为一种新兴职业,对于网络直播,弹幕功能是必须要有的,如下图:

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

参照原理图,下面一步一步来实现这个功能。

实现视频的播放

activity_main.xml

<RelativeLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/activity_main"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="#000"> 

 <VideoView
  android:id="@+id/video_view"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:layout_centerInParent="true"/>
</RelativeLayout> 

MainActivity.java

package com.jackie.bombscreen; 

import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.VideoView; 

public class MainActivity extends AppCompatActivity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  VideoView videoView = (VideoView) findViewById(R.id.video_view);
  videoView.setVideoPath(Environment.getExternalStorageDirectory() + "/xiaoxingyun.mp4");
  videoView.start();
 } 

 @Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  if (hasFocus && Build.VERSION.SDK_INT >= 19) {
   View decorView = getWindow().getDecorView();
   decorView.setSystemUiVisibility(
     View.SYSTEM_UI_FLAG_LAYOUT_STABLE
       | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
       | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
       | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
       | View.SYSTEM_UI_FLAG_FULLSCREEN
       | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
  }
 }
} 

最后别忘了设置AndroidMainfest.xml

效果如下:

实现弹幕的效果

接下来我们开始实现弹幕效果。弹幕其实也就是一个自定义的View,它的上面可以显示类似于跑马灯的文字效果。观众们发表的评论都会在弹幕上显示出来,但又会很快地移出屏幕,既可以起到互动的作用,同时又不会影响视频的正常观看。

我们可以自己来编写这样的一个自定义View,当然也可以直接使用网上现成的开源项目。那么为了能够简单快速地实现弹幕效果,这里我就准备直接使用由哔哩哔哩开源的弹幕效果库DanmakuFlameMaster。

DanmakuFlameMaster库的项目主页地址是:http://xiazai.jb51.net/201611/yuanma/DanmakuFlameMaster-master(jb51.net).rar

添加build.gradle依赖

compile 'com.github.ctiao:DanmakuFlameMaster:0.5.3'

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/activity_main"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="#000"> 

 <VideoView
  android:id="@+id/video_view"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:layout_centerInParent="true"/> 

 <master.flame.danmaku.ui.widget.DanmakuView
  android:id="@+id/danmaku_view"
  android:layout_width="match_parent"
  android:layout_height="match_parent" />
</RelativeLayout>

修改MainActivity.java

package com.jackie.bombscreen; 

import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.VideoView; 

import java.util.Random; 

import master.flame.danmaku.controller.DrawHandler;
import master.flame.danmaku.danmaku.model.BaseDanmaku;
import master.flame.danmaku.danmaku.model.DanmakuTimer;
import master.flame.danmaku.danmaku.model.IDanmakus;
import master.flame.danmaku.danmaku.model.android.DanmakuContext;
import master.flame.danmaku.danmaku.model.android.Danmakus;
import master.flame.danmaku.danmaku.parser.BaseDanmakuParser;
import master.flame.danmaku.ui.widget.DanmakuView; 

public class MainActivity extends AppCompatActivity {
 private boolean mIsShowDanmaku;
 private DanmakuView mDanmakuView;
 private DanmakuContext mDanmakuContext; 

 private BaseDanmakuParser parser = new BaseDanmakuParser() {
  @Override
  protected IDanmakus parse() {
   return new Danmakus();
  }
 }; 

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  VideoView videoView = (VideoView) findViewById(R.id.video_view);
  videoView.setVideoPath(Environment.getExternalStorageDirectory() + "/xiaoxingyun.mp4");
  videoView.start(); 

  mDanmakuView = (DanmakuView) findViewById(R.id.danmaku_view);
  mDanmakuView.enableDanmakuDrawingCache(true);
  mDanmakuView.setCallback(new DrawHandler.Callback() {
   @Override
   public void prepared() {
    mIsShowDanmaku = true;
    mDanmakuView.start();
    generateSomeDanmaku();
   } 

   @Override
   public void updateTimer(DanmakuTimer timer) { 

   } 

   @Override
   public void danmakuShown(BaseDanmaku danmaku) { 

   } 

   @Override
   public void drawingFinished() { 

   }
  }); 

  mDanmakuContext = DanmakuContext.create();
  mDanmakuView.prepare(parser, mDanmakuContext);
 } 

 /**
  * 向弹幕View中添加一条弹幕
  * @param content  弹幕的具体内容
  * @param withBorder 弹幕是否有边框
  */
 private void addDanmaku(String content, boolean withBorder) {
  BaseDanmaku danmaku = mDanmakuContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
  danmaku.text = content;
  danmaku.padding = 5;
  danmaku.textSize = sp2px(20);
  danmaku.textColor = Color.WHITE;
  danmaku.setTime(mDanmakuView.getCurrentTime());
  if (withBorder) {
   danmaku.borderColor = Color.GREEN;
  }
  mDanmakuView.addDanmaku(danmaku);
 } 

 /**
  * 随机生成一些弹幕内容以供测试
  */
 private void generateSomeDanmaku() {
  new Thread(new Runnable() {
   @Override
   public void run() {
    while(mIsShowDanmaku) {
     int time = new Random().nextInt(300);
     String content = "" + time + time;
     addDanmaku(content, false);
     try {
      Thread.sleep(time);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
   }
  }).start();
 } 

 /**
  * sp转px的方法。
  */
 public int sp2px(float spValue) {
  final float fontScale = getResources().getDisplayMetrics().scaledDensity;
  return (int) (spValue * fontScale + 0.5f);
 } 

 @Override
 protected void onPause() {
  super.onPause();
  if (mDanmakuView != null && mDanmakuView.isPrepared()) {
   mDanmakuView.pause();
  }
 } 

 @Override
 protected void onResume() {
  super.onResume();
  if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) {
   mDanmakuView.resume();
  }
 } 

 @Override
 protected void onDestroy() {
  super.onDestroy();
  mIsShowDanmaku = false;
  if (mDanmakuView != null) {
   mDanmakuView.release();
   mDanmakuView = null;
  }
 } 

 @Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  if (hasFocus && Build.VERSION.SDK_INT >= 19) {
   View decorView = getWindow().getDecorView();
   decorView.setSystemUiVisibility(
     View.SYSTEM_UI_FLAG_LAYOUT_STABLE
       | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
       | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
       | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
       | View.SYSTEM_UI_FLAG_FULLSCREEN
       | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
  }
 }
} 

效果图如下:

加入操作界面

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/activity_main"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="#000"> 

 <VideoView
  android:id="@+id/video_view"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:layout_centerInParent="true"/> 

 <master.flame.danmaku.ui.widget.DanmakuView
  android:id="@+id/danmaku_view"
  android:layout_width="match_parent"
  android:layout_height="match_parent" /> 

 <LinearLayout
  android:id="@+id/operation_layout"
  android:layout_width="match_parent"
  android:layout_height="50dp"
  android:layout_alignParentBottom="true"
  android:background="#fff"
  android:visibility="gone"> 

  <EditText
   android:id="@+id/edit_text"
   android:layout_width="0dp"
   android:layout_height="match_parent"
   android:layout_weight="1" /> 

  <Button
   android:id="@+id/send"
   android:layout_width="wrap_content"
   android:layout_height="match_parent"
   android:text="Send" />
 </LinearLayout>
</RelativeLayout> 
package com.jackie.bombscreen; 

import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.VideoView; 

import java.util.Random; 

import master.flame.danmaku.controller.DrawHandler;
import master.flame.danmaku.danmaku.model.BaseDanmaku;
import master.flame.danmaku.danmaku.model.DanmakuTimer;
import master.flame.danmaku.danmaku.model.IDanmakus;
import master.flame.danmaku.danmaku.model.android.DanmakuContext;
import master.flame.danmaku.danmaku.model.android.Danmakus;
import master.flame.danmaku.danmaku.parser.BaseDanmakuParser;
import master.flame.danmaku.ui.widget.DanmakuView; 

public class MainActivity extends AppCompatActivity {
 private boolean mIsShowDanmaku;
 private DanmakuView mDanmakuView;
 private DanmakuContext mDanmakuContext; 

 private BaseDanmakuParser parser = new BaseDanmakuParser() {
  @Override
  protected IDanmakus parse() {
   return new Danmakus();
  }
 }; 

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  VideoView videoView = (VideoView) findViewById(R.id.video_view);
  videoView.setVideoPath(Environment.getExternalStorageDirectory() + "/xiaoxingyun.mp4");
  videoView.start(); 

  mDanmakuView = (DanmakuView) findViewById(R.id.danmaku_view);
  mDanmakuView.enableDanmakuDrawingCache(true);
  mDanmakuView.setCallback(new DrawHandler.Callback() {
   @Override
   public void prepared() {
    mIsShowDanmaku = true;
    mDanmakuView.start();
    generateSomeDanmaku();
   } 

   @Override
   public void updateTimer(DanmakuTimer timer) { 

   } 

   @Override
   public void danmakuShown(BaseDanmaku danmaku) { 

   } 

   @Override
   public void drawingFinished() { 

   }
  }); 

  mDanmakuContext = DanmakuContext.create();
  mDanmakuView.prepare(parser, mDanmakuContext); 

  final LinearLayout operationLayout = (LinearLayout) findViewById(R.id.operation_layout);
  final Button send = (Button) findViewById(R.id.send);
  final EditText editText = (EditText) findViewById(R.id.edit_text);
  mDanmakuView.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
    if (operationLayout.getVisibility() == View.GONE) {
     operationLayout.setVisibility(View.VISIBLE);
    } else {
     operationLayout.setVisibility(View.GONE);
    }
   }
  }); 

  send.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
    String content = editText.getText().toString();
    if (!TextUtils.isEmpty(content)) {
     addDanmaku(content, true);
     editText.setText("");
    }
   }
  }); 

  getWindow().getDecorView().setOnSystemUiVisibilityChangeListener (new View.OnSystemUiVisibilityChangeListener() {
   @Override
   public void onSystemUiVisibilityChange(int visibility) {
    if (visibility == View.SYSTEM_UI_FLAG_VISIBLE) {
     onWindowFocusChanged(true);
    }
   }
  });
 } 

 /**
  * 向弹幕View中添加一条弹幕
  * @param content  弹幕的具体内容
  * @param withBorder 弹幕是否有边框
  */
 private void addDanmaku(String content, boolean withBorder) {
  BaseDanmaku danmaku = mDanmakuContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
  danmaku.text = content;
  danmaku.padding = 5;
  danmaku.textSize = sp2px(20);
  danmaku.textColor = Color.WHITE;
  danmaku.setTime(mDanmakuView.getCurrentTime());
  if (withBorder) {
   danmaku.borderColor = Color.GREEN;
  }
  mDanmakuView.addDanmaku(danmaku);
 } 

 /**
  * 随机生成一些弹幕内容以供测试
  */
 private void generateSomeDanmaku() {
  new Thread(new Runnable() {
   @Override
   public void run() {
    while(mIsShowDanmaku) {
     int time = new Random().nextInt(300);
     String content = "" + time + time;
     addDanmaku(content, false);
     try {
      Thread.sleep(time);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
   }
  }).start();
 } 

 /**
  * sp转px的方法。
  */
 public int sp2px(float spValue) {
  final float fontScale = getResources().getDisplayMetrics().scaledDensity;
  return (int) (spValue * fontScale + 0.5f);
 } 

 @Override
 protected void onPause() {
  super.onPause();
  if (mDanmakuView != null && mDanmakuView.isPrepared()) {
   mDanmakuView.pause();
  }
 } 

 @Override
 protected void onResume() {
  super.onResume();
  if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) {
   mDanmakuView.resume();
  }
 } 

 @Override
 protected void onDestroy() {
  super.onDestroy();
  mIsShowDanmaku = false;
  if (mDanmakuView != null) {
   mDanmakuView.release();
   mDanmakuView = null;
  }
 } 

 @Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  if (hasFocus && Build.VERSION.SDK_INT >= 19) {
   View decorView = getWindow().getDecorView();
   decorView.setSystemUiVisibility(
     View.SYSTEM_UI_FLAG_LAYOUT_STABLE
       | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
       | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
       | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
       | View.SYSTEM_UI_FLAG_FULLSCREEN
       | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
  }
 }
} 

效果图如下:

自己发的弹幕有绿色边框,很容易区分。

基本上实现了弹幕的功能,当然,里面的知识点还有很多,这只是最基本的功能。有时间的话,建议学学DanmakuFlameMaster,里面还有很多炫酷的功能。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

(0)

相关推荐

  • Android自定义View模仿虎扑直播界面的打赏按钮功能

    前言 作为一个资深篮球爱好者,我经常会用虎扑app看比赛直播,后来注意到文字直播界面右下角加了两个按钮,可以在直播过程中送虎扑币,为自己支持的球队加油. 具体的效果如下图所示: 我个人觉得挺好玩的,所以决定自己实现下这个按钮,废话不多说,先看实现的效果吧: 这个效果看起来和popupwindow差不多,但我是采用自定义view的方式来实现,下面说说过程. 实现过程 首先从虎扑的效果可以看到,它这两个按钮时浮在整个界面之上的,所以它需要和FrameLayout结合使用,因此我让它的宽度跟随屏幕大小

  • Android贝塞尔曲线实现直播点赞效果

    本文实例为大家分享了Android实现直播点赞效果的具体代码,供大家参考,具体内容如下 效果展示 原理分析 点赞效果最主要的难点和原理在于贝塞尔曲线动画的生成,我们通过图片主要讲解贝塞尔曲线动画 1.需要找到贝塞尔曲线的四个点 2.通过三级贝塞尔曲线的公式计算,获取贝塞尔曲线的轨迹路径点 3.通过设置点赞图片X,Y坐标,从而形成点赞的效果 实现步骤 1.初始化变量 //1.继承RelativeLayout public class ChristmasView extends RelativeLa

  • android实现直播点赞飘心动画效果

    前段时间在写直播的时候,需要观众在看直播的时候点赞的效果,在此参照了腾讯大神写的点赞(飘心动画效果).下面是效果图: 1.自定义飘心动画的属性 在attrs.xml 中增加自定义的属性 <!-- 飘心动画自定义的属性 --> <declare-styleable name="HeartLayout"> <attr name="initX" format="dimension"/> <attr name=&

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

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

  • Android直播app送礼物连击动画效果(实例代码)

    最近在做公司的直播项目,需要实现一个观看端连击送礼物的控件: 直接上代码: /** * @author yangyinglong on 2017/7/11 16:52. * @Description: todo(这里用一句话描述这个类的作用) * @Copyright Copyright (c) 2017 Tuandai Inc. All Rights Reserved. */ public class CustomGiftView extends LinearLayout { private

  • Android仿直播特效之点赞飘心效果

    本文实例为大家分享了Android实现点赞飘心效果的具体代码,供大家参考,具体内容如下 一.概述 老规矩先上图 好了,基本就是这个样子,录完的视频用格式工厂转换完就这个样子了,将就看吧 二.定义我们自己的Layout /** * @author 刘洋巴金 * @date 2017-4-27 * * 定义我们自己的布局 * */ public class LoveLayout extends RelativeLayout{ private Context context; private Layo

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

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

  • Android控件实现直播App特效之点赞飘心动画

    现在市面上直播类的应用可以说是一抓一大把,随随便便就以什么主题来开发个直播App,说白了就想在这领域分杯羹.在使用这些应用过程中其实不难发现,在所有的直播界面,少不了的就是各种打赏.各种点赞.今天自己就针对点赞功能敲了一下,代码不多,主要是涉及到动画运动轨迹运算,这里需借助 贝塞尔曲线 相关知识,我使用三阶贝塞尔曲线来实现轨迹动画. 运行效果 一.具体实现流程 仔细分析整个点赞过程可以发现,首先是"爱心"的出现动画,然后是"爱心"以类似气泡的形式向上运动. &quo

  • Android高级UI特效仿直播点赞动画效果

    本文给大家分享高级UI特效仿直播点赞效果-一个优美炫酷的点赞动画,具体实现代码大家参考本文. 效果图如下: 攻克难点: 心形图片的路径等走向 心形图片的控制范围 部分代码如下: 通过AbstractPathAnimator定义飘心动画控制器 @Override public void start(final View child, final ViewGroup parent) { parent.addView(child, new ViewGroup.LayoutParams(mConfig.

  • Android实现直播聊天区域中顶部的渐变效果

    背景 在4月份开发直播时,有一个需求,需要实现一个RecylerView顶部渐变的效果 实际效果 解决思路 图层重叠处理(本质是alpha叠加出来的效果) 实现流程 保存一个图层,然后画渐变,最后再和原来的图层进行合并,达到这个效果. 涉及知识(不知道的请google): *      主要通过RecyclerView 的 ItemDecoration类进行解决. *      Paint.Canvas.Shader.Xfermode(图层融合) *      Gradient(渐变) 详细过程

随机推荐