Android仿斗魚(yú)直播的彈幕效果
記得之前有位朋友在我的公眾號(hào)里問(wèn)過(guò)我,像直播的那種彈幕功能該如何實(shí)現(xiàn)?如今直播行業(yè)確實(shí)是非?;鸨?,大大小小的公司都要涉足一下直播的領(lǐng)域,用斗魚(yú)的話(huà)來(lái)講,現(xiàn)在就是千播之戰(zhàn)。而彈幕則無(wú)疑是直播功能當(dāng)中最為重要的一個(gè)功能之一,那么今天,我就帶著大家一起來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Android端彈幕效果。
分析
首先我們來(lái)看一下斗魚(yú)上的彈幕效果,如下圖所示:
這是一個(gè)Dota2游戲直播的界面,我們可以看到,在游戲界面的上方有很多的彈幕,看直播的觀眾們就是在這里進(jìn)行討論的。
那么這樣的一個(gè)界面該如何實(shí)現(xiàn)呢?其實(shí)并不復(fù)雜,我們只需要首先在布局中放置一個(gè)顯示游戲界面的View,然后在游戲界面的上方再覆蓋一個(gè)顯示彈幕的View就可以了。彈幕的View必須要做成完全透明的,這樣即使覆蓋在游戲界面的上方也不會(huì)影響到游戲的正常觀看,只有當(dāng)有人發(fā)彈幕消息時(shí),再將消息繪制到彈幕的View上面就可以了。原理示意圖如下所示:
但是我們除了要能看到彈幕之外也要能發(fā)彈幕才行,因此還要再在彈幕的View上面再覆蓋一個(gè)操作界面的View,然后我們就可以在操作界面上發(fā)彈幕、送禮物等。原理示意圖如下所示:
這樣我們就把基本的實(shí)現(xiàn)原理分析完了,下面就讓我們開(kāi)始一步步實(shí)現(xiàn)吧。
實(shí)現(xiàn)視頻播放
由于本篇文章的主題是實(shí)現(xiàn)彈幕效果,并不涉及直播的任何其他功能,因此這里我們就簡(jiǎn)單地使用VideoView播放一個(gè)本地視頻來(lái)模擬最底層的游戲界面。
首先使用Android Studio新建一個(gè)DanmuTest項(xiàng)目,然后修改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>
布局文件的代碼非常簡(jiǎn)單,只有一個(gè)VideoView,我們將它設(shè)置為居中顯示。
然后修改MainActivity中的代碼,如下所示:
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() + "/Pixels.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); } } }
上面的代碼中使用了VideoView的最基本用法。在onCreate()方法中獲取到了VideoView的實(shí)例,給它設(shè)置了一個(gè)視頻文件的地址,然后調(diào)用start()方法開(kāi)始播放。當(dāng)然,我事先已經(jīng)在SD的根目錄中準(zhǔn)備了一個(gè)叫Pixels.mp4的視頻文件。
這里使用到了SD卡的功能,但是為了代碼簡(jiǎn)單起見(jiàn),我并沒(méi)有加入運(yùn)行時(shí)權(quán)限的處理,因此一定要記得將你的項(xiàng)目的targetSdkVersion指定成23以下。
另外,為了讓視頻播放可以有最好的體驗(yàn)效果,這里使用了沉浸式模式的寫(xiě)法。對(duì)沉浸式模式還不理解的朋友可以參考我的上一篇文章 Android狀態(tài)欄微技巧,帶你真正理解沉浸式模式 。
最后,我們?cè)贏ndroidManifest.xml中將Activity設(shè)置為橫屏顯示并加入權(quán)限聲明,如下所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.guolin.danmutest"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:screenOrientation="landscape" android:configChanges="orientation|keyboardHidden|screenLayout|screenSize"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </manifest>
OK,現(xiàn)在可以運(yùn)行一下項(xiàng)目了,程序啟動(dòng)之后就會(huì)自動(dòng)開(kāi)始播放視頻,效果如下圖所示:
這樣我們就把第一步的功能實(shí)現(xiàn)了。
實(shí)現(xiàn)彈幕效果
接下來(lái)我們開(kāi)始實(shí)現(xiàn)彈幕效果。彈幕其實(shí)也就是一個(gè)自定義的View,它的上面可以顯示類(lèi)似于跑馬燈的文字效果。觀眾們發(fā)表的評(píng)論都會(huì)在彈幕上顯示出來(lái),但又會(huì)很快地移出屏幕,既可以起到互動(dòng)的作用,同時(shí)又不會(huì)影響視頻的正常觀看。
我們可以自己來(lái)編寫(xiě)這樣的一個(gè)自定義View,當(dāng)然也可以直接使用網(wǎng)上現(xiàn)成的開(kāi)源項(xiàng)目。那么為了能夠簡(jiǎn)單快速地實(shí)現(xiàn)彈幕效果,這里我就準(zhǔn)備直接使用由嗶哩嗶哩開(kāi)源的彈幕效果庫(kù)DanmakuFlameMaster了。
DanmakuFlameMaster庫(kù)的項(xiàng)目主頁(yè)地址是:https://github.com/Bilibili/DanmakuFlameMaster
話(huà)說(shuō)現(xiàn)在使用Android Studio來(lái)引入一些開(kāi)源庫(kù)真的非常方法,只需要在build.gradle文件里面添加開(kāi)源庫(kù)的依賴(lài)就可以了。那么我們修改app/build.gradle文件,并在dependencies閉包中添加如下依賴(lài):
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:24.2.1' testCompile 'junit:junit:4.12' compile 'com.github.ctiao:DanmakuFlameMaster:0.5.3' }
這樣我們就將DanmakuFlameMaster庫(kù)引入到當(dāng)前項(xiàng)目中了。然后修改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"/> <master.flame.danmaku.ui.widget.DanmakuView android:id="@+id/danmaku_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
可以看到,這里在RelativeLayout中加入了一個(gè)DanmakuView控件,這個(gè)控件就是用于顯示彈幕信息的了。注意一定要將DanmakuView寫(xiě)在VideoView的下面,因?yàn)镽elativeLayout中后添加的控件會(huì)被覆蓋在上面。
接下來(lái)修改MainActivity中的代碼,我們?cè)谶@里加入彈幕顯示的邏輯,如下所示:
public class MainActivity extends AppCompatActivity { private boolean showDanmaku; private DanmakuView danmakuView; private DanmakuContext danmakuContext; 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() + "/Pixels.mp4"); 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); } /** * 向彈幕View中添加一條彈幕 * @param content * 彈幕的具體內(nèi)容 * @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; } danmakuView.addDanmaku(danmaku); } /** * 隨機(jī)生成一些彈幕內(nèi)容以供測(cè)試 */ 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轉(zhuǎn)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; } } ...... }
可以看到,在onCreate()方法中我們先是獲取到了DanmakuView控件的實(shí)例,然后調(diào)用了enableDanmakuDrawingCache()方法來(lái)提升繪制效率,又調(diào)用了setCallback()方法來(lái)設(shè)置回調(diào)函數(shù)。
接著調(diào)用DanmakuContext.create()方法創(chuàng)建了一個(gè)DanmakuContext的實(shí)例,DanmakuContext可以用于對(duì)彈幕的各種全局配置進(jìn)行設(shè)定,如設(shè)置字體、設(shè)置最大顯示行數(shù)等。這里我們并沒(méi)有什么特殊的要求,因此一切都保持默認(rèn)。
另外我們還需要?jiǎng)?chuàng)建一個(gè)彈幕的解析器才行,這里直接創(chuàng)建了一個(gè)全局的BaseDanmakuParser。
有了DanmakuContext和BaseDanmakuParser,接下來(lái)我們就可以調(diào)用DanmakuView的prepare()方法來(lái)進(jìn)行準(zhǔn)備,準(zhǔn)備完成后會(huì)自動(dòng)調(diào)用剛才設(shè)置的回調(diào)函數(shù)中的prepared()方法,然后我們?cè)谶@里再調(diào)用DanmakuView的start()方法,這樣DanmakuView就可以開(kāi)始正常工作了。
雖說(shuō)DanmakuView已經(jīng)在正常工作了,但是屏幕上沒(méi)有任何彈幕信息的話(huà)我們也看不出效果,因此我們還要增加一個(gè)添加彈幕消息的功能。
觀察addDanmaku()方法,這個(gè)方法就是用于向DanmakuView中添加一條彈幕消息的。其中首先調(diào)用了createDanmaku()方法來(lái)創(chuàng)建一個(gè)BaseDanmaku實(shí)例,TYPE_SCROLL_RL表示這是一條從右向左滾動(dòng)的彈幕,然后我們就可以對(duì)彈幕的內(nèi)容、字體大小、顏色、顯示時(shí)間等各種細(xì)節(jié)進(jìn)行配置了。注意addDanmaku()方法中有一個(gè)withBorder參數(shù),這個(gè)參數(shù)用于指定彈幕消息是否帶有邊框,這樣才好將自己發(fā)送的彈幕和別人發(fā)送的彈幕進(jìn)行區(qū)分。
這樣我們就把最基本的彈幕功能就完成了,現(xiàn)在只需要當(dāng)在接收到別人發(fā)送的彈幕消息時(shí),調(diào)用addDanmaku()方法將這條彈幕添加到DanmakuView上就可以了。但接收別人發(fā)送來(lái)的消息又涉及到了即時(shí)通訊技術(shù),顯然這一篇文章中不可能將復(fù)雜的即時(shí)通訊技術(shù)也進(jìn)行講解,因此這里我專(zhuān)門(mén)寫(xiě)了一個(gè)generateSomeDanmaku()方法來(lái)隨機(jī)生成一些彈幕消息,這樣就可以模擬出和斗魚(yú)類(lèi)似的彈幕效果了。
除此之外,我們還需要在onPause()、onResume()、onDestroy()方法中進(jìn)行一些邏輯處理,以保證DanmakuView的資源可以得到釋放。
現(xiàn)在重新運(yùn)行一下程序,效果如下圖所示:
這樣我們就把第二步的功能也實(shí)現(xiàn)了。
加入操作界面
那么下面我們開(kāi)始進(jìn)行第三步功能實(shí)現(xiàn),加入發(fā)送彈幕消息的操作界面。
首先修改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"> ...... <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>
可以看到,這里我們加入了一個(gè)LinearLayout來(lái)作為操作界面。LinearLayout中并沒(méi)有什么復(fù)雜的控件,只有一個(gè)EditText用于輸入內(nèi)容,一個(gè)Button用于發(fā)送彈幕。注意我們一開(kāi)始是將LinearLayout隱藏的,因?yàn)椴荒茏屵@個(gè)操作界面一直遮擋著VideoView,只有用戶(hù)想要發(fā)彈幕的時(shí)候才應(yīng)該將它顯示出來(lái)。
接下來(lái)修改MainActivity中的代碼,在這里面加入發(fā)送彈幕的邏輯,如下所示:
public class MainActivity extends AppCompatActivity { ...... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ...... 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); danmakuView.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); } } }); } ...... }
這里的邏輯還是比較簡(jiǎn)單的,我們先是給DanmakuView設(shè)置了一個(gè)點(diǎn)擊事件,當(dāng)點(diǎn)擊屏幕時(shí)就會(huì)觸發(fā)這個(gè)點(diǎn)擊事件。然后進(jìn)行判斷,如果操作界面是隱藏的就將它顯示出來(lái),如果操作界面是顯示的就將它隱藏掉,這樣就可以簡(jiǎn)單地通過(guò)點(diǎn)擊屏幕來(lái)實(shí)現(xiàn)操作界面的隱藏和顯示了。
接下來(lái)我們又給發(fā)送按鈕注冊(cè)了一個(gè)點(diǎn)擊事件,當(dāng)點(diǎn)擊發(fā)送時(shí),獲取EditText中的輸入內(nèi)容,然后調(diào)用addDanmaku()方法將這條消息添加到DanmakuView上。另外,這條彈幕是由我們自己發(fā)送的,因此addDanmaku()方法的第二個(gè)參數(shù)要傳入true。
最后,由于系統(tǒng)輸入法彈出的時(shí)候會(huì)導(dǎo)致焦點(diǎn)丟失,從而退出沉浸式模式,因此這里還對(duì)系統(tǒng)全局的UI變化進(jìn)行了監(jiān)聽(tīng),保證程序一直可以處于沉浸式模式。
這樣我們就將所有的代碼都完成了,現(xiàn)在可以運(yùn)行一下看看最終效果了。由于電影播放的同時(shí)進(jìn)行GIF截圖生成的文件太大了,無(wú)法上傳,因此這里我是在電影暫停的情況進(jìn)行操作的。效果如下圖所示:
可以看到,我們自己發(fā)送的彈幕是有一個(gè)綠色邊框包圍的,很容易和其他彈幕區(qū)分開(kāi)。
這樣我們就把第三步的功能也實(shí)現(xiàn)了。
雖說(shuō)現(xiàn)在我們已經(jīng)成功實(shí)現(xiàn)了非常不錯(cuò)的彈幕效果,但其實(shí)這只是DanmakuFlameMaster庫(kù)提供的最基本的功能而已。嗶哩嗶哩提供的這個(gè)彈幕開(kāi)源庫(kù)中擁有極其豐富的功能,包含各種不同的彈幕樣式、特效等等。不過(guò)本篇文章的主要目標(biāo)是帶大家了解彈幕效果實(shí)現(xiàn)的思路,并不是要對(duì)DanmakuFlameMaster這個(gè)庫(kù)進(jìn)行全面的解析。如果你對(duì)這個(gè)庫(kù)非常感興趣,可以到它的github主頁(yè)上面去學(xué)習(xí)更多的用法。
以上所述是小編給大家介紹的Android仿斗魚(yú)直播的彈幕效果,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- Android自定義View實(shí)現(xiàn)彈幕效果
- Android雙重SurfaceView實(shí)現(xiàn)彈幕效果
- Android實(shí)現(xiàn)視頻彈幕功能
- Android自制精彩彈幕效果
- Android EasyBarrage實(shí)現(xiàn)輕量級(jí)彈幕效果
- Android編程實(shí)現(xiàn)簡(jiǎn)易彈幕效果示例【附demo源碼下載】
- 很棒的Android彈幕效果實(shí)例
- Android 實(shí)現(xiàn)仿網(wǎng)絡(luò)直播彈幕功能詳解及實(shí)例
- Android實(shí)現(xiàn)炫酷的網(wǎng)絡(luò)直播彈幕功能
- Android彈幕框架 黑暗火焰使基本使用方法
- Android實(shí)現(xiàn)自定義的彈幕效果
- 實(shí)例解析如何在Android應(yīng)用中實(shí)現(xiàn)彈幕動(dòng)畫(huà)效果
- Android簡(jiǎn)單實(shí)現(xiàn)彈幕效果
相關(guān)文章
Android 自定義Button控件實(shí)現(xiàn)按鈕點(diǎn)擊變色
這篇文章給大家介紹了android 自定義Button控件實(shí)現(xiàn)按鈕點(diǎn)擊變色的代碼,本文給大家附有注釋?zhuān)浅2诲e(cuò),代碼簡(jiǎn)單易懂,對(duì)android按鈕點(diǎn)擊變色的實(shí)現(xiàn)感興趣的朋友參考下吧2016-11-11Android編程判斷網(wǎng)絡(luò)連接是否可用的方法
這篇文章主要介紹了Android編程判斷網(wǎng)絡(luò)連接是否可用的方法,實(shí)例分析了Android判定網(wǎng)絡(luò)連接的相關(guān)技巧與實(shí)現(xiàn)步驟,需要的朋友可以參考下2015-12-12Android開(kāi)發(fā)文件存儲(chǔ)實(shí)例
這篇文章主要為大家詳細(xì)介紹了Android開(kāi)發(fā)文件存儲(chǔ)實(shí)例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11Android編程解析XML文件的方法詳解【基于XmlPullParser】
這篇文章主要介紹了Android編程解析XML文件的方法,結(jié)合實(shí)例形式分析了Android基于XmlPullParser解析xml文件的相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2017-07-07Android實(shí)現(xiàn)仿魅族日歷首頁(yè)功能
這篇文章主要介紹了Android實(shí)現(xiàn)仿魅族日歷首頁(yè)功能的實(shí)現(xiàn)過(guò)程以及相關(guān)代碼講解分享,對(duì)此有興趣的朋友參考下。2018-02-02Android Service啟動(dòng)過(guò)程完整分析
這篇文章主要為大家詳細(xì)分析了Android Service啟動(dòng)完整過(guò)程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10Android ScrollView實(shí)現(xiàn)橫向和豎向拖動(dòng)回彈效果
這篇文章主要為大家詳細(xì)介紹了Android ScrollView實(shí)現(xiàn)橫向和豎向拖動(dòng)回彈效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-09-09Android中Fragment相互切換間不被回收的實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于Android中Fragment相互切換間不被回收的實(shí)現(xiàn)方法,文中給出了詳細(xì)的示例代碼和注釋供大家參考學(xué)習(xí),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-08-08