Android弹幕实现:基于B站弹幕开源系统(7)QQ、微信聊天气泡样式的弹幕
在附录文章得基础上,改进普通文本弹幕,实现一种特殊效果的文本弹幕,像QQ、微信一样的带有气泡背景的弹幕。实现的重点是在SpannedCacheStuffer。同时要准备若干需要衬在文本弹幕背景部分的.9.png图片。
上层Java代码:
1package zhangfei.danmaku; 2 3import android.content.res.Configuration; 4import android.graphics.Canvas; 5import android.graphics.Paint; 6import android.graphics.Rect; 7import android.graphics.drawable.Drawable; 8import android.support.v4.content.ContextCompat; 9import android.support.v7.app.AppCompatActivity; 10import android.os.Bundle; 11import android.text.TextPaint; 12import android.util.Log; 13import android.view.View; 14 15import com.github.lzyzsd.randomcolor.RandomColor; 16 17import java.util.HashMap; 18 19import master.flame.danmaku.controller.IDanmakuView; 20import master.flame.danmaku.danmaku.model.BaseDanmaku; 21import master.flame.danmaku.danmaku.model.DanmakuTimer; 22import master.flame.danmaku.danmaku.model.IDanmakus; 23import master.flame.danmaku.danmaku.model.IDisplayer; 24import master.flame.danmaku.danmaku.model.android.DanmakuContext; 25import master.flame.danmaku.danmaku.model.android.SpannedCacheStuffer; 26import master.flame.danmaku.ui.widget.DanmakuView; 27 28public class MainActivity extends AppCompatActivity { 29 private DanmakuView mDanmakuView; 30 private DanmakuContext mContext; 31 private AcFunDanmakuParser mParser; 32 33 private AppCompatActivity mActivity; 34 35 private final String TAG = getClass().getSimpleName(); 36 37 private BackgroundCacheStuffer mBackgroundCacheStuffer = new BackgroundCacheStuffer(); 38 39 @Override 40 protected void onCreate(Bundle savedInstanceState) { 41 super.onCreate(savedInstanceState); 42 mActivity = this; 43 setContentView(R.layout.activity_main); 44 45 mContext = DanmakuContext.create(); 46 mParser = new AcFunDanmakuParser(); 47 48 initDanmakuView(); 49 50 findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { 51 @Override 52 public void onClick(View view) { 53 addItems(); 54 } 55 }); 56 } 57 58 private void initDanmakuView() { 59 // 设置最大显示行数 60 HashMap<Integer, Integer> maxLinesPair = new HashMap<>(); 61 maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_RL, 5); // 滚动弹幕最大显示5行 62 // 设置是否禁止重叠 63 HashMap<Integer, Boolean> overlappingEnablePair = new HashMap<Integer, Boolean>(); 64 overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_RL, true); 65 overlappingEnablePair.put(BaseDanmaku.TYPE_FIX_TOP, true); 66 67 mDanmakuView = (DanmakuView) findViewById(R.id.sv_danmaku); 68 69 mContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN, 10) 70 .setDuplicateMergingEnabled(false) 71 .setScrollSpeedFactor(1.2f) 72 .setScaleTextSize(1.0f) 73 .setCacheStuffer(mBackgroundCacheStuffer, null) 74 // 绘制背景使用BackgroundCacheStuffer 75 .setMaximumLines(maxLinesPair) 76 .preventOverlapping(overlappingEnablePair).setDanmakuMargin(40); 77 78 if (mDanmakuView != null) { 79 mDanmakuView.setCallback(new master.flame.danmaku.controller.DrawHandler.Callback() { 80 @Override 81 public void updateTimer(DanmakuTimer timer) { 82 } 83 84 @Override 85 public void drawingFinished() { 86 87 } 88 89 @Override 90 public void danmakuShown(BaseDanmaku danmaku) { 91 92 } 93 94 @Override 95 public void prepared() { 96 mDanmakuView.start(); 97 } 98 }); 99 100 mDanmakuView.setOnDanmakuClickListener(new IDanmakuView.OnDanmakuClickListener() { 101 102 @Override 103 public boolean onDanmakuClick(IDanmakus danmakus) { 104 BaseDanmaku latest = danmakus.last(); 105 if (null != latest) { 106 return true; 107 } 108 return false; 109 } 110 111 @Override 112 public boolean onDanmakuLongClick(IDanmakus danmakus) { 113 return false; 114 } 115 116 @Override 117 public boolean onViewClick(IDanmakuView view) { 118 return false; 119 } 120 }); 121 122 mDanmakuView.prepare(mParser, mContext); 123 // mDanmakuView.showFPS(true); 124 mDanmakuView.enableDanmakuDrawingCache(true); 125 } 126 } 127 128 private void addItems() { 129 RandomColor randomColor = new RandomColor(); 130 131 int id = (int) (Math.random() * 10) % 3; 132 133 int resId; 134 switch (id) { 135 case 0: 136 resId = R.drawable.bg_01; 137 break; 138 139 case 1: 140 resId = R.drawable.bg_02; 141 break; 142 143 case 2: 144 resId = R.drawable.bg_03; 145 break; 146 147 default: 148 resId = R.drawable.bg_01; 149 break; 150 } 151 152 String s = ""; 153 int count = (int) (Math.random() * 100) % 10 + 1; 154 for (int i = 0; i < count; i++) { 155 s = s + i; 156 } 157 158 addDanmaKuTextWithBackgroundImage(resId, s, randomColor.randomColor(), false); 159 } 160 161 /** 162 * 绘制背景(自定义弹幕样式) 163 */ 164 private class BackgroundCacheStuffer extends SpannedCacheStuffer { 165 @Override 166 public void measure(BaseDanmaku danmaku, TextPaint paint, boolean fromWorkerThread) { 167 danmaku.padding = 50; // 在背景绘制模式下增加padding 168 super.measure(danmaku, paint, fromWorkerThread); 169 } 170 171 @Override 172 public void drawBackground(BaseDanmaku danmaku, Canvas canvas, float left, float top) { 173 Object object = danmaku.tag; 174 if (object instanceof DanmakuTag) { 175 DanmakuTag danmakuTag = (DanmakuTag) object; 176 177 Drawable drawable = ContextCompat.getDrawable(mActivity, danmakuTag.bitmapResId); 178 179 float height = danmaku.paintHeight; 180 float width = danmaku.paintWidth; 181 182 Rect rect = new Rect(0, 0, (int) width, (int) height); 183 drawable.setBounds(rect); 184 drawable.draw(canvas); 185 } 186 } 187 188 @Override 189 public void drawStroke(BaseDanmaku danmaku, String lineText, Canvas canvas, float left, 190 float top, Paint paint) { 191 // 禁用描边绘制 192 } 193 } 194 195 private void addDanmaKuTextWithBackgroundImage(int bitmap_resId, String msg, 196 int textColor, boolean islive) { 197 BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL); 198 if (danmaku == null) { 199 Log.e(TAG, "BaseDanmaku空"); 200 } 201 202 DanmakuTag danmakuTag = new DanmakuTag(); 203 danmakuTag.bitmapResId = bitmap_resId; 204 205 danmaku.setTag(danmakuTag); 206 207 danmaku.text = " " + msg + " "; 208 // danmaku.padding = 5; 209 danmaku.priority = 1; // 一定会显示, 一般用于本机发送的弹幕 210 danmaku.isLive = islive; 211 danmaku.setTime(mDanmakuView.getCurrentTime() + 1200); 212 danmaku.textSize = 25f * (mParser.getDisplayer().getDensity() - 0.6f); 213 danmaku.textColor = textColor; 214 danmaku.textShadowColor = 0; // 重要:如果有图文混排,最好不要设置描边(设textShadowColor=0),否则会进行两次复杂的绘制导致运行效率降低 215 // danmaku.underlineColor = Color.GREEN; 216 // danmaku.borderColor=new RandomColor().randomColor(); 217 218 mDanmakuView.addDanmaku(danmaku); 219 } 220 221 private class DanmakuTag { 222 public int bitmapResId; 223 } 224 225 @Override 226 protected void onPause() { 227 super.onPause(); 228 if (mDanmakuView != null && mDanmakuView.isPrepared()) { 229 mDanmakuView.pause(); 230 } 231 } 232 233 @Override 234 protected void onResume() { 235 super.onResume(); 236 if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) { 237 mDanmakuView.resume(); 238 } 239 } 240 241 @Override 242 protected void onDestroy() { 243 super.onDestroy(); 244 if (mDanmakuView != null) { 245 // dont forget release! 246 mDanmakuView.release(); 247 mDanmakuView = null; 248 } 249 } 250 251 @Override 252 public void onBackPressed() { 253 super.onBackPressed(); 254 if (mDanmakuView != null) { 255 // dont forget release! 256 mDanmakuView.release(); 257 mDanmakuView = null; 258 } 259 } 260 261 @Override 262 public void onConfigurationChanged(Configuration newConfig) { 263 super.onConfigurationChanged(newConfig); 264 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { 265 mDanmakuView.getConfig().setDanmakuMargin(20); 266 } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { 267 mDanmakuView.getConfig().setDanmakuMargin(40); 268 } 269 } 270}
代码运行结果:

附录:
1,《Android弹幕实现:基于B站弹幕开源系统(1)》链接:http://blog.csdn.net/zhangphil/article/details/68067100
2,《Android弹幕实现:基于B站弹幕开源系统(2)》链接:http://blog.csdn.net/zhangphil/article/details/68114226
3,《Android弹幕实现:基于B站弹幕开源系统(3)-文本弹幕的完善和细节调整》链接:http://blog.csdn.net/zhangphil/article/details/68485505
4,《Android弹幕实现:基于B站弹幕开源系统(4)-重构》链接:http://blog.csdn.net/zhangphil/article/details/68947236
5,《Android弹幕实现:基于B站弹幕开源系统(5)-抽象和复用》链接:http://blog.csdn.net/zhangphil/article/details/69400428
6,《Android弹幕实现:基于B站弹幕开源系统(6)带用户头像且头像从网络加载》链接:http://blog.csdn.net/zhangphil/article/details/72778984