思路其实很简单,就是假定每条弹幕是一个TextView子类,然后添加到FrameLayout中,然后TextView子类有一个每次移动多少的量,用户可以设置这个数值moveStep控制弹幕的移动快慢。FrameLayout子类中有一个线程无线循环地把TextView子类的X坐标每次减去moveStep的值实现弹幕左移。由于安卓的View默认是OpenGL加速的,所以这套图层添加200个TextView弹幕也只是消耗百分之5的CPU,并不太耗费资源,非常丝滑。可以底层控件播放视频,顶层使用该控件即可做一个简单的弹幕Demo。
理论上,你可以继承ImageView,或者直接继承View实现自定义控件,使得该弹幕可以支持表情弹幕、GIF弹幕等等。这只是我的一种尝试。
一、首先是TextView子类BarrageText,moveStep用于控制弹幕步进像素量
package cjz.project.barrageView;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;
/**
* Created by cjz on 2019/5/7.
*/
public class BarrageText extends TextView{
/**此条弹幕的移动速度**/
private int moveStep = 2;
public BarrageText(Context context) {
super(context);
}
public BarrageText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BarrageText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**此条弹幕的移动速度**/
public int getMoveStep() {
return moveStep;
}
/**设置此条弹幕的移动速度**/
public void setMoveStep(int moveStep) {
this.moveStep = moveStep;
}
}
二、然后是弹幕容器BarrageView,用于控制弹幕移动,自动清理过多的弹幕以免资源消耗尽等等
package cjz.project.barrageView;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
/**
* Created by cjz on 2019/5/7.
*/
public class BarrageView extends FrameLayout {
/**最大弹幕容纳量**/
private int container = 300;
/**宏:触发滑动**/
private final int SWEEP = 0;
public BarrageView(Context context) {
super(context);
}
public BarrageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BarrageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**间接操作UI,匿名内部类覆盖重写一下就够用**/
private class UIHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case SWEEP:
//如果同时存在于屏幕中的弹幕太多了,就清理掉最开始的百分之60
if(getChildCount() > container){
synchronized (this) {
removeViews(0, (int) (container * 0.6));
// removeAllViews();
}
}
//弹幕移动
for(int i = 0; i < getChildCount(); i++){
View view = getChildAt(i);
if(view instanceof BarrageText){
BarrageText barrageText = (BarrageText) view;
barrageText.setX(barrageText.getX() - barrageText.getMoveStep());
if(barrageText.getX() + barrageText.getWidth() < 0){
//Log.i("清理", barrageText.toString());
removeViewAt(i);
i--;
}
}
}
// Log.i("View数量", getChildCount() + "");
break;
}
}
}
private UIHandler handler = new UIHandler();
/**是否保持轮播**/
private boolean run = true;
/**轮播线程**/
private Loop loop = null;
/**轮播线程**/
private class Loop extends Thread {
@Override
public void run() {
super.run();
while (run) {
try {
Thread.sleep(16);
//Log.i("looping", "looping");
handler.sendEmptyMessage(SWEEP);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
loop = null;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//检测本控件的依附情况
this.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
//重新被依附就启动轮播线程
@Override
public void onViewAttachedToWindow(View v) {
run = true;
startLoop();
}
//脱离依附则关闭循环线程
@Override
public void onViewDetachedFromWindow(View v) {
run = false;
if(loop != null && loop.isAlive()){
try {
loop.interrupt();
} catch (Exception e){
e.printStackTrace();
}
}
}
});
}
/**轮播触发**/
private void startLoop() {
//初始化循环
if (loop == null) {
loop = new Loop();
}
if (!loop.isAlive()) {
loop.start();
}
}
/**添加弹幕**/
public void addBarrageText(BarrageText barrageText){
barrageText.setX(getWidth());
addView(barrageText);
startLoop();
}
/**可以容纳多少个弹幕**/
public void setContainer(int container) {
this.container = container;
}
}
三、Demo Activity,添加随机字符作为弹幕,并以随机速度、随机色彩、随机字体大小插入到容器中:
package cjz.project.barrageView;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import java.util.Random;
import cjz.project.maptry.R;
/**
* Created by cjz on 2019/5/7.
*/
public class MainActivity extends Activity {
private BarrageView barrageView;
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
barrageView = findViewById(R.id.barrageView);
final Runnable barrageRunnable = new Runnable() {
final Random random = new Random();
@Override
public void run() {
BarrageText barrageText = new BarrageText(MainActivity.this);
barrageText.setText(getRandomString(random.nextInt(15)));
barrageText.setY(random.nextInt(1080));
barrageText.setTextSize((int)(30 + Math.random()*(100 - 1 + 30)));
barrageText.setMoveStep((int)(30 + Math.random()*(100 - 1 + 30)));
barrageText.setTextColor((0xFF000000 | (random.nextInt(255) & 0xFF) << 16 | (random.nextInt(255) & 0xFF) << 8 | (random.nextInt(255) & 0xFF)));
barrageView.addBarrageText(barrageText);
}
};
new Thread(new Runnable() {
@Override
public void run() {
while(true){
handler.post(barrageRunnable);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
public String getRandomString(int length){
String str="陈杰柱弹幕测试,好好玩哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈";
Random random=new Random();
StringBuffer sb=new StringBuffer();
for(int i=0;i
四、布局文件:
五、测试效果: