两种实现弹幕功能的方法,其实原理上的差别不大,
第一个方法是小巫见打巫,因为第二方法是我在github
上面集成下来的在功能完善方面是完虐第一个的,第一个方法可以说只是拿来大概的了解它,
这个直播弹幕大概是个什么东西!!
第一个的实现过程:
xml文件:
1.
2.
3.
一共三个。
Activity的实现:
--------------------------------------------------------------------定义控件--------------------------------------------------------------------
/////弹幕部分
private BarrageViewbv;
int count;
private Buttonbtn_send;
-----------------------------------------------------------------设置监听--------------------------------------------------------------------
--------------------------------------------------设置弹幕内容,并加入BarrageView布局中-------------------------------------
主要实现的两个类:
-----------------------------------------------------------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;
// /**
// * 统一线程池
// */
// 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用来抽取队列里面的东西
mHandler =new Handler(Looper.getMainLooper());
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
flutter();
}
/**
* 设置一行的宽高
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
this.mWidth = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(mWidth,HEIGHT);
}
/**
* 网队列里添加弹幕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);
}
}
/**
* 开始执行动画
*/
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执行,形成动画
*/
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);
}
}
/**
* 当view移出弹幕行,删除
* @param view
*/
private void removeBarrageView(View view){
view.setVisibility(GONE);
this.removeView(view);
view =null;
}
/**
* 停止发消息,取消动画
*/
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);
}
}
/**
* 随机添加弹幕到某一行
* @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);
}
}
第一个实现的大致原理:
先认识到这个方法的奇葩的地方:就是Textview转view那部分,如果他的布局不是用TextView开始的话,那么就会报错
Imageview哪里也是一样的,这个涉及到View的加载机制了!有兴趣的可以去了解一下,不过可以确定一点,就是xml文件的属性
是通过加载是的第一个确定的(如果没想起了解的想法的,可以先这样认为)。
然后这里要将几个阅读别人源码是的一些小技巧:
开始时的第一个眼神该放在它是否继承了什么还是说他就是一个简单的工具类。
比如这个,继承的是LinearLayout,说明它是在这个的基础上打造的,就会拥有它父类的特性
然后,就需要你去看他是如何调用的了,特别是在看一个装了很多功能的模块的时候,
因为你是为了某项需求而去看的,为了实现才去了解,所以你甚至不需要完全的了解它(如果你的时间真的说可以的话,
那也不是不可以)。
很明显,我们的起点可以从这里出发。然后顺着它,我们来到了这里
这里我们可以看出addBarrage这个方法来自mBarrages,而且这个mBarrages有get这个方法的,说明它可能是个list集合。
这是就可以得出一个初步的想法,传过来的view被mBarrages.addBarrage调用(而且还是被这个集合里面的随机其中一个调用的)。
然后,我们在看看BarrageView,我们的界面控件。因为是BarrageView实例化后调用的addgarrage,所以,我们也需要了解
主要看这一行,如果是其他的话,估计需要找一下(或者全部知道它),因为这里就只有这一行是信息量最大的,如果你提前看了他是继承什么的,那这个看起来一
点都不难理解,不过读的时候就有点麻烦,因为如果是按这个步骤来读,对于BarrageLine的信息是零,所以我们需要去了解一下BarrageLine
最好带着疑问去,比如:结合前面的mBarrages.addBarrage方法,所以我们点进去后就可以重点关注这个方法究竟是咋样的,这是我们来到了
这里
mQueue.offer(view);很明显这是个加入队列的方法,这里也解释了这一点
到这时,我们有需要了解一下BarrageLine是如何工作的了,因为他在实例化之后加入了我们的BarrageView,所以需要了解它。
如果说要哪里入手的话就有点难讲了,毕竟我们从上一个类哪里得到的信息是,那个集合里面的它取到了一个view,
而且被循环了三次之后被加入了
BarrageView,而且,从他的对齐方式可以大致的想到。。。他应该是这样的
接下来可以说方向就是理解这个view ,他传进去后是如何是BarrageLine起作用的
这里指的了解一下的是Looper是负责抽取队列里面的东西的
很明显这里是判断是否需要addNextView();的
而这个就已经体现了我们view的作用了,他从队列里取了出来,这时view便可以显示出来了(如果你问它怎么就显示出来了,其实是因为
addview这个方法,FragmentLayout属于ViewGroup的布局,这就是为什么在所有开始之前先看他究竟继承了什么。所以他可以调用
viewgroup方法),其他部分的无非是销毁view,停止啊,在个个周期之内需要怎么做的处理。
总结:其实第一个方法贯穿这个功能实现的整个过程的就是那个view,一步一步的顺着线索往下找,就可以找到最终它无非是通过BarrageLine
的方法加入队列之后被addBarrageView()--》addNextView()取出,然后随即的出现在三行BarrageView里面的BarrageLine里面而已。