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

Android 弹幕自定义

Android 弹幕自定义

Android 自定义弹幕控件

Android 自定义弹幕控件

Android 教你亲手打造酷炫的弹幕效果

Android弹幕实现现状与原理浅析