Android開發類似直播APP的彈幕和懸浮窗播放功能
閒來無事,最近自己查網上資料開發可以發送彈幕和懸浮窗播放功能的APP,寫的不好,輕噴。
一、彈幕功能主要使用嗶哩嗶哩的彈幕庫進行開發的,可以發送自己輸入的彈幕文字,還做了彈幕是否顯示的開關。上代碼:
package com.barrage.barragetest.activity;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Environment;
import android.support.design.button.MaterialButton;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.VideoView;
import com.barrage.barragetest.R;
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 PlayVideoActivity extends Activity{
private boolean showDanmaku;
private DanmakuView danmakuView;
private DanmakuContext danmakuContext;
private VideoView videoView;
private LinearLayout ll_linearlayout;
private EditText et_write;
private MaterialButton mb_send,mb_close;
//視頻地址
// private String file_path = Environment.getExternalStorageDirectory() + "/DCIM/Camera/shipin.mp4";
private String file_path = "android.resource://com.barrage.barragetest/" + R.raw.shipin;
private BaseDanmakuParser parser = new BaseDanmakuParser() {
@Override
protected IDanmakus parse() {
return new Danmakus();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//隱去標題欄(應用程序的名字)
requestWindowFeature(Window.FEATURE_NO_TITLE);
//隱去狀態欄部分(電池等圖標和一起修飾部分)
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);// 設置全屏
setContentView(R.layout.activity_play_video);
initView();
}
//初始化控件
private void initView(){
ll_linearlayout = (LinearLayout) findViewById(R.id.ll_linearlayout); //彈幕輸入框
et_write = (EditText) findViewById(R.id.et_write); //彈幕輸入框
mb_send = (MaterialButton) findViewById(R.id.mb_send); //發送彈幕
mb_close = (MaterialButton) findViewById(R.id.mb_close); //關閉或打開彈幕
videoView = (VideoView) findViewById(R.id.video_view);
videoView.setVideoPath(file_path); //加載視頻
videoView.start();
danmakuView = (DanmakuView) findViewById(R.id.danmaku_view);
danmakuView.enableDanmakuDrawingCache(true);
danmakuView.setCallback(new DrawHandler.Callback() {
@Override
public void prepared() {
showDanmaku = true;
danmakuView.start();
generateSomeDanmaku();
}
@Override
public void updateTimer(DanmakuTimer timer) {
}
@Override
public void danmakuShown(BaseDanmaku danmaku) {
}
@Override
public void drawingFinished() {
}
});
danmakuContext = DanmakuContext.create();
danmakuView.prepare(parser, danmakuContext);
initEvent();
}
//點擊事件
private void initEvent(){
//發送彈幕
mb_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!"".equals(et_write.getText().toString())) {
addDanmaku(et_write.getText().toString(),true);
et_write.setText("");
}
}
});
//控制彈幕開關
mb_close.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//關閉彈幕
if ("關閉".equals(mb_close.getText().toString())) {
showDanmaku = false;
mb_close.setText("打開");
}else{//打開彈幕
showDanmaku = true;
mb_close.setText("關閉");
generateSomeDanmaku();
}
}
});
danmakuView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ll_linearlayout.getVisibility()== View.VISIBLE) { //已顯示
ll_linearlayout.setVisibility(View.GONE);
}else{ //隱藏
ll_linearlayout.setVisibility(View.VISIBLE);
}
}
});
}
/**
* 向彈幕View中添加一條彈幕
* @param content
* 彈幕的具體內容
* @param withBorder
* 彈幕是否有邊框
*/
private void addDanmaku(String content, boolean withBorder) {
BaseDanmaku danmaku = danmakuContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
danmaku.text = content;
danmaku.padding = 5;
danmaku.textSize = sp2px(20);
danmaku.textColor = Color.WHITE;
danmaku.setTime(danmakuView.getCurrentTime());
if (withBorder) {
//設置綠色邊框,藍色字體
danmaku.borderColor = Color.GREEN;
danmaku.textColor = Color.BLUE;
}
danmakuView.addDanmaku(danmaku);
}
/**
* 隨機生成一些彈幕內容以供測試
*/
private void generateSomeDanmaku() {
new Thread(new Runnable() {
@Override
public void run() {
while(showDanmaku) {
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 (danmakuView != null && danmakuView.isPrepared()) {
danmakuView.pause();
}
}
@Override
protected void onResume() {
super.onResume();
if (danmakuView != null && danmakuView.isPrepared() && danmakuView.isPaused()) {
danmakuView.resume();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
showDanmaku = false;
if (danmakuView != null) {
danmakuView.release();
danmakuView = null;
}
}
}
頁面佈局activity_play_video.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"/>
<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/ll_linearlayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_margin="8dp"
android:background="#ffffff"
android:visibility="gone">
<EditText
android:id="@+id/et_write"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<android.support.design.button.MaterialButton
android:id="@+id/mb_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="發送"
android:theme="@style/Theme.MaterialComponents.Light"/>
<android.support.design.button.MaterialButton
android:id="@+id/mb_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="關閉"
android:theme="@style/Theme.MaterialComponents.Light"/>
</LinearLayout>
</RelativeLayout>
注意:1、不要忘了權限申請;2、這裏我把視頻文件放在項目裏,所以視頻路徑是項目資源文件路徑。
效果圖如下:
二、懸浮窗播放功能。該功能需要懸浮窗權限SYSTEM_ALERT_WINDOW,這是兩大特殊權限之一,需要手動設置。
寫個按鈕直接調用showWindow()方法就行,完整的頁面代碼我就不寫出來了
private WindowManager mWindowManager;
private WindowManager.LayoutParams mLayout;
// 窗口寬高值
private float x, y;
//懸浮窗口布局
private View mWindowsView;
//顯示懸浮窗口
public void showWindow() {
//先檢查是否具有懸浮窗權限
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(this, "當前無權限,請授權", Toast.LENGTH_SHORT);
startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 0);
} else {
// 取得系統窗體
mWindowManager = (WindowManager) getApplicationContext()
.getSystemService(WINDOW_SERVICE);
// 窗體的佈局樣式
mLayout = new WindowManager.LayoutParams();
// 設置窗體顯示類型——TYPE_SYSTEM_ALERT(系統提示)
mLayout.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
// 設置窗體焦點及觸摸:
// FLAG_NOT_FOCUSABLE(不能獲得按鍵輸入焦點)
mLayout.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
// 設置顯示的模式
mLayout.format = PixelFormat.RGBA_8888;
// 設置對齊的方法
mLayout.gravity = Gravity.TOP | Gravity.LEFT;
// 設置窗體寬度和高度
// 設置視頻的播放窗口大小
mLayout.width = 700;
mLayout.height = 400;
mLayout.x = 300;
mLayout.y = 300;
//將指定View解析後添加到窗口管理器裏面
mWindowsView = View.inflate(this, R.layout.layout_window, null);
VideoView vv_float_video = (VideoView) mWindowsView.findViewById(R.id.vv_float_video);
mWindowsView.findViewById(R.id.iv_close).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
closeWindow();
}
});
playVideo(vv_float_video);
mWindowManager.addView(mWindowsView, mLayout);
mWindowsView.setOnTouchListener(new View.OnTouchListener() {
float mTouchStartX;
float mTouchStartY;
@Override
public boolean onTouch(View view, MotionEvent event) {
x = event.getRawX();
y = event.getRawY() - 25;//25狀態欄大小
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mTouchStartX = event.getX();
mTouchStartY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
//原始座標減去移動座標
mLayout.x = (int) (x - mTouchStartX);
mLayout.y = (int) (y - mTouchStartY);
mWindowManager.updateViewLayout(mWindowsView, mLayout);
Log.i("main", "x值=" + x + "\ny值=" + y + "\nmTouchX" + mTouchStartX + "\nmTouchY=" + mTouchStartY);
break;
}
return true;
}
});
}
}
//播放視頻
private void playVideo(VideoView videoView) {
//獲取本地視頻文件進行播放
ContentResolver resolver = getContentResolver();
Cursor c = resolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, null, null, null, null);
if (c.moveToNext()) {
String path = c.getString(c.getColumnIndex(MediaStore.Video.Media.DATA));
videoView.setVideoPath(path);
videoView.requestFocus();
videoView.start();
}
}
//關閉窗口點擊事件
public void closeWindow() {
mWindowManager.removeView(mWindowsView);
}
窗口頁面佈局layout_window.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<VideoView
android:id="@+id/vv_float_video"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/iv_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:src="@mipmap/ic_close" />
</RelativeLayout>
效果圖如下: