安卓实时弹幕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
原创粉丝点击