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 弹幕自定义的主要内容,如果未能解决你的问题,请参考以下文章