弹幕(一)自定义无法暂停
根据时间戳和播放器同步弹幕数据

/*** 一个简化版的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);}@Overridepublic DanmakuView get() {DanmakuView view;if (mCache.isEmpty()) { // 缓存中没有Viewif (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;}@Overridepublic void release() {mCache.clear();}/*** @return 使用中的DanmakuView和缓存中的DanmakuView数量之和*/@Overridepublic int count() {return mCache.size() + mInUseSize;}@Overridepublic void setMaxSize(int max) {mMaxSize = max;}/*** 一个包裹类,保存一个DanmakuView和它的过期时间。*/private class DanmakuViewWithExpireTime {private DanmakuView danmakuView; // 缓存的DanmakuViewprivate 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;}}@Overridepublic 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秒q200, // 最大弹幕数: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(DanmakuManager danmakuManager) {mDanmakuManager = danmakuManager;int maxLine = danmakuManager.getConfig().getMaxDanmakuLine();mTops = new boolean[maxLine];mBottoms = new boolean[maxLine];}private int getLineHeightWithPadding() {return (int) (1.35f * mDanmakuManager.getConfig().getLineHeight());}int getMarginTop(DanmakuView view) {switch (view.getDanmaku().mode) {case scroll:return getScrollY(view);case top:return getTopY(view);case bottom:return getBottomY(view);}return -1;}private int getScrollY(DanmakuView view) {if (mLastDanmakus.size() == 0) {mLastDanmakus.add(view);return 0;}int i;for (i = 0; i < mLastDanmakus.size(); i++) {DanmakuView last = mLastDanmakus.get(i);int timeDisappear = calTimeDisappear(last); // 最后一条弹幕还需多久消失int timeArrive = calTimeArrive(view); // 这条弹幕需要多久到达屏幕边缘boolean isFullyShown = isFullyShown(last);
//            EasyL.d(TAG, "getScrollY: 行: " + i + ", 消失时间: " + timeDisappear + ", 到达时间: " + timeArrive + ", 行高: " + lineHeight);if (timeDisappear <= timeArrive && isFullyShown) {// 如果最后一个弹幕在这个弹幕到达之前消失,并且最后一个字已经显示完毕,// 那么新的弹幕就可以在这一行显示mLastDanmakus.set(i, view);return i * getLineHeightWithPadding();}}int maxLine = mDanmakuManager.getConfig().getMaxDanmakuLine();if (maxLine == 0 || i < maxLine) {mLastDanmakus.add(view);return i * getLineHeightWithPadding();}return -1;}private int getTopY(DanmakuView view) {for (int i = 0; i < mTops.length; i++) {boolean isShowing = mTops[i];if (!isShowing) {final int finalI = i;mTops[finalI] = true;view.addOnExitListener(view1 -> mTops[finalI] = false);return i * getLineHeightWithPadding();}}return -1;}private int getBottomY(DanmakuView view) {for (int i = 0; i < mBottoms.length; i++) {boolean isShowing = mBottoms[i];if (!isShowing) {final int finalI = i;mBottoms[finalI] = true;view.addOnExitListener(view1 -> mBottoms[finalI] = false);return getParentHeight() - (i + 1) * getLineHeightWithPadding();}}return -1;}/*** 这条弹幕是否已经全部出来了。如果没有的话,* 后面的弹幕不能出来,否则就重叠了。*/private boolean isFullyShown(DanmakuView view) {if (view == null) {return true;}int scrollX = view.getScrollX();int textLength = view.getTextLength();return textLength - scrollX < getParentWidth();}/*** 这条弹幕还有多少毫秒彻底消失。*/private int calTimeDisappear(DanmakuView view) {if (view == null) {return 0;}float speed = calSpeed(view);int scrollX = view.getScrollX();int textLength = view.getTextLength();int wayToGo = textLength - scrollX;return (int) (wayToGo / speed);}/*** 这条弹幕还要多少毫秒抵达屏幕边缘。*/private int calTimeArrive(DanmakuView view) {float speed = calSpeed(view);int wayToGo = getParentWidth();return (int) (wayToGo / speed);}/*** 这条弹幕的速度。单位:px/ms*/private float calSpeed(DanmakuView view) {int textLength = view.getTextLength();int width = getParentWidth();float s = textLength + width + 0.0f;int t = mDanmakuManager.getDisplayDuration(view.getDanmaku());return s / t;}private int getParentHeight() {ViewGroup parent = mDanmakuManager.mDanmakuContainer.get();if (parent == null || parent.getHeight() == 0) {return 1080;}return parent.getHeight();}private int getParentWidth() {ViewGroup parent = mDanmakuManager.mDanmakuContainer.get();if (parent == null || parent.getWidth() == 0) {return 1920;}return parent.getWidth();}}
/*** DanmakuView的基类,继承自TextView,一个弹幕对应一个DanmakuView。* 这里实现了一些通用的功能。* <p>* Created by LittleFogCat.*/
@SuppressWarnings("unused")
public class DanmakuView extends TextViewBorder {/*** 弹幕内容*/private Danmaku mDanmaku;/*** 监听*/private ListenerInfo mListenerInfo;private class ListenerInfo {private ArrayList<OnEnterListener> mOnEnterListeners;private List<OnExitListener> mOnExitListener;}/*** 弹幕进场时的监听*/public interface OnEnterListener {void onEnter(DanmakuView view);}/*** 弹幕离场后的监听*/public interface OnExitListener {void onExit(DanmakuView view);}/*** 显示时长 ms*/private int mDuration;public DanmakuView(Context context) {this(context, null);}public DanmakuView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public DanmakuView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}/*** 设置弹幕内容*/public void setDanmaku(Danmaku danmaku) {mDanmaku = danmaku;setText(danmaku.text);switch (danmaku.mode) {case top:case bottom:setGravity(Gravity.CENTER);break;case scroll:default:setGravity(Gravity.START | Gravity.CENTER_VERTICAL);break;}}public void setBorderColor(String borderColor) {if (!TextUtils.isEmpty(borderColor)) {super.setBorderColor(true);} else {super.setBorderColor(false);}}public Danmaku getDanmaku() {return mDanmaku;}/*** 显示弹幕*/public void show(final ViewGroup parent, int duration) {mDuration = duration;switch (mDanmaku.mode) {case top:case bottom:showFixedDanmaku(parent, duration);break;case scroll:default:showScrollDanmaku(parent, duration);break;}if (hasOnEnterListener()) {for (OnEnterListener listener : getListenerInfo().mOnEnterListeners) {listener.onEnter(this);}}postDelayed(() -> {setVisibility(GONE);if (hasOnExitListener()) {for (OnExitListener listener : getListenerInfo().mOnExitListener) {listener.onExit(DanmakuView.this);}}parent.removeView(DanmakuView.this);}, duration);}private void showScrollDanmaku(ViewGroup parent, int duration) {int screenWidth = ScreenUtil.getScreenWidth();int textLength = getTextLength();scrollTo(-screenWidth, 0);parent.addView(this);smoothScrollTo(textLength, 0, duration);}private void showFixedDanmaku(ViewGroup parent, int duration) {setGravity(Gravity.CENTER);parent.addView(this);}private ListenerInfo getListenerInfo() {if (mListenerInfo == null) {mListenerInfo = new ListenerInfo();}return mListenerInfo;}public void addOnEnterListener(OnEnterListener l) {ListenerInfo li = getListenerInfo();if (li.mOnEnterListeners == null) {li.mOnEnterListeners = new ArrayList<>();}if (!li.mOnEnterListeners.contains(l)) {li.mOnEnterListeners.add(l);}}public void clearOnEnterListeners() {ListenerInfo li = getListenerInfo();if (li.mOnEnterListeners == null || li.mOnEnterListeners.size() == 0) {return;}li.mOnEnterListeners.clear();}public void addOnExitListener(OnExitListener l) {ListenerInfo li = getListenerInfo();if (li.mOnExitListener == null) {li.mOnExitListener = new CopyOnWriteArrayList<>();}if (!li.mOnExitListener.contains(l)) {li.mOnExitListener.add(l);}}public void clearOnExitListeners() {ListenerInfo li = getListenerInfo();if (li.mOnExitListener == null || li.mOnExitListener.size() == 0) {return;}li.mOnExitListener.clear();}public boolean hasOnEnterListener() {ListenerInfo li = getListenerInfo();return li.mOnEnterListeners != null && li.mOnEnterListeners.size() != 0;}public boolean hasOnExitListener() {ListenerInfo li = getListenerInfo();return li.mOnExitListener != null && li.mOnExitListener.size() != 0;}public int getTextLength() {return (int) getPaint().measureText(getText().toString());}public int getDuration() {return mDuration;}/*** 恢复初始状态*/public void restore() {clearOnEnterListeners();clearOnExitListeners();setVisibility(VISIBLE);setScrollX(0);setScrollY(0);}private Scroller mScroller;public void smoothScrollTo(int x, int y, int duration) {if (mScroller == null) {mScroller = new Scroller(getContext(), new LinearInterpolator());setScroller(mScroller);}int sx = getScrollX();int sy = getScrollY();mScroller.startScroll(sx, sy, x - sx, y - sy, duration);}@Overridepublic void computeScroll() {if (mScroller != null && mScroller.computeScrollOffset()) {//            EasyL.v(TAG, "computeScroll: " + mScroller.getCurrX());scrollTo(mScroller.getCurrX(), mScroller.getCurrY());postInvalidate();}}void callExitListener() {for (OnExitListener listener : getListenerInfo().mOnExitListener) {listener.onExit(this);}}
}
class DanmakuViewFactory {@SuppressLint("InflateParams")static DanmakuView createDanmakuView(Context context) {return (DanmakuView) LayoutInflater.from(context).inflate(R.layout.danmaku_view, null, false);}static DanmakuView createDanmakuView(Context context, ViewGroup parent) {return (DanmakuView) LayoutInflater.from(context).inflate(R.layout.danmaku_view, parent, false);}
}
public class DanMuView extends FrameLayout {private FrameLayout container;private DanmakuManager dm;private static final int SENDDMLIST = 1001;//    private List<IDanmaku> danmakus = new ArrayList<>();private long currentTime = 0;private HashMap<Integer, List<BulletBean.DataBean>> danmaku = new HashMap<>();public DanMuView(@NonNull Context context) {this(context, null);}public DanMuView(@NonNull Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public DanMuView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context);}public void init(Context context) {View view = LayoutInflater.from(context).inflate(R.layout.danma_layout, this, false);addView(view);container = view.findViewById(R.id.frame_layout);initDanmu(context);}private void initDanmu(Context context) {// 获得DanmakuManager单例dm = DanmakuManager.getInstance();dm.init(context, container);dm.setMaxDanmakuSize(35); // 设置同屏最大弹幕数DanmakuManager.Config config = dm.getConfig(); // 弹幕相关设置config.setDurationScroll(10000); // 设置滚动字幕显示时长,默认10秒config.setMaxScrollLine(3); // 设置滚动字幕最大行数config.setLineHeight(ScreenUtil.autoSize(40)); // 设置行高}public void setDuration(int duration){DanmakuManager.Config config = dm.getConfig(); // 弹幕相关设置config.setDurationScroll(duration); // 设置滚动字幕显示时长,默认10秒}/*** 发送弹幕消息** @param danmaku*/public void sendDmMessage(IDanmaku danmaku) {if (container != null) {dm.setDanmakuContainer(container);}dm.send(danmaku);}/*** 发送弹幕组** @param danmakus*/public void sendDmMessage(HashMap<Integer, List<BulletBean.DataBean>> danmakus) {danmaku.clear();danmaku.putAll(danmakus);Log.e("zrq_dm","*" + danmaku.size() + " - " + danmakus.size());if (container != null) {dm.setDanmakuContainer(container);}}public void clearDmAllMessage() {DanmakuManager.getInstance().clearDmAllMessage();container.removeAllViews();danmaku.clear();}public void release() {clearDmAllMessage();}public void setCurrentTime(String date, long time) {String[] split = date.split(":");List<BulletBean.DataBean> dataBeans = danmaku.get(Integer.parseInt(time / 60 / 1000 + ""));
//        Log.e("zrq_dm"," -- "+danmaku.toString());if (dataBeans == null) {return;}
//        Log.e("zrq_dm","----");int curTime = Integer.parseInt(split[0]) * 60 * 60 + Integer.parseInt(split[1]) * 60 + Integer.parseInt(split[2]);for (int i = 0; i < dataBeans.size(); i++) {BulletBean.DataBean dataBean = dataBeans.get(i);
//            if (Integer.parseInt(split[2]) == dataBean.time && Integer.parseInt(split[1]) == dataBean.minute){if (curTime == dataBean.time) {sendDmMessage(new IDanmaku(0, dataBeans.get(i).content));}}}public void setCurrentTime(long time) {currentTime = time;//对应的分取对应内的所有数据List<BulletBean.DataBean> dataBeans = danmaku.get(Integer.parseInt(currentTime / 60 / 1000 + ""));if (dataBeans == null) {return;}for (int i = 0; i < dataBeans.size(); i++) {long times = (long) dataBeans.get(i).minute * 1000 * 60 + dataBeans.get(i).time * 1000;if (currentTime <= times && times <= currentTime + 1000) {sendDmMessage(new IDanmaku(times, dataBeans.get(i).content));}}
//        for (int i = 0; i < danmakus.size(); i++) {//            if (currentTime <= danmakus.get(i).time && currentTime + 1000 >= danmakus.get(i).time) {//                sendDmMessage(danmakus.get(i));
//                break;
//            }
//        }}}
public class IDanmaku extends Danmaku {public long time;public String text;public int size = Danmaku.DEFAULT_TEXT_SIZE;public IDanmaku(long time,String text,int textSize) {super(text,textSize,Mode.scroll,COLOR_WHITE,"");this.time = time;this.size = textSize;}public IDanmaku(long time,String text,String textColor) {super(text,50,Mode.scroll,textColor,"");this.time = time;this.size = 50;}public IDanmaku(long time,String text,String textColor,String borderColor) {super(text,50,Mode.scroll,textColor,borderColor);this.time = time;this.size = 50;}public IDanmaku(long time,String text) {super(text,50,Mode.scroll,COLOR_WHITE,"");this.time = time;}}
public interface Pool<T> {/*** 从缓存中获取一个T的实例*/T get();/*** 释放缓存*/void release();/*** @return 缓存中T实例的数量*/int count();void setMaxSize(int max);
}
/*** Created by LittleFogCat.* <p>* 自动适配屏幕像素的工具类。* 需要先调用{@link ScreenUtil#init(Context)}才能正常使用。如果屏幕旋转,* 那么需要再次调用{@link ScreenUtil#init(Context)}以更新。*/@SuppressWarnings({"unused", "WeakerAccess", "SuspiciousNameCombination"})
public class ScreenUtil {private static final String TAG = "ScreenUtil";/*** 屏幕宽度,在调用init()之后通过{@link ScreenUtil#getScreenWidth()}获取*/private static int sScreenWidth = 1920;/*** 屏幕高度,在调用init()之后通过{@link ScreenUtil#getScreenHeight()} ()}获取*/private static int sScreenHeight = 1080;/*** 设计宽度。用于{@link ScreenUtil#autoWidth(int)}*/private static int sDesignWidth = 1080;/*** 设计高度。用于{@link ScreenUtil#autoHeight(int)} (int)}*/private static int sDesignHeight = 1920;/*** 初始化ScreenUtil。在屏幕旋转之后,需要再次调用这个方法,否则计算将会出错。*/public static void init(Context context) {DisplayMetrics m = context.getResources().getDisplayMetrics();sScreenWidth = m.widthPixels;sScreenHeight = m.heightPixels;if (sDesignWidth > sDesignHeight != sScreenWidth > sScreenHeight) {int tmp = sDesignWidth;sDesignWidth = sDesignHeight;sDesignHeight = tmp;}}public static void setDesignWidthAndHeight(int width, int height) {sDesignWidth = width;sDesignHeight = height;}/*** 根据实际屏幕和设计图的比例,自动缩放像素大小。* <p>* 例如设计图大小是1920像素x1080像素,实际屏幕是2560像素x1440像素,那么对于一个设计图中100x100像素的方形,* 实际屏幕中将会缩放为133像素x133像素。这有可能导致图形的失真(当实际的横竖比和设计图不同时)** @param origin 设计图上的像素大小* @return 实际屏幕中的尺寸*/public static int autoSize(int origin) {return autoWidth(origin);}/*** 对于在横屏和竖屏下尺寸不同的物体,分别给出设计图的像素,返回实际屏幕中应有的像素。** @param land 在横屏设计图中的像素大小* @param port 在竖屏设计图中的像素大小*/public static int autoSize(int land, int port) {return isPortrait() ? autoSize(port) : autoSize(land);}/*** 根据屏幕分辨率自适应宽度。** @param origin 设计图中的宽度,像素* @return 实际屏幕中的宽度,像素*/public static int autoWidth(int origin) {if (sScreenWidth == 0 || sDesignWidth == 0) {return origin;}int autoSize = origin * sScreenWidth / sDesignWidth;if (origin != 0 && autoSize == 0) {return 1;}return autoSize;}/*** 根据屏幕分辨率自适应高度** @param origin 设计图中的高度,像素* @return 实际屏幕中的高度,像素*/public static int autoHeight(int origin) {if (sScreenHeight == 0 || sDesignHeight == 0) {return origin;}int auto = origin * sScreenHeight / sDesignHeight;if (origin != 0 && auto == 0) {return 1;}return auto;}public static int getScreenWidth() {return sScreenWidth;}public static void setScreenWidth(int w) {sScreenWidth = w;}public static int getScreenHeight() {return sScreenHeight;}public static void setScreenHeight(int h) {sScreenHeight = h;}/*** 是否是竖屏*/public static boolean isPortrait() {return getScreenHeight() > getScreenWidth();}}

Android 弹幕(一)自定义相关推荐

  1. Android气泡弹幕,Android弹幕实现:基于B站弹幕开源系统(7)QQ、微信聊天气泡样式的弹幕...

    Android弹幕实现:基于B站弹幕开源系统(7)QQ.微信聊天气泡样式的弹幕 在附录文章得基础上,改进普通文本弹幕,实现一种特殊效果的文本弹幕,像QQ.微信一样的带有气泡背景的弹幕.实现的重点是在S ...

  2. android 自定义dialog 定时关闭,Android编程实现自定义Dialog的大小自动控制方法示例...

    本文实例讲述了Android编程实现自定义Dialog的大小自动控制方法.分享给大家供大家参考,具体如下: Android应用开发中,无论是出于功能还是增加用户体验,弹出对话框(Dialog)进行一些 ...

  3. 如何在android中创建自定义对话框?

    本文翻译自:How to create a Custom Dialog box in android? I want to create a custom dialog box like below ...

  4. 针对Android Tv的自定义RecyclerView2 0横竖向连动

    版权声明:本文为博主原创文章,转载请注明出处. 推荐: 欢迎关注我创建的Android TV 简书专题,会定期给大家分享一些AndroidTv相关的内容: www.jianshu.com/c/37ef ...

  5. android 自定义actionbar前面有一块空白,解决Android V7后自定义Toolbar、ActionBar左侧有空白问题...

    如图所示: 1.查看Wiget.AppCompat.Toolbar的parent主题,如下所示: @style/TextAppearance.Widget.AppCompat.Toolbar.Titl ...

  6. android标尺自定义view,android尺子的自定义view——RulerView详解

    项目中用到自定义尺子的样式: 原效果为 因为跟自己要使用的view稍有不同 所以做了一些修改,修改的注释都放在代码中了,特此记录一下. 首先是一个自定义View: public class RuleV ...

  7. android 继承dialog自定义对话框

    android  继承dialog自定义对话框 样式如下: 代码实现: 主要Activity: public class MainActivity extends Activity { Button ...

  8. android如何让自定义控件居中,Android自定义控件之自定义TextView,实现drawableLeft可以和文字一起居中...

    如何实现使用TextView的DrawableLeft使图片和文字居中显示呢??? 代码如下: 1.首先自定义一个类,继承TextViewpackage com.test.signcalendar.w ...

  9. Android自定义控件之自定义时钟

    Android自定义控件之自定义时钟 这个是我从别的开源项目中挖出来的,真心写的很不错,然后继续下来以便不时之需,直接上代码: WatcherBoard.java这个是自定义的时钟类 package ...

  10. android 自定义menu背景,Android编程实现自定义系统菜单背景的方法

    本文实例讲述了Android编程实现自定义系统菜单背景的方法.分享给大家供大家参考,具体如下: 不多说,上图,见代码. package lab.sodino.menutest; import andr ...

最新文章

  1. RabbitMQ (三)消息重试
  2. 帝国Cms批量上传多图morepic上传超过最大文件2m的限制的方法
  3. 最严谨的计算机语言p,用于PLC的华P语言编译器设计及实现.pdf
  4. [python交互]Excel催化剂与python交互原理剖析,py开发者按此规范可自行扩展功能...
  5. Java 大小写转换
  6. 清除mysql数据碎片_MySQL 清除表空间碎片方法总结
  7. Spring Boot Mybatis简单使用
  8. 如何下载安装Photoshop cs 6(供新手)
  9. 怎么得到hid设备名_上海海关旧设备进口清关公司这个不错
  10. 天龙八部linux 换win,Linux从菜鸟到大师之天龙八部 第三部文本编辑处理.doc
  11. rootkit检测、病毒查杀、webshell检测
  12. hdu 5025 Saving Tang Monk 状态压缩dp+广搜
  13. 360与腾讯之争——顾客有可能成为上帝
  14. optaplanner学习笔记(六)使用Solver求解器
  15. 面试一次问一次,HashMap是该拿下了(一)
  16. 一个标星 5.2k+ 的牛逼开源商城系统
  17. 微信公众号数据2019_2019年微信公众号文章数据报告
  18. 【程序设计基础 学习笔记】单向链表(TBC)
  19. 自然语言处理实战——LSTM
  20. 华硕wl 700ge 路由器 刷OpenWrt 过程记录

热门文章

  1. 设计一套前端项目组织结构
  2. java mpeg ps转mpeg-4_MPEG4格式转换器下载_枫叶MPEG4格式转换器(MPEG转换器) 9.0.5.0 共享版_极速下载站...
  3. linux mipi接口驱动,rp3399之mipi接口ov4689摄像头驱动
  4. Linux 摄像头驱动
  5. 用于细粒度图像分类的通道交互网络
  6. CodeForces-831A-Unimodal Array (水题)
  7. 如何找到生日中阴阳历重合的年份
  8. Mac/Linux下使用vim出现E45: ‘readonly‘ option is set (add ! to override)错误
  9. 数字图像处理 自学笔记 (武汉大学 贾永红)
  10. 现场总线与计算机网络的最大区别,现场总线与局域网的区别是什么