安卓实时弹幕demo(一)弹幕效果

//2016/08/03///

/by  XBW///

/android studio//

先上图,看效果


DanmakuItem.java

  
  
  1. package com.xbw.danmu.danmu.opendanmaku;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Canvas;  
  5. import android.graphics.Color;  
  6. import android.graphics.Paint;  
  7. import android.text.Layout;  
  8. import android.text.SpannableString;  
  9. import android.text.StaticLayout;  
  10. import android.text.TextPaint;  
  11.   
  12. import java.util.Random;  
  13.   
  14. public class DanmakuItem implements IDanmakuItem {  
  15.   
  16.     /** X axis base speed*/  
  17.     private static int sBaseSpeed = 3;  
  18.   
  19.     private Context mContext;  
  20.   
  21.     /**DanmakuView width, height*/  
  22.     private int mContainerWidth, mContainerHeight;  
  23.   
  24.     private int mTextSize;  
  25.   
  26.     private int mTextColor = Color.WHITE;  
  27.   
  28.     private SpannableString mContent;  
  29.   
  30.     private int mCurrX, mCurrY;  
  31.   
  32.     /** X axis speed factor*/  
  33.     private float mFactor;  
  34.   
  35.     private StaticLayout staticLayout;  
  36.     private StaticLayout borderStaticLayout;  
  37.     private static TextPaint strokePaint = new TextPaint();  
  38.   
  39.     private int mContentWidth , mContentHeight;  
  40.   
  41.   
  42.     static {  
  43.         strokePaint.setARGB(255000);  
  44. //        strokePaint.setTextAlign(Paint.Align.CENTER);  
  45. //        strokePaint.setTextSize(16);  
  46. //        strokePaint.setTypeface(Typeface.DEFAULT_BOLD);  
  47.         strokePaint.setStyle(Paint.Style.STROKE);  
  48.         strokePaint.setStrokeWidth(4);  
  49.         strokePaint.setAntiAlias(true);  
  50.   
  51.     }  
  52.     /** 
  53.      * construct a DanmakuItem 
  54.      * @param context Context 
  55.      * @param content paint text as content 
  56.      * @param startX start position of X axis, 
  57.      *               normally should be the screen width, e.g. right side of the view). 
  58.      *               the Y axis position will be assigned a channel by the DanmakuView randomly. 
  59.      */  
  60.     public DanmakuItem(Context context, CharSequence content, int startX) {  
  61.         this(context, new SpannableString(content), startX, 000, 1f);  
  62.     }  
  63.   
  64.     public DanmakuItem(Context context, CharSequence content, int startX, int startY) {  
  65.         this(context, new SpannableString(content), startX, startY, 00, 1f);  
  66.     }  
  67.   
  68.   
  69.     public DanmakuItem(Context context, SpannableString content, int startX, int startY,  
  70.                        int textColorResId, int textSizeInDip, float speedFactor) {  
  71.         this.mContext = context;  
  72.         this.mContent = content;  
  73.         this.mCurrX = startX;  
  74.         this.mCurrY = startY;  
  75.         setTextColor(textColorResId);  
  76.         setTextSize(textSizeInDip);  
  77.         mFactor = speedFactor;  
  78.         measure();  
  79.     }  
  80.   
  81.     private void measure() {  
  82.         TextPaint tp = new TextPaint();  
  83.         tp.setAntiAlias(true);  
  84.         tp.setColor(mTextColor);  
  85.         tp.setTextSize(mTextSize);  
  86.         strokePaint.setTextSize(mTextSize);  
  87. //        tp.setShadowLayer(4, 0, 0, Color.BLACK);  
  88.         mContentHeight = getFontHeight(tp);  
  89.         staticLayout = new StaticLayout(mContent,  
  90.                 tp,  
  91.                 (int) Layout.getDesiredWidth(mContent, 0, mContent.length(), tp) + 1,  
  92.                 Layout.Alignment.ALIGN_NORMAL,  
  93.                 1.0f,  
  94.                 0.0f,  
  95.                 false);  
  96.         mContentWidth = staticLayout.getWidth();  
  97.         borderStaticLayout = new StaticLayout(mContent,  
  98.                 strokePaint,  
  99.                 (int) Layout.getDesiredWidth(mContent, 0, mContent.length(), tp) + 1,  
  100.                 Layout.Alignment.ALIGN_NORMAL,  
  101.                 1.0f,  
  102.                 0.0f,  
  103.                 false);  
  104.     }  
  105.   
  106.     @Override  
  107.     public void doDraw(Canvas canvas) {  
  108.         int canvasWidth = canvas.getWidth();  
  109.         int canvasHeight = canvas.getHeight();  
  110.   
  111.         if (canvasWidth != this.mContainerWidth || canvasHeight != this.mContainerHeight) {//phone rotated !  
  112.             this.mContainerWidth = canvasWidth;  
  113.             this.mContainerHeight = canvasHeight;  
  114.         }  
  115.         canvas.save();  
  116.         canvas.translate(mCurrX,mCurrY);  
  117. //        for (int i = 0; i < 4; i++) { //加深阴影,产生描边效果. stroke/outline effect  
  118. //            staticLayout.draw(canvas);  
  119. //        }  
  120.         borderStaticLayout.draw(canvas);  
  121.         staticLayout.draw(canvas);  
  122.         canvas.restore();  
  123.         mCurrX = (int) (mCurrX - sBaseSpeed * mFactor);//only support moving along X axis  
  124.     }  
  125.   
  126.     @Override  
  127.     public void setTextSize(int textSizeInDip) {  
  128.         if (textSizeInDip > 0) {  
  129.             this.mTextSize = dip2px(mContext, textSizeInDip);  
  130.             measure();  
  131.         } else {  
  132.             this.mTextSize = dip2px(mContext, 14); // textSize default to 12 dp  
  133.         }  
  134.   
  135.     }  
  136.   
  137.     @Override  
  138.     public void setTextColor(int textColorResId) {  
  139.         int[] colorss= {Color.RED, Color.BLACK, Color.BLUE, Color.WHITE, Color.YELLOW, Color.CYAN, Color.DKGRAY, Color.GRAY, Color.GREEN, Color.LTGRAY, Color.MAGENTA};  
  140.         Random random = new Random();  
  141.         int p = random.nextInt(colorss.length);  
  142.         if (textColorResId > 0) {  
  143.             this.mTextColor = colorss[p];  
  144.             measure();  
  145.         }  
  146.     }  
  147.   
  148.     @Override  
  149.     public void setStartPosition(int x, int y) {  
  150.         this.mCurrX = x;  
  151.         this.mCurrY = y;  
  152.     }  
  153.   
  154.     @Override  
  155.     public void setSpeedFactor(float factor) {  
  156.         this.mFactor = factor;  
  157.     }  
  158.   
  159.     @Override  
  160.     public float getSpeedFactor() {  
  161.         return mFactor;  
  162.     }  
  163.   
  164.     @Override  
  165.     public boolean isOut() {  
  166.         return mCurrX < 0 && Math.abs(mCurrX) > mContentWidth;  
  167.     }  
  168.   
  169.     @Override  
  170.     public void release() {  
  171.         mContext = null;  
  172.     }  
  173.   
  174.     @Override  
  175.     public int getWidth() {  
  176.         return mContentWidth;  
  177.     }  
  178.   
  179.     @Override  
  180.     public int getHeight() {  
  181.         return mContentHeight;  
  182.     }  
  183.   
  184.     @Override  
  185.     public int getCurrX() {  
  186.         return mCurrX;  
  187.     }  
  188.   
  189.     @Override  
  190.     public int getCurrY() {  
  191.         return mCurrY;  
  192.     }  
  193.   
  194.     /*** 
  195.      * test whether this Danmaku Item would be hit the already running one or not 
  196.      * if it to be run on the same channel 
  197.      * @param runningItem item is already moving on the channel 
  198.      * @return hit or not 
  199.      */  
  200.     public boolean willHit(IDanmakuItem runningItem) {  
  201.         if (runningItem.getWidth() + runningItem.getCurrX() > mContainerWidth) {  
  202.             return true;  
  203.         }  
  204.   
  205.         if (runningItem.getSpeedFactor()>= mFactor) {  
  206.             return false;  
  207.         }  
  208.   
  209.         float len1 = runningItem.getCurrX() + runningItem.getWidth();  
  210.         float t1 = len1 / (runningItem.getSpeedFactor() * DanmakuItem.sBaseSpeed);  
  211.         float len2 = t1 * mFactor * DanmakuItem.sBaseSpeed;  
  212.         if (len2 > len1) {  
  213.             return true;  
  214.         } else {  
  215.             return false;  
  216.         }  
  217.   
  218.     }  
  219.   
  220.   
  221.     public static int getBaseSpeed() {  
  222.         return sBaseSpeed;  
  223.     }  
  224.   
  225.     public static void setBaseSpeed(int baseSpeed) {  
  226.         DanmakuItem.sBaseSpeed = baseSpeed;  
  227.     }  
  228.   
  229.   
  230.     private static int dip2px(Context context, float dipValue) {  
  231.         final float scale = context.getResources().getDisplayMetrics().density;  
  232.         return (int) (dipValue * scale + 0.5f);  
  233.     }  
  234.   
  235.     private static int getFontHeight(TextPaint paint){  
  236.         Paint.FontMetrics fm = paint.getFontMetrics();  
  237.         return (int) Math.ceil(fm.descent - fm.top) + 2;  
  238.     }  
  239. }  
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

   
   
  1. package com.xbw.danmu.danmu.opendanmaku;  
  2.   
  3. import android.content.Context;  
  4. import android.content.res.TypedArray;  
  5. import android.graphics.Canvas;  
  6. import android.graphics.Color;  
  7. import android.graphics.Paint;  
  8. import android.graphics.PorterDuff;  
  9. import android.text.TextPaint;  
  10. import android.util.AttributeSet;  
  11. import android.util.Log;  
  12. import android.view.View;  
  13.   
  14.   
  15. import com.xbw.danmu.danmu.R;  
  16.   
  17. import java.util.ArrayList;  
  18. import java.util.HashMap;  
  19. import java.util.Iterator;  
  20. import java.util.LinkedList;  
  21. import java.util.List;  
  22. import java.util.Random;  
  23.   
  24. /** 
  25.  * 弹幕View 
  26.  */  
  27. public class DanmakuView extends View {  
  28.   
  29.     public static final String TAG = "DanmakuView";  
  30.   
  31.     private final Context mContext;  
  32.   
  33.     private int mMaxRow = 6//最多几条弹道  
  34.     private int mPickItemInterval = 300;//每隔多长时间取出一条弹幕来播放.  
  35.     private int mMaxRunningPerRow = 8//每条弹道上最多同时有几个弹幕在屏幕上运行  
  36.     private float mStartYOffset = 0.01f; //第一个弹道在Y轴上的偏移占整个View的百分比  
  37.     private float mEndYOffset = 0.9f;//最后一个弹道在Y轴上的偏移占整个View的百分比  
  38.   
  39.   
  40.   
  41.     private HashMap<Integer,ArrayList<IDanmakuItem>> mChannelMap;  
  42.     private final java.util.Deque<IDanmakuItem> mWaitingItems = new LinkedList<>();  
  43.     private int[] mChannelY; //每条弹道的Y坐标  
  44.     private static final float mPartition = 0.95f; //仅View顶部的部分可以播放弹幕百分比  
  45.   
  46.     private static final int STATUS_RUNNING = 1;  
  47.     private static final int STATUS_PAUSE = 2;  
  48.     private static final int STATUS_STOP = 3;  
  49.   
  50.     private volatile int status = STATUS_STOP;  
  51.   
  52.     private static Random random = new Random();  
  53.   
  54.     private boolean mShowDebug = false;  
  55.     private LinkedList<Long> times;  
  56.     private Paint fpsPaint;  
  57.     private long previousTime = 0;  
  58.     private LinkedList<Float> lines;  
  59.   
  60.   
  61.     public DanmakuView(Context context) {  
  62.         this(context, null);  
  63.     }  
  64.   
  65.     public DanmakuView(Context context, AttributeSet attrs) {  
  66.         this(context, attrs, 0);  
  67.     }  
  68.   
  69.     public DanmakuView(Context context, AttributeSet attrs, int defStyleAttr) {  
  70.         super(context, attrs, defStyleAttr);  
  71.         mContext = context;  
  72.         TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.DanmakuView, 00);  
  73.         mMaxRow = a.getInteger(R.styleable.DanmakuView_max_row, 1);  
  74.         mPickItemInterval = a.getInteger(R.styleable.DanmakuView_pick_interval, 1000);  
  75.         mMaxRunningPerRow = a.getInteger(R.styleable.DanmakuView_max_running_per_row, 1);  
  76.         mShowDebug = a.getBoolean(R.styleable.DanmakuView_show_debug, false);  
  77.         mStartYOffset = a.getFloat(R.styleable.DanmakuView_start_Y_offset, 0.01f);  
  78.         mEndYOffset = a.getFloat(R.styleable.DanmakuView_end_Y_offset, 0.9f);  
  79.         a.recycle();  
  80.         checkYOffset(mStartYOffset, mEndYOffset);  
  81.         init();  
  82.     }  
  83.   
  84.     private void checkYOffset(float start, float end) {  
  85.         if (start >= end ){  
  86.             throw new IllegalArgumentException("start_Y_offset must < end_Y_offset");  
  87.         }  
  88.         if (start < 0f || start >= 1f || end < 0f || end > 1f) {  
  89.             throw new IllegalArgumentException("start_Y_offset and end_Y_offset must between 0 and 1)");  
  90.         }  
  91.     }  
  92.   
  93.     private void init() {  
  94.         setBackgroundColor(Color.TRANSPARENT);  
  95.         setDrawingCacheBackgroundColor(Color.TRANSPARENT);  
  96.         calculation();  
  97.     }  
  98.   
  99.     private void calculation() {  
  100.         if (mShowDebug) {  
  101.             fpsPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);  
  102.             fpsPaint.setColor(Color.YELLOW);  
  103.             fpsPaint.setTextSize(20);  
  104.             times = new LinkedList<>();  
  105.             lines = new LinkedList<>();  
  106.         }  
  107.         initChannelMap();  
  108.         initChannelY();  
  109.     }  
  110.   
  111.     private void initChannelMap(){  
  112.         mChannelMap = new HashMap<>(mMaxRow);  
  113.         for (int i = 0; i < mMaxRow; i++) {  
  114.             ArrayList<IDanmakuItem> runningRow= new ArrayList<IDanmakuItem>(mMaxRunningPerRow);  
  115.             mChannelMap.put(i, runningRow);  
  116.         }  
  117.     }  
  118.   
  119.     private void initChannelY() {  
  120.         if (mChannelY == null){  
  121.             mChannelY = new int[mMaxRow];  
  122.         }  
  123.   
  124.         float rowHeight = getHeight() * (mEndYOffset - mStartYOffset) / mMaxRow;  
  125.         float baseOffset = getHeight() * mStartYOffset;  
  126.         for (int i = 0; i < mMaxRow; i++) {  
  127.             mChannelY[i] = (int) (baseOffset + rowHeight * (i + 1) - rowHeight * 78);//每一行空间顶部留1/4,剩下3/4显示文字  
  128.   
  129.         }  
  130.         if (mShowDebug) {  
  131.             lines.add(baseOffset);  
  132.             for (int i = 0; i < mMaxRow; i++) {  
  133.                 lines.add(baseOffset + rowHeight * (i + 1));  
  134.             }  
  135.         }  
  136.     }  
  137.   
  138.     @Override  
  139.     protected void onDraw(Canvas canvas) {  
  140.         super.onDraw(canvas);  
  141.         if (status == STATUS_RUNNING) {  
  142.             try {  
  143.                 canvas.drawColor(Color.TRANSPARENT);  
  144.   
  145.                 //先绘制正在播放的弹幕  
  146.                 for (int i = 0; i < mChannelMap.size(); i++) {  
  147.                     ArrayList<IDanmakuItem> list = mChannelMap.get(i);  
  148.                     for (Iterator<IDanmakuItem> it = list.iterator(); it.hasNext(); ) {  
  149.                         IDanmakuItem item = it.next();  
  150.                         if (item.isOut()) {  
  151.                             it.remove();  
  152.                         } else {  
  153.                             item.doDraw(canvas);  
  154.                         }  
  155.                     }  
  156.                 }  
  157.   
  158.                 //检查是否需要加载播放下一个弹幕  
  159.                 if (System.currentTimeMillis() - previousTime > mPickItemInterval) {  
  160.                     previousTime = System.currentTimeMillis();  
  161. //                    Log.d(TAG, "start pick new item..");  
  162.                     IDanmakuItem di = mWaitingItems.pollFirst();  
  163.                     if (di != null) {  
  164.                         int indexY = findVacant(di);  
  165.                         if (indexY >= 0) {  
  166. //                            Log.d(TAG, "find vacant channel");  
  167.                             di.setStartPosition(canvas.getWidth() - 2, mChannelY[indexY]);  
  168. //                            Log.d(TAG, "draw new, text:" + di.getText());  
  169.                             //Log.d(TAG, String.format("doDraw, position,x=%s,y=%s", c.getWidth() - 1, mChannelY[indexY]));  
  170.                             di.doDraw(canvas);  
  171.                             mChannelMap.get(indexY).add(di);//不要忘记加入正运行的维护的列表中  
  172.   
  173.                         } else {  
  174. //                                Log.d(TAG, "Not find vacant channel, add it back");  
  175.                             addItemToHead(di);//找不到可以播放的弹道,则把它放回列表中  
  176.                         }  
  177.   
  178.                     } else {  
  179.                         //no item 弹幕播放完毕,  
  180.                     }  
  181.   
  182.                 }  
  183.   
  184.                 if (mShowDebug) {  
  185.                     int fps = (int) fps();  
  186.                     canvas.drawText("FPS:" + fps, 5f, 20f, fpsPaint);  
  187.                     for (float yp : lines) {  
  188.                         canvas.drawLine(0f, yp, getWidth(), yp, fpsPaint);  
  189.                     }  
  190.                 }  
  191.   
  192.             } catch (Exception e) {  
  193.                 e.printStackTrace();  
  194.             }  
  195.             invalidate();  
  196.   
  197.         } else {//暂停或停止,隐藏弹幕内容  
  198.             canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);  
  199.         }  
  200.     }  
  201.   
  202.   
  203.     /**随机寻找一个可以播放弹幕而不会发生碰撞的弹道,返回弹道的Y坐标在mChannelY上的index,如果没有找到则返回-1*/  
  204.     private int findVacant(IDanmakuItem item) {  
  205.         try {//fix NPT exception  
  206.             for (int i = 0; i < mMaxRow; i++) {  
  207.                 ArrayList<IDanmakuItem> list = mChannelMap.get(i);  
  208.                 if (list.size() == 0) {  
  209.                     return i;  
  210.                 }  
  211.             }  
  212.             int ind = random.nextInt(mMaxRow);  
  213.             for (int i = 0; i < mMaxRow; i++) {  
  214.                 ArrayList<IDanmakuItem> list = mChannelMap.get((i + ind) % mMaxRow);  
  215.                 if (list.size() > mMaxRunningPerRow) {//每个弹道最多mMaxRunning个弹幕  
  216.                     continue;  
  217.                 }  
  218.                 IDanmakuItem di = list.get(list.size() - 1);  
  219.                 if (!item.willHit(di)) {  
  220.                     return (i + ind) % mMaxRow;  
  221.                 }  
  222.             }  
  223.         } catch (Exception e) {  
  224.             Log.w(TAG, "findVacant,Exception:" + e.toString());  
  225. //                e.printStackTrace();  
  226.         }  
  227.   
  228.         return -1;  
  229.     }  
  230.   
  231.     private void clearPlayingItems() {  
  232.         if (mChannelMap != null) {  
  233.             synchronized (mChannelMap) {  
  234.                 for (int i = 0; i < mChannelMap.size(); i++) {  
  235.                     ArrayList<IDanmakuItem> list = mChannelMap.get(i);  
  236.                     if (list != null) {  
  237.                         list.clear();  
  238.                     }  
  239.                 }  
  240.             }  
  241.         }  
  242.     }  
  243.   
  244.     @Override  
  245.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  246.         super.onSizeChanged(w, h, oldw, oldh);  
  247.         initChannelY();//可能屏幕方向切换了,得重新计算坐标  
  248.     }  
  249.   
  250.     public boolean isPaused() {  
  251.         return STATUS_PAUSE == status;  
  252.     }  
  253.   
  254.   
  255.     /**播放显示弹幕*/  
  256.     public void show() {  
  257.         status = STATUS_RUNNING;  
  258.         invalidate();  
  259.     }  
  260.   
  261.     /**隐藏弹幕,暂停播放*/  
  262.     public void hide() {  
  263.         status = STATUS_PAUSE;  
  264.         invalidate();  
  265.     }  
  266.   
  267.     /**清空正在播放和等待播放的弹幕*/  
  268.     public void clear() {  
  269.         status = STATUS_STOP;  
  270.         clearItems();  
  271.         invalidate();  
  272.     }  
  273.   
  274. //    /**清空弹幕等待队列,暂停播放*/  
  275. //    public void pauseAndClear() {  
  276. //        if (mWaitingItems != null) {  
  277. //            synchronized (mWaitingItems) {  
  278. //                mWaitingItems.clear();  
  279. //            }  
  280. //        }  
  281. //        clearPlayingItems();  
  282. //    }  
  283.   
  284.     private void clearItems() {  
  285.         clearRunning();  
  286.         clearWaiting();  
  287.     }  
  288.   
  289.     private void clearRunning() {  
  290.         if (null != mChannelMap && !mChannelMap.isEmpty()) {  
  291.             mChannelMap.clear();  
  292.         }  
  293.     }  
  294.   
  295.     private void clearWaiting(){  
  296.         if (null != mWaitingItems && !mWaitingItems.isEmpty()) {  
  297.             mWaitingItems.clear();  
  298.         }  
  299.     }  
  300.   
  301.     public void setMaxRow(int maxRow) {  
  302.         this.mMaxRow = maxRow;  
  303.         calculation();  
  304.         clearRunning();  
  305.     }  
  306.   
  307.     public void setPickItemInterval(int pickItemInterval) {  
  308.         this.mPickItemInterval = pickItemInterval;  
  309.     }  
  310.   
  311.     public void setMaxRunningPerRow(int maxRunningPerRow) {  
  312.         this.mMaxRunningPerRow = maxRunningPerRow;  
  313.     }  
  314.   
  315.     public void setStartYOffset(float startYOffset, float endYOffset) {  
  316.         checkYOffset(startYOffset, endYOffset);  
  317.         clearRunning();  
  318.         this.mStartYOffset = startYOffset;  
  319.         this.mEndYOffset = endYOffset;  
  320.         calculation();  
  321.     }  
  322.   
  323.   
  324.     public void addItem(IDanmakuItem item) {  
  325.         synchronized (mWaitingItems) {  
  326.             this.mWaitingItems.add(item);  
  327.         }  
  328.     }  
  329.   
  330.     public void addItemToHead(IDanmakuItem item) {  
  331.         synchronized (mWaitingItems) {  
  332.             this.mWaitingItems.offerFirst(item);  
  333.         }  
  334.     }  
  335.   
  336.     /**是否新建后台线程来执行添加任务*/  
  337.     public void addItem(final List<IDanmakuItem> list, boolean backgroundLoad) {  
  338.         if (backgroundLoad) {  
  339.             new Thread(){  
  340.                 @Override  
  341.                 public void run() {  
  342.                     synchronized (mWaitingItems) {  
  343.                         mWaitingItems.addAll(list);  
  344.                     }  
  345.                     postInvalidate();  
  346.                 }  
  347.             }.start();  
  348.         } else {  
  349.             this.mWaitingItems.addAll(list);  
  350.         }  
  351.     }  
  352.   
  353.   
  354.     /** Calculates and returns frames per second */  
  355.     private double fps() {  
  356.         long lastTime = System.nanoTime();  
  357.         times.addLast(lastTime);  
  358.         double NANOS = 1000000000.0;  
  359.         double difference = (lastTime - times.getFirst()) / NANOS;  
  360.         int size = times.size();  
  361.         int MAX_SIZE = 100;  
  362.         if (size > MAX_SIZE) {  
  363.             times.removeFirst();  
  364.         }  
  365.         return difference > 0 ? times.size() / difference : 0.0;  
  366.     }  
  367. }  
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

  1. package com.xbw.danmu.danmu.opendanmaku;  
  2.   
  3. import android.graphics.Canvas;  
  4.   
  5. public interface IDanmakuItem {  
  6.   
  7.     void doDraw(Canvas canvas);  
  8.   
  9.     void setTextSize(int sizeInDip);  
  10.   
  11.     void setTextColor(int colorResId);  
  12.   
  13.     void setStartPosition(int x, int y);  
  14.   
  15.     void setSpeedFactor(float factor);  
  16.   
  17.     float getSpeedFactor();  
  18.   
  19.     boolean isOut();  
  20.   
  21.     boolean willHit(IDanmakuItem runningItem);  
  22.   
  23.     void release();  
  24.   
  25.     int getWidth();  
  26.   
  27.     int getHeight();  
  28.   
  29.     int getCurrX();  
  30.   
  31.     int getCurrY();  
  32. }  
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();
}
弹幕发送

  1. private void danmushow(String a,Context context){  
  2.         //int[] color= {Color.RED,Color.BLACK,Color.BLUE,Color.WHITE,Color.YELLOW,Color.CYAN,Color.DKGRAY,Color.GRAY,Color.GREEN,Color.LTGRAY,Color.MAGENTA};  
  3.         //Random random = new Random();  
  4.         //int p = random.nextInt(color.length);  
  5.         IDanmakuItem item = new DanmakuItem(context, new SpannableString(a), mDanmakuView.getWidth(),0,R.color.white,20,1);//参数 上下文,弹幕内容,位置x,位置y,颜色,字体大小,速度。  
  6.         //IDanmakuItem item = new DanmakuItem(context, new SpannableString(a), mDanmakuView.getWidth());  
  7.         //item.setTextColor(context.getResources().getColor(Color.parseColor(Colors[p])));  
  8.         //item.setTextSize(14);  
  9.         //item.setTextColor(getRandomColor());  
  10.         mDanmakuView.addItemToHead(item);  
  11.     }  
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);
    }

我把弹幕发送放在了消息透传里,这样就可以接受服务发送的实时消息了,把接收到的消息以弹幕的形式发送出来。

这只是弹幕部分,下边说消息透传。

 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值