安卓实时弹幕demo(一)弹幕效果
来源:互联网 发布:英语听力怎么练 知乎 编辑:程序博客网 时间:2024/05/16 12:51
//////////2016/08/03///////////
/////////by XBW///////////////
/////////android studio//////
先上图,看效果
DanmakuItem.java
package com.xbw.danmu.danmu.opendanmaku;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.text.Layout;import android.text.SpannableString;import android.text.StaticLayout;import android.text.TextPaint;import java.util.Random;public class DanmakuItem implements IDanmakuItem { /** X axis base speed*/ private static int sBaseSpeed = 3; private Context mContext; /**DanmakuView width, height*/ private int mContainerWidth, mContainerHeight; private int mTextSize; private int mTextColor = Color.WHITE; private SpannableString mContent; private int mCurrX, mCurrY; /** X axis speed factor*/ private float mFactor; private StaticLayout staticLayout; private StaticLayout borderStaticLayout; private static TextPaint strokePaint = new TextPaint(); private int mContentWidth , mContentHeight; static { strokePaint.setARGB(255, 0, 0, 0);// strokePaint.setTextAlign(Paint.Align.CENTER);// strokePaint.setTextSize(16);// strokePaint.setTypeface(Typeface.DEFAULT_BOLD); strokePaint.setStyle(Paint.Style.STROKE); strokePaint.setStrokeWidth(4); strokePaint.setAntiAlias(true); } /** * construct a DanmakuItem * @param context Context * @param content paint text as content * @param startX start position of X axis, * normally should be the screen width, e.g. right side of the view). * the Y axis position will be assigned a channel by the DanmakuView randomly. */ public DanmakuItem(Context context, CharSequence content, int startX) { this(context, new SpannableString(content), startX, 0, 0, 0, 1f); } public DanmakuItem(Context context, CharSequence content, int startX, int startY) { this(context, new SpannableString(content), startX, startY, 0, 0, 1f); } public DanmakuItem(Context context, SpannableString content, int startX, int startY, int textColorResId, int textSizeInDip, float speedFactor) { this.mContext = context; this.mContent = content; this.mCurrX = startX; this.mCurrY = startY; setTextColor(textColorResId); setTextSize(textSizeInDip); mFactor = speedFactor; measure(); } private void measure() { TextPaint tp = new TextPaint(); tp.setAntiAlias(true); tp.setColor(mTextColor); tp.setTextSize(mTextSize); strokePaint.setTextSize(mTextSize);// tp.setShadowLayer(4, 0, 0, Color.BLACK); mContentHeight = getFontHeight(tp); staticLayout = new StaticLayout(mContent, tp, (int) Layout.getDesiredWidth(mContent, 0, mContent.length(), tp) + 1, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); mContentWidth = staticLayout.getWidth(); borderStaticLayout = new StaticLayout(mContent, strokePaint, (int) Layout.getDesiredWidth(mContent, 0, mContent.length(), tp) + 1, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } @Override public void doDraw(Canvas canvas) { int canvasWidth = canvas.getWidth(); int canvasHeight = canvas.getHeight(); if (canvasWidth != this.mContainerWidth || canvasHeight != this.mContainerHeight) {//phone rotated ! this.mContainerWidth = canvasWidth; this.mContainerHeight = canvasHeight; } canvas.save(); canvas.translate(mCurrX,mCurrY);// for (int i = 0; i < 4; i++) { //加深阴影,产生描边效果. stroke/outline effect// staticLayout.draw(canvas);// } borderStaticLayout.draw(canvas); staticLayout.draw(canvas); canvas.restore(); mCurrX = (int) (mCurrX - sBaseSpeed * mFactor);//only support moving along X axis } @Override public void setTextSize(int textSizeInDip) { if (textSizeInDip > 0) { this.mTextSize = dip2px(mContext, textSizeInDip); measure(); } else { this.mTextSize = dip2px(mContext, 14); // textSize default to 12 dp } } @Override public void setTextColor(int textColorResId) { int[] colorss= {Color.RED, Color.BLACK, Color.BLUE, Color.WHITE, Color.YELLOW, Color.CYAN, Color.DKGRAY, Color.GRAY, Color.GREEN, Color.LTGRAY, Color.MAGENTA}; Random random = new Random(); int p = random.nextInt(colorss.length); if (textColorResId > 0) { this.mTextColor = colorss[p]; measure(); } } @Override public void setStartPosition(int x, int y) { this.mCurrX = x; this.mCurrY = y; } @Override public void setSpeedFactor(float factor) { this.mFactor = factor; } @Override public float getSpeedFactor() { return mFactor; } @Override public boolean isOut() { return mCurrX < 0 && Math.abs(mCurrX) > mContentWidth; } @Override public void release() { mContext = null; } @Override public int getWidth() { return mContentWidth; } @Override public int getHeight() { return mContentHeight; } @Override public int getCurrX() { return mCurrX; } @Override public int getCurrY() { return mCurrY; } /*** * test whether this Danmaku Item would be hit the already running one or not * if it to be run on the same channel * @param runningItem item is already moving on the channel * @return hit or not */ public boolean willHit(IDanmakuItem runningItem) { if (runningItem.getWidth() + runningItem.getCurrX() > mContainerWidth) { return true; } if (runningItem.getSpeedFactor()>= mFactor) { return false; } float len1 = runningItem.getCurrX() + runningItem.getWidth(); float t1 = len1 / (runningItem.getSpeedFactor() * DanmakuItem.sBaseSpeed); float len2 = t1 * mFactor * DanmakuItem.sBaseSpeed; if (len2 > len1) { return true; } else { return false; } } public static int getBaseSpeed() { return sBaseSpeed; } public static void setBaseSpeed(int baseSpeed) { DanmakuItem.sBaseSpeed = baseSpeed; } private static int dip2px(Context context, float dipValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dipValue * scale + 0.5f); } private static int getFontHeight(TextPaint paint){ Paint.FontMetrics fm = paint.getFontMetrics(); return (int) Math.ceil(fm.descent - fm.top) + 2; }}DanmakuView.java
package com.xbw.danmu.danmu.opendanmaku;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.PorterDuff;import android.text.TextPaint;import android.util.AttributeSet;import android.util.Log;import android.view.View;import com.xbw.danmu.danmu.R;import java.util.ArrayList;import java.util.HashMap;import java.util.Iterator;import java.util.LinkedList;import java.util.List;import java.util.Random;/** * 弹幕View */public class DanmakuView extends View { public static final String TAG = "DanmakuView"; private final Context mContext; private int mMaxRow = 6; //最多几条弹道 private int mPickItemInterval = 300;//每隔多长时间取出一条弹幕来播放. private int mMaxRunningPerRow = 8; //每条弹道上最多同时有几个弹幕在屏幕上运行 private float mStartYOffset = 0.01f; //第一个弹道在Y轴上的偏移占整个View的百分比 private float mEndYOffset = 0.9f;//最后一个弹道在Y轴上的偏移占整个View的百分比 private HashMap<Integer,ArrayList<IDanmakuItem>> mChannelMap; private final java.util.Deque<IDanmakuItem> mWaitingItems = new LinkedList<>(); private int[] mChannelY; //每条弹道的Y坐标 private static final float mPartition = 0.95f; //仅View顶部的部分可以播放弹幕百分比 private static final int STATUS_RUNNING = 1; private static final int STATUS_PAUSE = 2; private static final int STATUS_STOP = 3; private volatile int status = STATUS_STOP; private static Random random = new Random(); private boolean mShowDebug = false; private LinkedList<Long> times; private Paint fpsPaint; private long previousTime = 0; private LinkedList<Float> lines; 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); mContext = context; TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.DanmakuView, 0, 0); mMaxRow = a.getInteger(R.styleable.DanmakuView_max_row, 1); mPickItemInterval = a.getInteger(R.styleable.DanmakuView_pick_interval, 1000); mMaxRunningPerRow = a.getInteger(R.styleable.DanmakuView_max_running_per_row, 1); mShowDebug = a.getBoolean(R.styleable.DanmakuView_show_debug, false); mStartYOffset = a.getFloat(R.styleable.DanmakuView_start_Y_offset, 0.01f); mEndYOffset = a.getFloat(R.styleable.DanmakuView_end_Y_offset, 0.9f); a.recycle(); checkYOffset(mStartYOffset, mEndYOffset); init(); } private void checkYOffset(float start, float end) { if (start >= end ){ throw new IllegalArgumentException("start_Y_offset must < end_Y_offset"); } if (start < 0f || start >= 1f || end < 0f || end > 1f) { throw new IllegalArgumentException("start_Y_offset and end_Y_offset must between 0 and 1)"); } } private void init() { setBackgroundColor(Color.TRANSPARENT); setDrawingCacheBackgroundColor(Color.TRANSPARENT); calculation(); } private void calculation() { if (mShowDebug) { fpsPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); fpsPaint.setColor(Color.YELLOW); fpsPaint.setTextSize(20); times = new LinkedList<>(); lines = new LinkedList<>(); } initChannelMap(); initChannelY(); } private void initChannelMap(){ mChannelMap = new HashMap<>(mMaxRow); for (int i = 0; i < mMaxRow; i++) { ArrayList<IDanmakuItem> runningRow= new ArrayList<IDanmakuItem>(mMaxRunningPerRow); mChannelMap.put(i, runningRow); } } private void initChannelY() { if (mChannelY == null){ mChannelY = new int[mMaxRow]; } float rowHeight = getHeight() * (mEndYOffset - mStartYOffset) / mMaxRow; float baseOffset = getHeight() * mStartYOffset; for (int i = 0; i < mMaxRow; i++) { mChannelY[i] = (int) (baseOffset + rowHeight * (i + 1) - rowHeight * 7/ 8);//每一行空间顶部留1/4,剩下3/4显示文字 } if (mShowDebug) { lines.add(baseOffset); for (int i = 0; i < mMaxRow; i++) { lines.add(baseOffset + rowHeight * (i + 1)); } } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (status == STATUS_RUNNING) { try { canvas.drawColor(Color.TRANSPARENT); //先绘制正在播放的弹幕 for (int i = 0; i < mChannelMap.size(); i++) { ArrayList<IDanmakuItem> list = mChannelMap.get(i); for (Iterator<IDanmakuItem> it = list.iterator(); it.hasNext(); ) { IDanmakuItem item = it.next(); if (item.isOut()) { it.remove(); } else { item.doDraw(canvas); } } } //检查是否需要加载播放下一个弹幕 if (System.currentTimeMillis() - previousTime > mPickItemInterval) { previousTime = System.currentTimeMillis();// Log.d(TAG, "start pick new item.."); IDanmakuItem di = mWaitingItems.pollFirst(); if (di != null) { int indexY = findVacant(di); if (indexY >= 0) {// Log.d(TAG, "find vacant channel"); di.setStartPosition(canvas.getWidth() - 2, mChannelY[indexY]);// Log.d(TAG, "draw new, text:" + di.getText()); //Log.d(TAG, String.format("doDraw, position,x=%s,y=%s", c.getWidth() - 1, mChannelY[indexY])); di.doDraw(canvas); mChannelMap.get(indexY).add(di);//不要忘记加入正运行的维护的列表中 } else {// Log.d(TAG, "Not find vacant channel, add it back"); addItemToHead(di);//找不到可以播放的弹道,则把它放回列表中 } } else { //no item 弹幕播放完毕, } } if (mShowDebug) { int fps = (int) fps(); canvas.drawText("FPS:" + fps, 5f, 20f, fpsPaint); for (float yp : lines) { canvas.drawLine(0f, yp, getWidth(), yp, fpsPaint); } } } catch (Exception e) { e.printStackTrace(); } invalidate(); } else {//暂停或停止,隐藏弹幕内容 canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); } } /**随机寻找一个可以播放弹幕而不会发生碰撞的弹道,返回弹道的Y坐标在mChannelY上的index,如果没有找到则返回-1*/ private int findVacant(IDanmakuItem item) { try {//fix NPT exception for (int i = 0; i < mMaxRow; i++) { ArrayList<IDanmakuItem> list = mChannelMap.get(i); if (list.size() == 0) { return i; } } int ind = random.nextInt(mMaxRow); for (int i = 0; i < mMaxRow; i++) { ArrayList<IDanmakuItem> list = mChannelMap.get((i + ind) % mMaxRow); if (list.size() > mMaxRunningPerRow) {//每个弹道最多mMaxRunning个弹幕 continue; } IDanmakuItem di = list.get(list.size() - 1); if (!item.willHit(di)) { return (i + ind) % mMaxRow; } } } catch (Exception e) { Log.w(TAG, "findVacant,Exception:" + e.toString());// e.printStackTrace(); } return -1; } private void clearPlayingItems() { if (mChannelMap != null) { synchronized (mChannelMap) { for (int i = 0; i < mChannelMap.size(); i++) { ArrayList<IDanmakuItem> list = mChannelMap.get(i); if (list != null) { list.clear(); } } } } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); initChannelY();//可能屏幕方向切换了,得重新计算坐标 } public boolean isPaused() { return STATUS_PAUSE == status; } /**播放显示弹幕*/ public void show() { status = STATUS_RUNNING; invalidate(); } /**隐藏弹幕,暂停播放*/ public void hide() { status = STATUS_PAUSE; invalidate(); } /**清空正在播放和等待播放的弹幕*/ public void clear() { status = STATUS_STOP; clearItems(); invalidate(); }// /**清空弹幕等待队列,暂停播放*/// public void pauseAndClear() {// if (mWaitingItems != null) {// synchronized (mWaitingItems) {// mWaitingItems.clear();// }// }// clearPlayingItems();// } private void clearItems() { clearRunning(); clearWaiting(); } private void clearRunning() { if (null != mChannelMap && !mChannelMap.isEmpty()) { mChannelMap.clear(); } } private void clearWaiting(){ if (null != mWaitingItems && !mWaitingItems.isEmpty()) { mWaitingItems.clear(); } } public void setMaxRow(int maxRow) { this.mMaxRow = maxRow; calculation(); clearRunning(); } public void setPickItemInterval(int pickItemInterval) { this.mPickItemInterval = pickItemInterval; } public void setMaxRunningPerRow(int maxRunningPerRow) { this.mMaxRunningPerRow = maxRunningPerRow; } public void setStartYOffset(float startYOffset, float endYOffset) { checkYOffset(startYOffset, endYOffset); clearRunning(); this.mStartYOffset = startYOffset; this.mEndYOffset = endYOffset; calculation(); } public void addItem(IDanmakuItem item) { synchronized (mWaitingItems) { this.mWaitingItems.add(item); } } public void addItemToHead(IDanmakuItem item) { synchronized (mWaitingItems) { this.mWaitingItems.offerFirst(item); } } /**是否新建后台线程来执行添加任务*/ public void addItem(final List<IDanmakuItem> list, boolean backgroundLoad) { if (backgroundLoad) { new Thread(){ @Override public void run() { synchronized (mWaitingItems) { mWaitingItems.addAll(list); } postInvalidate(); } }.start(); } else { this.mWaitingItems.addAll(list); } } /** Calculates and returns frames per second */ private double fps() { long lastTime = System.nanoTime(); times.addLast(lastTime); double NANOS = 1000000000.0; double difference = (lastTime - times.getFirst()) / NANOS; int size = times.size(); int MAX_SIZE = 100; if (size > MAX_SIZE) { times.removeFirst(); } return difference > 0 ? times.size() / difference : 0.0; }}IDanmakuItem.java
package com.xbw.danmu.danmu.opendanmaku;import android.graphics.Canvas;public interface IDanmakuItem { void doDraw(Canvas canvas); void setTextSize(int sizeInDip); void setTextColor(int colorResId); void setStartPosition(int x, int y); void setSpeedFactor(float factor); float getSpeedFactor(); boolean isOut(); boolean willHit(IDanmakuItem runningItem); void release(); int getWidth(); int getHeight(); int getCurrX(); int getCurrY();}弹幕发送
private void danmushow(String a,Context context){ //int[] color= {Color.RED,Color.BLACK,Color.BLUE,Color.WHITE,Color.YELLOW,Color.CYAN,Color.DKGRAY,Color.GRAY,Color.GREEN,Color.LTGRAY,Color.MAGENTA}; //Random random = new Random(); //int p = random.nextInt(color.length); IDanmakuItem item = new DanmakuItem(context, new SpannableString(a), mDanmakuView.getWidth(),0,R.color.white,20,1);//参数 上下文,弹幕内容,位置x,位置y,颜色,字体大小,速度。 //IDanmakuItem item = new DanmakuItem(context, new SpannableString(a), mDanmakuView.getWidth()); //item.setTextColor(context.getResources().getColor(Color.parseColor(Colors[p]))); //item.setTextSize(14); //item.setTextColor(getRandomColor()); mDanmakuView.addItemToHead(item); }
我把弹幕发送放在了消息透传里,这样就可以接受服务发送的实时消息了,把接收到的消息以弹幕的形式发送出来。
这只是弹幕部分,下边说消息透传。
0 0
- 安卓实时弹幕demo(一)弹幕效果
- 安卓实时弹幕demo(一)弹幕效果
- 安卓实时弹幕demo(二)消息透传
- 安卓实时弹幕demo(二)消息透传
- 安卓实时弹幕demo(三)消息透传服务器
- 安卓实时弹幕demo(四)科大讯飞语音SDK
- 安卓弹幕实现
- Unity模拟弹幕效果(一)
- 弹幕效果
- 实时弹幕(swoole+websocket)
- 安卓实现直播弹幕
- 弹幕
- 弹幕
- 弹幕
- 弹幕
- 网站实时弹幕
- cocos2d飞机弹幕demo
- 弹幕小demo
- CodeForces 55D Beautiful numbers (数位dp+搜索)★
- js打字机效果
- scala 文件写入操作
- Android学习笔记037之基于TCP的socket通信
- What is iPlanet
- 安卓实时弹幕demo(一)弹幕效果
- HTML学习13-iframe内联框架
- jQuery无刷新上传之uploadify简单试用
- PyQt5教程-12-切换按钮
- Socket网络编程学习笔记(1):常用方法介绍
- Android技术——缓存技术
- hdu5781 多校5 ATM Mechine【概率dp】
- S - 过山车
- Socket网络编程学习笔记(2):面向连接的Socket