Android仿网络直播弹幕功能的实现
现在网络直播越来越火,网络主播也逐渐成为一种新兴职业,对于网络直播,弹幕功能是必须要有的,如下图:
首先来分析一下,这个弹幕功能是怎么实现的,首先在最下面肯定是一个游戏界面View,然后游戏界面上有弹幕View,弹幕的View必须要做成完全透明的,这样即使覆盖在游戏界面的上方也不会影响到游戏的正常观看,只有当有人发弹幕消息时,再将消息绘制到弹幕的View上面就可以了,下方肯定还有有操作界面View,可以让用户来发弹幕和送礼物的功能,原理示意图如下所示:
参照原理图,下面一步一步来实现这个功能。
实现视频的播放
activity_main.xml
[html] view plain copy
1.
7.
8. 13.
MainActivity.java
[java] view plain copy
1. package com.jackie.bombscreen;
2.
3. import android.os.Build;
4. import android.os.Bundle;
5. import android.os.Environment;
6. import android.support.v7.app.AppCompatActivity; 7. import android.view.View;
8. import android.widget.VideoView;
9.
10. public class MainActivity extends AppCompatActivity { 11. @Override
12. protected void onCreate(Bundle savedInstanceState) { 13. super.onCreate(savedInstanceState); 14. setContentView(R.layout.activity_main); 15. VideoView videoView = (VideoView) findViewById(R.id.video_view);
16. videoView.setVideoPath(Environment.getExternalStorageDirectory() + "/xiaoxingyun.mp4");
17. videoView.start();
18. }
19.
20. @Override
21. public void onWindowFocusChanged(boolean hasFocus) { 22. super.onWindowFocusChanged(hasFocus); 23. if (hasFocus && Build.VERSION.SDK_INT >= 19) { 24. View decorView = getWindow().getDecorView();
25. decorView.setSystemUiVisibility(
26. View.SYSTEM_UI_FLAG_LAYOUT_STABLE 27. | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 28. | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 29. | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 30. | View.SYSTEM_UI_FLAG_FULLSCREEN 31. | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); 32. }
33. }
34. }
最后别忘了设置AndroidMainfest.xml
效果如下:
实现弹幕的效果
接下来我们开始实现弹幕效果。弹幕其实也就是一个自定义的View,它的上面可以显示类似于跑马灯的文字效果。观众们发
的评论都会在弹幕上显示出来,但又会很快地移出屏幕,既可以起到互动的作用,同时又不会影响视频的正常观看。
我们可以自己来编写这样的一个自定义View,当然也可以直接使用网上现成的开源项目。那么为了能够简单快速地实现弹幕效果,这里我就准备直接使用由哔哩哔哩开源的弹幕效果库DanmakuFlameMaster。
DanmakuFlameMaster库的项目主页地址是:
添加build.gradle依赖
compile 'com.github.ctiao:DanmakuFlameMaster:0.5.3'
[html] view plain copy
1. 2.
8.
9. 14.
15. 19.
修改MainActivity.java
[java] view plain copy
1. package com.jackie.bombscreen;
2.
3. import android.graphics.Color;
4. import android.os.Build;
5. import android.os.Bundle;
6. import android.os.Environment;
7. import android.support.v7.app.AppCompatActivity; 8. import android.view.View;
9. import android.widget.VideoView;
10.
11. import java.util.Random;
12.
13. import master.flame.danmaku.controller.DrawHandler; 14. import master.flame.danmaku.danmaku.model.BaseDanmaku; 15. import master.flame.danmaku.danmaku.model.DanmakuTimer;
16. import master.flame.danmaku.danmaku.model.IDanmakus; 17. import master.flame.danmaku.danmaku.model.android.DanmakuContext; 18. import master.flame.danmaku.danmaku.model.android.Danmakus; 19. import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; 20. import master.flame.danmaku.ui.widget.DanmakuView; 21.
22. public class MainActivity extends AppCompatActivity { 23. private boolean mIsShowDanmaku;
24. private DanmakuView mDanmakuView;
25. private DanmakuContext mDanmakuContext;
26.
27. private BaseDanmakuParser parser = new BaseDanmakuParser() { 28. @Override
29. protected IDanmakus parse() {
30. return new Danmakus();
31. }
32. };
33.
34. @Override
35. protected void onCreate(Bundle savedInstanceState) { 36. super.onCreate(savedInstanceState);
37. setContentView(R.layout.activity_main); 38. VideoView videoView = (VideoView) findViewById(R.id.video_view); 39. videoView.setVideoPath(Environment.getExternalStorageDirectory() + "/xiaoxingyun.mp4");
40. videoView.start();
41.
42. mDanmakuView = (DanmakuView) findViewById(R.id.danmaku_view); 43. mDanmakuView.enableDanmakuDrawingCache(true); 44. mDanmakuView.setCallback(new DrawHandler.Callback() { 45. @Override
46. public void prepared() {
47. mIsShowDanmaku = true;
48. mDanmakuView.start();
49. generateSomeDanmaku();
50. }
51.
52. @Override
53. public void updateTimer(DanmakuTimer timer) { 54.
55. }
56.
57. @Override
58. public void danmakuShown(BaseDanmaku danmaku) { 59.
60. }
61.
62. @Override
63. public void drawingFinished() { 64.
65. }
66. });
67.
68. mDanmakuContext = DanmakuContext.create(); 69. mDanmakuView.prepare(parser, mDanmakuContext); 70. }
71.
72. /**
73. * 向弹幕View中添加一条弹幕
74. * @param content 弹幕的具体内容 75. * @param withBorder 弹幕是否有边框 76. */
77. private void addDanmaku(String content, boolean withBorder) {
78. BaseDanmaku danmaku = mDanmakuContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
79. danmaku.text = content;
80. danmaku.padding = 5;
81. danmaku.textSize = sp2px(20);
82. danmaku.textColor = Color.WHITE; 83. danmaku.setTime(mDanmakuView.getCurrentTime()); 84. if (withBorder) {
85. danmaku.borderColor = Color.GREEN; 86. }
87. mDanmakuView.addDanmaku(danmaku); 88. }
89.
90. /**
91. * 随机生成一些弹幕内容以供测试
92. */
93. private void generateSomeDanmaku() { 94. new Thread(new Runnable() {
95. @Override
96. public void run() {
97. while(mIsShowDanmaku) { 98. int time = new Random().nextInt(300); 99. String content = "" + time + time; 100. addDanmaku(content, false); 101. try {
102. Thread.sleep(time); 103. } catch (InterruptedException e) {
104. e.printStackTrace(); 105. }
106. }
107. }
108. }).start();
109. }
110.
111. /**
112. * sp转px的方法。
113. */
114. public int sp2px(float spValue) { 115. final float fontScale = getResources().getDisplayMetrics().scaledDensity;
116. return (int) (spValue * fontScale + 0.5f);
117. }
118.
119. @Override
120. protected void onPause() { 121. super.onPause();
122. if (mDanmakuView != null && mDanmakuView.isPrepared()) {
123. mDanmakuView.pause(); 124. }
125. }
126.
127. @Override
128. protected void onResume() { 129. super.onResume();
130. if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) {
131. mDanmakuView.resume(); 132. }
133. }
134.
135. @Override
136. protected void onDestroy() { 137. super.onDestroy();
138. mIsShowDanmaku = false; 139. if (mDanmakuView != null) { 140. mDanmakuView.release(); 141. mDanmakuView = null; 142. }
143. }
144.
145. @Override
146. public void onWindowFocusChanged(boolean hasFocus) {
147. super.onWindowFocusChwww.baiyuewang.netanged(hasFocus);
148. if (hasFocus && Build.VERSION.SDK_INT >= 19) { 149. View decorView = getWindow().getDecorView(); 150. decorView.setSystemUiVisibility(
151. View.SYSTEM_UI_FLAG_LAYOUT_STABLE 152. | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 153. | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 154. | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 155. | View.SYSTEM_UI_FLAG_FULLSCREEN 156. | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); 157. }
158. }
159. }
效果图如下:
加入操作界面
[html] view plain copy
1. 2.
8.
9. 14.
15. 19.
20.
27.
28. 33.
34.
39.
40.
[java] view plain copy
1. package com.jackie.bombscreen;
2.
3. import android.graphics.Color;
4. import android.os.Build;
5. import android.os.Bundle;
6. import android.os.Environment;
7. import android.support.v7.app.AppCompatActivity; 8. import android.text.TextUtils;
9. import android.view.View;
10. import android.widget.Button;
11. import android.widget.EditText;
12. import android.widget.LinearLayout;
13. import android.widget.VideoView;
14.
15. import java.util.Random;
16.
17. import master.flame.danmaku.controller.DrawHandler; 18. import master.flame.danmaku.danmaku.model.BaseDanmaku; 19. import master.flame.danmaku.danmaku.model.DanmakuTimer; 20. import master.flame.danmaku.danmaku.model.IDanmakus; 21. import master.flame.danmaku.danmaku.model.android.DanmakuContext; 22. import master.flame.danmaku.danmaku.model.android.Danmakus; 23. import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; 24. import master.flame.danmaku.ui.widget.DanmakuView; 25.
26. public class MainActivity extends AppCompatActivity { 27. private boolean mIsShowDanmaku;
28. private DanmakuView mDanmakuView;
29. private DanmakuContext mDanmakuContext; 30.
31. private BaseDanmakuParser parser = new BaseDanmakuParser() { 32. @Override
33. protected IDanmakus parse() {
34. return new Danmakus();
35. }
36. };
37.
38. @Override
39. protected void onCreate(Bundle savedInstanceState) { 40. super.onCreate(savedInstanceState);
41. setContentView(R.layout.activity_main);
42. VideoView videoView = (VideoView) findViewById(R.id.video_view); 43. videoView.setVideoPath(Environment.getExternalStorageDirectory() + "/xiaoxingyun.mp4");
44. videoView.start();
45.
46. mDanmakuView = (DanmakuView) findViewById(R.id.danmaku_view); 47. mDanmakuView.enableDanmakuDwww.tt951.comrawingCache(true); 48. mDanmakuView.setCallback(new DrawHandler.Callback() { 49. @Override
50. public void prepared() {
51. mIsShowDanmaku = true;
52. mDanmakuView.start();
53. generateSomeDanmaku();
54. }
55.
56. @Override
57. public void updateTimer(DanmakuTimer timer) { 58.
59. }
60.
61. @Override
62. public void danmakuShown(BaseDanmaku danmaku) { 63.
64. }
65.
66. @Override
67. public void drawingFinished() {
68.
69. }
70. });
71.
72. mDanmakuContext = DanmakuContext.create();
73. mDanmakuView.prepare(parser, mDanmakuContext); 74.
75. final LinearLayout operationLayout = (LinearLayout) findViewById(R.id.operation_layout);
76. final Button send = (Button) findViewById(R.id.send); 77. final EditText editText = (EditText) findViewById(R.id.edit_text); 78. mDanmakuView.setOnClickListener(new View.OnClickListener() { 79. @Override
80. public void onClick(View view) {
81. if (operationLayout.getVisibility() == View.GONE) { 82. operationLayout.setVisibility(View.VISIBLE); 83. } else {
84. operationLayout.setVisibility(View.GONE);
85. }
86. }
87. });
88.
89. send.setOnClickListener(new View.OnClickListener() { 90. @Override
91. public void onClick(View view) { 92. String content = editText.getText().toString(); 93. if (!TextUtils.isEmpty(content)) { 94. addDanmaku(content, true); 95. editText.setText("");
96. }
97. }
98. });
99.
100. getWindow().getDecorView().setOnSystemUiVisibilityChangeListener (new View.OnSystemUiVisibilityChangeListener() {
101. @Override
102. public void onSystemUiVisibilityChange(int visibility) { 103. if (visibility == View.SYSTEM_UI_FLAG_VISIBLE) { 104. onWindowFocusChanged(true); 105. }
106. }
107. });
108. }
109.
110. /**
111. * 向弹幕View中添加一条弹幕
112. * @param content 弹幕的具体内容
113. * @param withBorder 弹幕是否有边框
114. */
115. private void addDanmaku(String content, boolean withBorder) { 116. BaseDanmaku danmaku = mDanmakuContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
117. danmaku.text = content;
118. danmaku.padding = 5;
119. danmaku.textSize = sp2px(20);
120. danmaku.textColor = Color.WHITE;
121. danmaku.setTime(mDanmakuView.getCurrentTime()); 122. if (withBorder) {
123. danmaku.borderColor = Color.GREEN; 124. }
125. mDanmakuView.addDanmaku(danmaku);
126. }
127.
128. /**
129. * 随机生成一些弹幕内容以供测试
130. */
131. private void generateSomeDanmaku() { 132. new Thread(new Runnable() { 133. @Override
134. public void run() {
135. while(mIsShowDanmaku) { 136. int time = new Random().nextInt(300); 137. String content = "" + time + time; 138. addDanmaku(content, false); 139. try {
140. Thread.sleep(time); 141. } catch (InterruptedException e) { 142. e.printStackTrace(); 143. }
144. }
145. }
146. }).start();
147. }
148.
149. /**
150. * sp转px的方法。
151. */
152. public int sp2px(float spValue) { 153. final float fontScale = getResources().getDisplayMetrics().scaledDensity;
154. return (int) (spValue * fontScale + 0.5f); 155. }
156.
157. @Override
158. protected void onPause() {
159. super.onPause();
160. if (mDanmakuView != null && mDanmakuView.isPrepared()) {
161. mDanmakuView.pause();
162. }
163. }
164.
165. @Override
166. protected void onResume() {
167. super.onResume();
168. if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) {
169. mDanmakuView.resume();
170. }
171. }
172.
173. @Override
174. protected void onDestroy() {
175. super.onDestroy();
176. mIsShowDanmaku = false;
177. if (mDanmakuView != null) {
178. mDanmakuView.release();
179. mDanmakuView = null;
180. }
181. }
182.
183.
184. @Override
185. public void onWindowFocusChanged(boolean hasFocus) { 186. super.onWindowFocusChanged(hasFocus);
187. if (hasFocus && Build.VERSION.SDK_INT >= 19) { 188. View decorView = getWindow().getDecorView(); 189. decorView.setSystemUiVisibility(
190. View.SYSTEM_UI_FLAG_LAYOUT_STABLE 191. | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 192. | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 193. | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 194. | View.SYSTEM_UI_FLAG_FULLSCREEN 195. | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); 196. }
197. }
198. }