Android自制精彩弹幕效果
作者:mChenys
这篇文章主要为大家详细介绍了Android自制精彩弹幕效果,弹幕垂直方向可固定随机,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
好久没有写过文章,最近发现直播特别的火,很多app都集成了直播的功能,发现有些直播是带有弹幕的,效果还不错,今天心血来潮,特地写了篇制作弹幕的文章.
今天要实现的效果如下:
1.弹幕垂直方向固定
2.弹幕垂直方向随机
上面效果图中白色的背景就是弹幕本身,是一个自定义的FrameLayout,我这里是为了更好的展示弹幕的位置才设置成了白色,当然如果是叠加在VideoView上的话,就需要设置成透明色了.
制作弹幕需要考虑以下几点问题:
1.弹幕的大小可以随意调整
2.弹幕内移动的item(或者称字幕)出现的位置,水平方向是从屏幕右边移动到屏幕左边,垂直方向是不能超出弹幕本身的高度的.
3.字幕移除屏幕后,需要将对应item(字幕)从其父容器(弹幕)中移除.
4.如果字幕出现的垂直方向的高度是随机的,那么还需要避免字幕重叠的情况.
ok,下面是弹幕自定义view的代码:
/** * Created by dell on 2016/9/28. */ public class DanmuView extends FrameLayout { private static final String TAG = "DanmuView"; private static final long DEFAULT_ANIM_DURATION = 6000; //默认每个动画的播放时长 private static final long DEFAULT_QUERY_DURATION = 3000; //遍历弹幕的默认间隔 private LinkedList<View> mViews = new LinkedList<>();//弹幕队列 private boolean isQuerying; private int mWidth;//弹幕的宽度 private int mHeight;//弹幕的高度 private Handler mUIHandler = new Handler(); private boolean TopDirectionFixed;//弹幕顶部的方向是否固定 private Handler mQueryHandler; private int mTopGravity = Gravity.CENTER_VERTICAL;//顶部方向固定时的默认对齐方式 public void setHeight(int height) { mHeight = height; } public void setWidth(int width) { mWidth = width; } public void setTopGravity(int gravity) { this.mTopGravity = gravity; } public void add(List<Danmu> danmuList) { for (int i = 0; i < danmuList.size(); i++) { Danmu danmu = danmuList.get(i); addDanmuToQueue(danmu); } } public void add(Danmu danmu) { addDanmuToQueue(danmu); } public DanmuView(Context context) { this(context, null); } public DanmuView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DanmuView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); HandlerThread thread = new HandlerThread("query"); thread.start(); //循环取出弹幕显示 mQueryHandler = new Handler(thread.getLooper()) { @Override public void handleMessage(Message msg) { final View view = mViews.poll(); if (null != view) { mUIHandler.post(new Runnable() { @Override public void run() { //添加弹幕 showDanmu(view); } }); } sendEmptyMessageDelayed(0, DEFAULT_QUERY_DURATION); } }; } /** * 将要展示的弹幕添加到队列中 * * @param danmu */ private void addDanmuToQueue(Danmu danmu) { if (null != danmu) { final View view = View.inflate(getContext(), R.layout.layout_danmu, null); TextView usernameTv = (TextView) view.findViewById(R.id.tv_username); TextView infoTv = (TextView) view.findViewById(R.id.tv_info); ImageView headerIv = (ImageView) view.findViewById(R.id.iv_header); usernameTv.setText(danmu.getUserName());//昵称 infoTv.setText(danmu.getInfo());//信息 Glide.with(getContext()).//头像 load(danmu.getHeaderUrl()). transform(new CropCircleTransformation(getContext())).into(headerIv); view.measure(0, 0); //添加弹幕到队列中 mViews.offerLast(view); } } /** * 播放弹幕 * * @param topDirectionFixed 弹幕顶部的方向是否固定 */ public void startPlay(boolean topDirectionFixed) { this.TopDirectionFixed = topDirectionFixed; if (mWidth == 0 || mHeight == 0) { getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @SuppressLint("NewApi") @Override public void onGlobalLayout() { getViewTreeObserver().removeOnGlobalLayoutListener(this); if (mWidth == 0) mWidth = getWidth() - getPaddingLeft() - getPaddingRight(); if (mHeight == 0) mHeight = getHeight() - getPaddingTop() - getPaddingBottom(); if (!isQuerying) { mQueryHandler.sendEmptyMessage(0); } } }); } else { if (!isQuerying) { mQueryHandler.sendEmptyMessage(0); } } } /** * 显示弹幕,包括动画的执行 * * @param view */ private void showDanmu(final View view) { isQuerying = true; Log.d(TAG, "mWidth:" + mWidth + " mHeight:" + mHeight); final LayoutParams lp = new LayoutParams(view.getMeasuredWidth(), view.getMeasuredHeight()); lp.leftMargin = mWidth; if (TopDirectionFixed) { lp.gravity = mTopGravity | Gravity.LEFT; } else { lp.gravity = Gravity.LEFT | Gravity.TOP; lp.topMargin = getRandomTopMargin(view); } view.setLayoutParams(lp); view.setTag(lp.topMargin); //设置item水平滚动的动画 ValueAnimator animator = ValueAnimator.ofInt(mWidth, -view.getMeasuredWidth()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { lp.leftMargin = (int) animation.getAnimatedValue(); view.setLayoutParams(lp); } }); addView(view);//显示弹幕 animator.setDuration(DEFAULT_ANIM_DURATION); animator.setInterpolator(new LinearInterpolator()); animator.start();//开启动画 animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.clearAnimation(); existMarginValues.remove(view.getTag());//移除已使用过的顶部边距 removeView(view);//移除弹幕 animation.cancel(); } }); } //记录当前仍在显示状态的弹幕的垂直方向位置(避免重复) private Set<Integer> existMarginValues = new HashSet<>(); private int linesCount; private int range = 10; private int getRandomTopMargin(View view) { //计算可用的行数 linesCount = mHeight / view.getMeasuredHeight(); if (linesCount <= 1) { linesCount = 1; } Log.d(TAG, "linesCount:" + linesCount); //检查重叠 while (true) { int randomIndex = (int) (Math.random() * linesCount); int marginValue = randomIndex * (mHeight / linesCount); //边界检查 if (marginValue > mHeight - view.getMeasuredHeight()) { marginValue = mHeight - view.getMeasuredHeight() - range; } if (marginValue == 0) { marginValue = range; } if (!existMarginValues.contains(marginValue)) { existMarginValues.add(marginValue); Log.d(TAG, "marginValue:" + marginValue); return marginValue; } } } }
弹幕实体类:
/** * Created by dell on 2016/9/28. */ public class Danmu { private String headerUrl;//头像 private String userName;//昵称 private String info;//信息 public String getHeaderUrl() { return headerUrl; } public void setHeaderUrl(String headerUrl) { this.headerUrl = headerUrl; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getInfo() { return info; } public void setInfo(String info) { this.info = info; } } 测试类,MainActivity public class MainActivity extends AppCompatActivity { DanmuView mDanmuView; EditText mMsgEdt; Button mSendBtn; Handler mDanmuAddHandler; boolean continueAdd; int counter; @Override protected void onResume() { super.onResume(); mDanmuView.startPlay(true);//true表示弹幕的垂直方向是固定的,false则随机 continueAdd = true; mDanmuAddHandler.sendEmptyMessageDelayed(0, 6000); } @Override protected void onPause() { super.onPause(); continueAdd = false; mDanmuAddHandler.removeMessages(0); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initData(); initListener(); } private void initView() { mDanmuView = (DanmuView) findViewById(R.id.danmuView); mMsgEdt = (EditText) findViewById(R.id.edt_msg); mSendBtn = (Button) findViewById(R.id.btn_send); } private void initData() { List<Danmu> danmuList = new ArrayList<>(); for (int i = 0; i < 3; i++) { Danmu danmu = new Danmu(); danmu.setHeaderUrl("http://tupian.qqjay.com/tou3/2016/0725/cb00091099ffbf09f4861f2bbb5dd993.jpg"); danmu.setUserName("Mr.chen" + i); danmu.setInfo("我是弹幕啊,不要问我为什么不可以那么长!!!"); danmuList.add(danmu); } mDanmuView.add(danmuList); //下面是模拟每秒添加一个弹幕的过程 HandlerThread ht = new HandlerThread("send danmu"); ht.start(); mDanmuAddHandler = new Handler(ht.getLooper()) { @Override public void handleMessage(Message msg) { runOnUiThread(new Runnable() { @Override public void run() { Danmu danmu = new Danmu(); danmu.setHeaderUrl("http://tupian.qqjay.com/tou3/2016/0803/87a8b262a5edeff0e11f5f0ba24fb22f.jpg"); danmu.setUserName("Mr.new" + (counter++)); danmu.setInfo("新的弹幕啊!!!新的弹幕啊!!!新的弹幕啊!!!新的弹幕啊!!!"); mDanmuView.add(danmu); } }); //继续添加 if (continueAdd) { sendEmptyMessageDelayed(0, 1000); } } }; } private void initListener() { //手动添加 mSendBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String msg = mMsgEdt.getText().toString().trim(); if (TextUtils.isEmpty(msg)) { Toast.makeText(MainActivity.this, "亲,你想发送什么啊?", Toast.LENGTH_SHORT).show(); return; } mMsgEdt.setText(""); Danmu danmu = new Danmu(); danmu.setHeaderUrl("http://img0.imgtn.bdimg.com/it/u=2198087564,4037394230&fm=11&gp=0.jpg"); danmu.setUserName("I'am good man"); danmu.setInfo("我是新人:" + msg); mDanmuView.add(danmu); } }); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。