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库的项目主页地址是:https://github.com/Bilibili/DanmakuFlameMaster

添加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,里面还有很多炫酷的功能。

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

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索android
, 弹幕
网络直播
android弹幕效果实现、android 弹幕实现原理、android 视频弹幕实现、android 弹幕文字实现、android 弹幕实现,以便于您获取更多的相关知识。

时间: 2024-09-01 07:39:33

Android实现炫酷的网络直播弹幕功能_Android的相关文章

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

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

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

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

Android打造炫酷的电影票在线选座app在线选座功能_Android

不知道大家有没有用过,淘宝电影客户端(淘票票)买过电影票,纵观各类在线选座app的在线选座功能 淘宝在线选座功能用户体验最好,用起来最顺手,夸张点说已经到了炉火纯青的地步,下面我们看一下效果: 效果分析: 整个控件分成几个部分,座位图区域.座位缩略图区域.行号区域.屏幕区域 1.座位图可以自由的移动缩放,放大缩小移动后会自动回弹到合适的位置,选中座位会自动放大到合适比例. 2.行号部分跟着座位图缩放以及上下移动,屏幕区域跟着座位图左右移动缩放. 3.当手指按下的时候会出现缩略图,缩略图上有个红色

Android中通过AsyncTask类来制作炫酷进度条的实例教程_Android

AsyncTask (API level 3,所以几乎所有目前在市面上流通的 Android 版本皆可使用) 是除 Thread 外的另一种选择,Android 团队鼓励主执行绪(UI thread) 专注于操作 & 画面的流畅呈现, 其余工作 (如网络资料传输.档案/磁碟/资料存取) 最好都在背景执行: Thread 通常要搭配 Handler 使用,而 AsyncTask 用意在简化背景执行 thread 程序码的撰写. 如果您预期要执行的工作能在几秒内完成,就可以选择使用 AsyncTas

Android实现炫酷的CheckBox效果_Android

首先贴出实现的效果图: gif的效果可能有点过快,在真机上运行的效果会更好一些.我们主要的思路就是利用属性动画来动态地画出选中状态以及对勾的绘制过程.看到上面的效果图,相信大家都迫不及待地要跃跃欲试了,那就让我们开始吧. 自定义View的第一步:自定义属性. <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="SmoothChe

Android绘制炫酷引导界面_Android

一个超炫的引导界面,分享给大家 代码: MainActivity.java package com.bzu.gxs.webview1; import android.app.Activity; import android.os.Build; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.KeyEvent; import android.view.Men

Android打造炫酷进度条效果

本文实例为大家分享了Android炫酷进度条效果的具体代码,供大家参考,具体内容如下 学习:视频地址 HorizontalProgressbarWithProgress的代码 import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.os.Build; imp

Android自定义view实现电影票在线选座功能_Android

先看看电影票在线选座功能实现的效果图: 界面比较粗糙,主要看原理. 这个界面主要包括以下几部分 1.座位 2.左边的排数 3.左上方的缩略图 4.缩略图中的红色区域 5.手指移动时跟随移动 6.两个手指缩放时跟随缩放 主要技术点 1.矩阵Matrix 2.GestureDetector与ScaleGestureDetector 3.Bitmap的一下基本用法 4.这里只需要重写view的onDraw就可实现全部功能 可以发现这个其实没什么难度,主要就是一些位置的计算. 为了能便于理解首先把要用到

Android自定义View仿支付宝输入六位密码功能_Android

跟选择银行卡界面类似,也是用一个PopupWindow,不过输入密码界面是一个自定义view,当输入六位密码完成后用回调在Activity中获取到输入的密码并以Toast显示密码.效果图如下: 自定义view布局效果图及代码如下: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/