

兩種實(shí)現(xiàn)彈幕功能的方法,其實(shí)原理上的差別不大,
第一個(gè)方法是小巫見打巫,因?yàn)榈诙椒ㄊ俏以趃ithub
上面集成下來的在功能完善方面是完虐第一個(gè)的,第一個(gè)方法可以說只是拿來大概的了解它,
這個(gè)直播彈幕大概是個(gè)什么東西!!
第一個(gè)的實(shí)現(xiàn)過程:
xml文件:
1.

2.

3.

一共三個(gè)。
Activity的實(shí)現(xiàn):
--------------------------------------------------------------------定義控件--------------------------------------------------------------------
/////彈幕部分
private BarrageViewbv;
int count;
private Buttonbtn_send;
-----------------------------------------------------------------設(shè)置監(jiān)聽--------------------------------------------------------------------

--------------------------------------------------設(shè)置彈幕內(nèi)容,并加入BarrageView布局中-------------------------------------

主要實(shí)現(xiàn)的兩個(gè)類:
-----------------------------------------------------------BarrageLine?----------------------------------------------
package net.ossrs.yasea.demo.Activity.View;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import java.util.concurrent.ConcurrentLinkedQueue;
public class BarrageLine extends FrameLayout
{
private HandlermHandler;
private ConcurrentLinkedQueuemQueue =new ConcurrentLinkedQueue<>();
private int mWidth;
private int PADDING =20;
private int HEIGHT =100;
// /**
// * 統(tǒng)一線程池
// */
// public static Executor mExecutor = Executors.newCachedThreadPool();
? ? public BarrageLine(Context context) {
this(context,null);
}
public BarrageLine(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public BarrageLine(Context context, AttributeSet attrs,int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
///LOOPER用來抽取隊(duì)列里面的東西
? ? ? ? mHandler =new Handler(Looper.getMainLooper());
}
@Override
? ? protected void onAttachedToWindow() {
super.onAttachedToWindow();
flutter();
}
/**
* 設(shè)置一行的寬高
? ? * @param widthMeasureSpec
? ? * @param heightMeasureSpec
? ? */
? ? @Override
? ? protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
this.mWidth = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(mWidth,HEIGHT);
}
/**
* 網(wǎng)隊(duì)列里添加彈幕view
? ? * @param view
? ? */
? ? public void addBarrage(View view){
mQueue.offer(view);
}
private class AddBarrageTaskimplements Runnable{
Viewview;
public AddBarrageTask(View view){
this.view = view;
}
@Override
? ? ? ? public void run() {
mQueue.offer(view);
}
}
/**
* 開始執(zhí)行動畫
*/
? ? private void flutter(){
mHandler.post(mFlutterTask);
}
private RunnablemFlutterTask =new Runnable() {
@Override
? ? ? ? public void run() {
addBarrageView();
moveView();
mHandler.postDelayed(this,5);
}
};
/**
* 判斷每一行是否要添加view
*/
? ? private void addBarrageView() {
if (getChildCount() ==0){
addNextView();
return;
}
View lastChild =this.getChildAt(getChildCount()-1);
int lastChildRight = (int) (lastChild.getTranslationX()+(int)lastChild.getTag());
if (lastChildRight+PADDING>=mWidth)
return;
addNextView();
}
/**
* 給每一行添加view
*/
? ? private void addNextView(){
if (mQueue.isEmpty())
return;
View view =mQueue.poll();
view.measure(0,0);
view.setTag(view.getMeasuredWidth());
addView(view);
view.setTranslationX(mWidth);
}
/**
* 通過handler.post執(zhí)行,形成動畫
*/
? ? private void moveView() {
if (this.getChildCount()==0)
return;
for (int i=0;i
View view =this.getChildAt(i);
view.setTranslationX(view.getTranslationX()-3);
if (view.getTranslationX()+(int)view.getTag()<=0)
removeBarrageView(view);
}
}
/**
* 當(dāng)view移出彈幕行,刪除
? ? * @param view
? ? */
? ? private void removeBarrageView(View view){
view.setVisibility(GONE);
this.removeView(view);
view =null;
}
/**
* 停止發(fā)消息,取消動畫
*/
? ? private void stop(){
if (mHandler!=null)
mHandler.removeCallbacks(mFlutterTask);
}
@Override
? ? protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stop();
}
}
-----------------------------------------------------------BarrageView?----------------------------------------------
package net.ossrs.yasea.demo.Activity.View;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import java.util.ArrayList;
import java.util.Random;
public class BarrageView extends LinearLayout
{
private ArrayListmBarrages =new ArrayList();
private RandommRandom;
public BarrageView(Context context) {
this(context,null);
}
public BarrageView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public BarrageView(Context context, AttributeSet attrs,int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 總共三行彈幕
*/
? ? private void init() {
mRandom =new Random();
setOrientation(LinearLayout.VERTICAL);
for (int i =0; i <3; i++) {
BarrageLine bl =new BarrageLine(getContext());
LinearLayout.LayoutParams param =new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
bl.setLayoutParams(param);
this.addView(bl);
mBarrages.add(bl);
}
}
/**
* 隨機(jī)添加彈幕到某一行
? ? * @param view
? ? */
? ? public void addBarrage(View view) {
mBarrages.get(mRandom.nextInt(3)).addBarrage(view);
}
/**
* 指定添加彈幕到某一行
? ? * @param view
? ? * @param line
? ? */
? ? public void addBarrage(View view,int line){
mBarrages.get(line).addBarrage(view);
}
}
第一個(gè)實(shí)現(xiàn)的大致原理:
先認(rèn)識到這個(gè)方法的奇葩的地方:就是Textview轉(zhuǎn)view那部分,如果他的布局不是用TextView開始的話,那么就會報(bào)錯
Imageview哪里也是一樣的,這個(gè)涉及到View的加載機(jī)制了!有興趣的可以去了解一下,不過可以確定一點(diǎn),就是xml文件的屬性
是通過加載是的第一個(gè)確定的(如果沒想起了解的想法的,可以先這樣認(rèn)為)。
然后這里要將幾個(gè)閱讀別人源碼是的一些小技巧:
開始時(shí)的第一個(gè)眼神該放在它是否繼承了什么還是說他就是一個(gè)簡單的工具類。

比如這個(gè),繼承的是LinearLayout,說明它是在這個(gè)的基礎(chǔ)上打造的,就會擁有它父類的特性
然后,就需要你去看他是如何調(diào)用的了,特別是在看一個(gè)裝了很多功能的模塊的時(shí)候,
因?yàn)槟闶菫榱四稠?xiàng)需求而去看的,為了實(shí)現(xiàn)才去了解,所以你甚至不需要完全的了解它(如果你的時(shí)間真的說可以的話,
那也不是不可以)。

很明顯,我們的起點(diǎn)可以從這里出發(fā)。然后順著它,我們來到了這里

這里我們可以看出addBarrage這個(gè)方法來自mBarrages,而且這個(gè)mBarrages有g(shù)et這個(gè)方法的,說明它可能是個(gè)list集合。
這是就可以得出一個(gè)初步的想法,傳過來的view被mBarrages.addBarrage調(diào)用(而且還是被這個(gè)集合里面的隨機(jī)其中一個(gè)調(diào)用的)。

然后,我們在看看BarrageView,我們的界面控件。因?yàn)槭荁arrageView實(shí)例化后調(diào)用的addgarrage,所以,我們也需要了解

主要看這一行,如果是其他的話,估計(jì)需要找一下(或者全部知道它),因?yàn)檫@里就只有這一行是信息量最大的,如果你提前看了他是繼承什么的,那這個(gè)看起來一
點(diǎn)都不難理解,不過讀的時(shí)候就有點(diǎn)麻煩,因?yàn)槿绻前催@個(gè)步驟來讀,對于BarrageLine的信息是零,所以我們需要去了解一下BarrageLine
最好帶著疑問去,比如:結(jié)合前面的mBarrages.addBarrage方法,所以我們點(diǎn)進(jìn)去后就可以重點(diǎn)關(guān)注這個(gè)方法究竟是咋樣的,這是我們來到了
這里

mQueue.offer(view);很明顯這是個(gè)加入隊(duì)列的方法,這里也解釋了這一點(diǎn)

到這時(shí),我們有需要了解一下BarrageLine是如何工作的了,因?yàn)樗趯?shí)例化之后加入了我們的BarrageView,所以需要了解它。
如果說要哪里入手的話就有點(diǎn)難講了,畢竟我們從上一個(gè)類哪里得到的信息是,那個(gè)集合里面的它取到了一個(gè)view,
而且被循環(huán)了三次之后被加入了
BarrageView,而且,從他的對齊方式可以大致的想到。。。他應(yīng)該是這樣的

接下來可以說方向就是理解這個(gè)view ,他傳進(jìn)去后是如何是BarrageLine起作用的
?

這里指的了解一下的是Looper是負(fù)責(zé)抽取隊(duì)列里面的東西的


?

?

很明顯這里是判斷是否需要addNextView();的

而這個(gè)就已經(jīng)體現(xiàn)了我們view的作用了,他從隊(duì)列里取了出來,這時(shí)view便可以顯示出來了(如果你問它怎么就顯示出來了,其實(shí)是因?yàn)?/b>
addview這個(gè)方法,F(xiàn)ragmentLayout屬于ViewGroup的布局,這就是為什么在所有開始之前先看他究竟繼承了什么。所以他可以調(diào)用
viewgroup方法),其他部分的無非是銷毀view,停止啊,在個(gè)個(gè)周期之內(nèi)需要怎么做的處理。
總結(jié):其實(shí)第一個(gè)方法貫穿這個(gè)功能實(shí)現(xiàn)的整個(gè)過程的就是那個(gè)view,一步一步的順著線索往下找,就可以找到最終它無非是通過BarrageLine
的方法加入隊(duì)列之后被addBarrageView()--》addNextView()取出,然后隨即的出現(xiàn)在三行BarrageView里面的BarrageLine里面而已。