文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android自制精彩弹幕效果

2023-05-30 22:19

关注

好久没有写过文章,最近发现直播特别的火,很多app都集成了直播的功能,发现有些直播是带有弹幕的,效果还不错,今天心血来潮,特地写了篇制作弹幕的文章.

今天要实现的效果如下:

弹幕垂直方向固定

Android自制精彩弹幕效果

弹幕垂直方向随机

Android自制精彩弹幕效果

上面效果图中白色的背景就是弹幕本身,是一个自定义的FrameLayout,我这里是为了更好的展示弹幕的位置才设置成了白色,当然如果是叠加在VideoView上的话,就需要设置成透明色了.
制作弹幕需要考虑以下几点问题:
1.弹幕的大小可以随意调整
2.弹幕内移动的item(或者称字幕)出现的位置,水平方向是从屏幕右边移动到屏幕左边,垂直方向是不能超出弹幕本身的高度的.
3.字幕移除屏幕后,需要将对应item(字幕)从其父容器(弹幕)中移除.
4.如果字幕出现的垂直方向的高度是随机的,那么还需要避免字幕重叠的情况.

ok,下面是弹幕自定义view的代码:

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); } }; }  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); } }  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); } } }  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; } } }}

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 资料下载
  • 历年真题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯