Android 弹幕自定义
Posted 吹着空调哼着歌
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 弹幕自定义相关的知识,希望对你有一定的参考价值。
弹幕(一)自定义无法暂停
根据时间戳和播放器同步弹幕数据
/**
* 一个简化版的DanmakuViewPool
*/
public class CachedDanmakuViewPool implements Pool<DanmakuView>
private static final String TAG = "CachedDanmakuViewPool";
/**
* 缓存DanmakuView队列。显示已经完毕的DanmakuView会被添加到缓存中进行复用。
* 在一定的时间@link CachedDanmakuViewPool#mKeepAliveTime过后,没有被访问到的DanmakuView会被回收。
*/
private LinkedList<DanmakuViewWithExpireTime> mCache = new LinkedList<>();
/**
* 缓存存活时间
*/
private long mKeepAliveTime;
/**
* 定时清理缓存
*/
private ScheduledExecutorService mChecker = Executors.newSingleThreadScheduledExecutor();
/**
* 创建新DanmakuView的Creator
*/
private ViewCreator<DanmakuView> mCreator;
/**
* 最大DanmakuView数量。
* 这个数量包含了正在显示的DanmakuView和已经显示完毕进入缓存等待复用的DanmakuView之和。
*/
private int mMaxSize;
/**
* 正在显示的弹幕数量。
*/
private int mInUseSize;
/**
* @param creator 生成一个DanmakuView
*/
CachedDanmakuViewPool(long keepAliveTime, int maxSize, ViewCreator<DanmakuView> creator)
mKeepAliveTime = keepAliveTime;
mMaxSize = maxSize;
mCreator = creator;
mInUseSize = 0;
scheduleCheckUnusedViews();
/**
* 每隔一秒检查并清理掉空闲队列中超过一定时间没有被使用的DanmakuView
*/
private void scheduleCheckUnusedViews()
mChecker.scheduleWithFixedDelay(() ->
// EasyL.v(TAG, "scheduleCheckUnusedViews: mInUseSize=" + mInUseSize + ", mCacheSize=" + mCache.size());
long current = System.currentTimeMillis();
while (!mCache.isEmpty())
DanmakuViewWithExpireTime first = mCache.getFirst();
if (current > first.expireTime)
mCache.remove(first);
else
break;
, 1000, 1000, TimeUnit.MILLISECONDS);
@Override
public DanmakuView get()
DanmakuView view;
if (mCache.isEmpty()) // 缓存中没有View
if (mInUseSize >= mMaxSize)
return null;
view = mCreator.create();
else // 有可用的缓存,从缓存中取
view = mCache.poll().danmakuView;
view.addOnExitListener(v ->
long expire = System.currentTimeMillis() + mKeepAliveTime;
v.restore();
DanmakuViewWithExpireTime item = new DanmakuViewWithExpireTime();
item.danmakuView = v;
item.expireTime = expire;
mCache.offer(item);
mInUseSize--;
);
mInUseSize++;
return view;
@Override
public void release()
mCache.clear();
/**
* @return 使用中的DanmakuView和缓存中的DanmakuView数量之和
*/
@Override
public int count()
return mCache.size() + mInUseSize;
@Override
public void setMaxSize(int max)
mMaxSize = max;
/**
* 一个包裹类,保存一个DanmakuView和它的过期时间。
*/
private class DanmakuViewWithExpireTime
private DanmakuView danmakuView; // 缓存的DanmakuView
private long expireTime; // 超过这个时间没有被访问的缓存将被丢弃
public interface ViewCreator<T>
T create();
public class Danmaku
public static final String COLOR_WHITE = "#ffffffff";
public static final String COLOR_FFD662 = "#FFD662";
public static final String COLOR_RED = "#ffff0000";
public static final String COLOR_GREEN = "#ff00ff00";
public static final String COLOR_BLUE = "#ff0000ff";
public static final String COLOR_YELLOW = "#ffffff00";
public static final String COLOR_PURPLE = "#ffff00ff";
public static final int DEFAULT_TEXT_SIZE = 24;
public String text;// 文字
public int size = DEFAULT_TEXT_SIZE;// 字号
public Mode mode = Mode.scroll;// 模式:滚动、顶部、底部
public String color = COLOR_WHITE;// 默认白色
public String borderColor = "";// 默认无边框
public enum Mode
scroll, top, bottom
public Danmaku()
public Danmaku(String text, int textSize, Mode mode, String color,String borderColor)
this.text = text;
this.size = textSize;
this.mode = mode;
this.color = color;
if (!TextUtils.isEmpty(borderColor))
this.borderColor = borderColor;
@Override
public String toString()
return "Danmaku" +
"text='" + text + '\\'' +
", size=" + size +
", mode=" + mode +
", color='" + color + '\\'' +
", borderColor='" + borderColor + '\\'' +
'';
/**
* 用法示例:
* DanmakuManager dm = DanmakuManager.getInstance();
* dm.init(getContext());
* dm.show(new Danmaku("test"));
* <p>
* Created by LittleFogCat.
*/
@SuppressWarnings("unused")
public class DanmakuManager
private static final String TAG = DanmakuManager.class.getSimpleName();
private static final int RESULT_OK = 0;
private static final int RESULT_NULL_ROOT_VIEW = 1;
private static final int RESULT_FULL_POOL = 2;
private static final int TOO_MANY_DANMAKU = 2;
private static DanmakuManager sInstance;
/**
* 弹幕容器
*/
WeakReference<FrameLayout> mDanmakuContainer;
/**
* 弹幕池
*/
private Pool<DanmakuView> mDanmakuViewPool;
private Config mConfig;
private DanmakuPositionCalculator mPositionCal;
private Context context;
private FrameLayout container;
private DanmakuManager()
public static DanmakuManager getInstance()
if (sInstance == null)
sInstance = new DanmakuManager();
return sInstance;
/**
* 初始化。在使用之前必须调用该方法。
*/
public void init(Context context, FrameLayout container)
this.context = context;
this.container = container;
if (mDanmakuViewPool == null)
mDanmakuViewPool = new CachedDanmakuViewPool(
60000, // 缓存存活时间:60秒
200, // 最大弹幕数:100
() -> DanmakuViewFactory.createDanmakuView(context, container));
setDanmakuContainer(container);
ScreenUtil.init(context);
mConfig = new Config();
mPositionCal = new DanmakuPositionCalculator(this);
public Config getConfig()
if (mConfig == null)
mConfig = new Config();
return mConfig;
private DanmakuPositionCalculator getPositionCalculator()
if (mPositionCal == null)
mPositionCal = new DanmakuPositionCalculator(this);
return mPositionCal;
public void setDanmakuViewPool(Pool<DanmakuView> pool)
if (mDanmakuViewPool != null)
mDanmakuViewPool.release();
mDanmakuViewPool = pool;
public void clearDmAllMessage()
if (mDanmakuViewPool != null)
mDanmakuViewPool.release();
/**
* 设置允许同时出现最多的弹幕数,如果屏幕上显示的弹幕数超过该数量,那么新出现的弹幕将被丢弃,
* 直到有旧的弹幕消失。
*
* @param max 同时出现的最多弹幕数,-1无限制
*/
public void setMaxDanmakuSize(int max)
if (mDanmakuViewPool == null)
return;
mDanmakuViewPool.setMaxSize(max);
/**
* 设置弹幕的容器,所有的弹幕都在这里面显示
*/
public void setDanmakuContainer(final FrameLayout root)
if (root == null)
// throw new NullPointerException("Danmaku container cannot be null!");
return;
mDanmakuContainer = new WeakReference<>(root);
/**
* 发送一条弹幕
*/
public int send(Danmaku danmaku)
if (mDanmakuViewPool == null)
// throw new NullPointerException("Danmaku view pool is null. Did you call init() first?");
mDanmakuViewPool = new CachedDanmakuViewPool(
60000, // 缓存存活时间:60秒q
200, // 最大弹幕数:100
() -> DanmakuViewFactory.createDanmakuView(context, container));
DanmakuView view = mDanmakuViewPool.get();
if (view == null)
// EasyL.w(TAG, "show: Too many danmaku, discard");
return RESULT_FULL_POOL;
if (mDanmakuContainer == null || mDanmakuContainer.get() == null)
// EasyL.w(TAG, "show: Root view is null.");
return RESULT_NULL_ROOT_VIEW;
view.setDanmaku(danmaku);
// 字体大小
int textSize = danmaku.size;
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
// 字体边框
view.setBorderColor(danmaku.borderColor);
// 字体颜色
try
int color = Color.parseColor(danmaku.color);
view.setTextColor(color);
catch (Exception e)
e.printStackTrace();
view.setTextColor(Color.WHITE);
// 计算弹幕距离顶部的位置
DanmakuPositionCalculator dpc = getPositionCalculator();
int marginTop = dpc.getMarginTop(view);
if (marginTop == -1)
// 屏幕放不下了
// EasyL.d(TAG, "send: screen is full, too many danmaku [" + danmaku + "]");
return TOO_MANY_DANMAKU;
FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) view.getLayoutParams();
if (p == null)
p = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
p.topMargin = marginTop;
view.setLayoutParams(p);
view.setMinHeight((int) (getConfig().getLineHeight() * 1.35));
view.show(mDanmakuContainer.get(), getDisplayDuration(danmaku));
return RESULT_OK;
/**
* @return 返回这个弹幕显示时长
*/
int getDisplayDuration(Danmaku danmaku)
Config config = getConfig();
int duration;
switch (danmaku.mode)
case top:
duration = config.getDurationTop();
break;
case bottom:
duration = config.getDurationBottom();
break;
case scroll:
default:
duration = config.getDurationScroll();
break;
return duration;
/**
* 一些配置
*/
public static class Config
/**
* 行高,单位px
*/
private int lineHeight;
/**
* 滚动弹幕显示时长
*/
private int durationScroll;
/**
* 顶部弹幕显示时长
*/
private int durationTop;
/**
* 底部弹幕的显示时长
*/
private int durationBottom;
/**
* 滚动弹幕的最大行数
*/
private int maxScrollLine;
public int getLineHeight()
return lineHeight;
public void setLineHeight(int lineHeight)
this.lineHeight = lineHeight;
public int getMaxScrollLine()
return maxScrollLine;
public int getDurationScroll()
if (durationScroll == 0)
durationScroll = 10000;
return durationScroll;
public void setDurationScroll(int durationScroll)
this.durationScroll = durationScroll;
public int getDurationTop()
if (durationTop == 0)
durationTop = 5000;
return durationTop;
public void setDurationTop(int durationTop)
this.durationTop = durationTop;
public int getDurationBottom()
if (durationBottom == 0)
durationBottom = 5000;
return durationBottom;
public void setDurationBottom(int durationBottom)
this.durationBottom = durationBottom;
public int getMaxDanmakuLine()
if (maxScrollLine == 0)
maxScrollLine = 12;
return maxScrollLine;
public void setMaxScrollLine(int maxScrollLine)
this.maxScrollLine = maxScrollLine;
/**
* 用于计算弹幕位置,来保证弹幕不重叠又不浪费空间。
*/
class DanmakuPositionCalculator
private static final String TAG = "DanPositionCalculator";
private DanmakuManager mDanmakuManager;
private List<DanmakuView> mLastDanmakus = new ArrayList<>();// 保存每一行最后一个弹幕消失的时间
private boolean[] mTops;
private boolean[] mBottoms;
DanmakuPositionCalculator以上是关于Android 弹幕自定义的主要内容,如果未能解决你的问题,请参考以下文章