当前位置: 首页 > news >正文

Android自定义view实现横向滚动弹幕

参考文章
此方案使用动画方式实现,只适合轻量级别的弹幕滚动效果实现,数据量过大时会出现内存激增的情况。

效果:

效果图

自定义view代码

public class TumbleLayout extends ViewGroup {private final String TAG = "TumbleLayout";private int parentWidth;private int parentHeight;private long currentHshCode = 0;// 弹幕数据缓存池private DataPool dataPool = new DataPool<ContentBeen>(100);private DataPool userDataPool = new DataPool<ContentBeen>(10);private boolean isDetached = false;public TumbleLayout(@NonNull Context context) {super(context);initView();}public TumbleLayout(@NonNull Context context, @Nullable AttributeSet attrs) {super(context, attrs);initView();}public TumbleLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initView();}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);if (getParent() instanceof ViewGroup) {parentWidth = ((ViewGroup) getParent()).getWidth();parentHeight = ((ViewGroup) getParent()).getHeight();}}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int viewWidth = getViewWidth(widthMeasureSpec);int viewHeight = getViewHeight(heightMeasureSpec);parentWidth = viewWidth;parentHeight = viewHeight;// 设置子view的宽高setMeasuredDimension(viewWidth, viewHeight);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {// 宽高计算完毕 开始显示弹幕if (changed) {showNextData();}}@Overrideprotected void onFinishInflate() {super.onFinishInflate();}private void initView() {}public void addUserChildView(ContentBeen contentBeen) {if (userDataPool != null) {userDataPool.release(contentBeen);if (!dataPool.hasNext()) {showNextData();}}}public void addChildView(ContentBeen contentBeen) {if (dataPool != null) {// 将数据加入队列dataPool.release(contentBeen);}}private void startAnimator(View child) {ObjectAnimator animator = new ObjectAnimator();animator.setIntValues(0, parentWidth + child.getMeasuredWidth());animator.setDuration(3000);animator.setInterpolator(new LinearInterpolator());animator.addUpdateListener(animation -> {// view已经退出 则停止所有动画 防止内存泄露if (isDetached) {animation.cancel();return;}int x = (int) animation.getAnimatedValue();int left = parentWidth - x;int right = parentWidth + child.getMeasuredWidth() - x;// 控制弹幕密集度 当上一条数据离开屏幕右侧边框时 展示下一条弹幕数据if (currentHshCode == child.hashCode() && right + 50 <= parentWidth) {// 展示下一条弹幕showNextData();}child.layout(left, child.getTop(), right, child.getBottom());if (child.getRight() <= 0) {// 动画结束 移除viewremoveView(child);}});animator.start();}private void showNextData() {ContentBeen acquire = null;if (userDataPool == null && dataPool == null) {return;}// 用户本地弹幕优先级最高 若有本地用户弹幕 则先展示用户弹幕if (userDataPool.hasNext()) {acquire = (ContentBeen) userDataPool.acquire();} else if (dataPool.hasNext()) {acquire = (ContentBeen) dataPool.acquire();}// 执行一下条弹幕出现if (acquire != null) {// 小于最大数量时 添加新的子viewcurrentHshCode = acquire.getChildView().hashCode();addView(acquire.getChildView());int childCount = getChildCount();if (childCount != 0) {int index = childCount - 1;View child = getChildAt(index);measureMyChild(child);int left = parentWidth + 30;int num = laneNum(child);int top = num * child.getMeasuredHeight();int right = parentWidth + child.getMeasuredWidth() + 30;int bottom = top + child.getMeasuredHeight();MLog.e(TAG, "measureMyChild  hashCode = " + child.hashCode()+ " top = " + top + " bottom = " + bottom + " parentHeight" + getHeight());child.layout(left, top, right, bottom);startAnimator(child);}}}private int getViewWidth(int measureSpec) {int size = 100;int specSize = MeasureSpec.getSize(measureSpec);int specMode = MeasureSpec.getMode(measureSpec);if (specMode == MeasureSpec.EXACTLY) {size = specSize;} else if (specMode == MeasureSpec.AT_MOST) {size = Math.max(size, specSize);}return size;}private int getViewHeight(int measureSpec) {int size = 100;int specSize = MeasureSpec.getSize(measureSpec);int specMode = MeasureSpec.getMode(measureSpec);if (specMode == MeasureSpec.EXACTLY) {size = specSize;} else if (specMode == MeasureSpec.AT_MOST) {size = Math.max(size, specSize);}return size;}/*** 测量某一个child的宽高*/protected void measureMyChild(View child) {final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}/*** 计算随机高度值 起到随机位置展示效果*/private int laneNum(View child) {// 计算出大概有几条泳道int laneCount = getHeight() / child.getMeasuredHeight();// 给弹幕随机分配泳道Random random = new Random();// 返回泳道编号return random.nextInt(laneCount);}public void destroy() {// 回收资源 防止泄露userDataPool.clean();dataPool.clean();userDataPool = null;dataPool = null;}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();isDetached = true;MLog.e(TAG, "onDetachedFromWindow");}
}

存储数据队列的代码

public class DataPool<T> implements Pools.Pool<T> {private Object[] mPool;private int mPoolSize;private int l = 0;private int curIndex = 0;public DataPool(int maxPoolSize) {if (maxPoolSize <= 0) {throw new IllegalArgumentException("The max pool size must be > 0");}// 构造池对象容器mPool = new Object[maxPoolSize];}@Nullable@Overridepublic T acquire() {// 从容器中取出对象if (l > 0) {T instance = (T) mPool[curIndex];mPool[curIndex] = null;l--;curIndex++;if(l <= 0){curIndex = 0;}return instance;}return null;}@Overridepublic boolean release(@NonNull T instance) {if (isInPool(instance)) {throw new IllegalStateException("Already in the pool!");}// 存储对象if (l < mPool.length) {mPool[l] = instance;l++;return true;}return false;}// 判断对象是否在池中private boolean isInPool(@NonNull T instance) {// 遍历池对象for (int i = 0; i < l; i++) {if (mPool[i] == instance) {return true;}}return false;}public boolean hasNext(){return l > 0;}public void clean(){l = 0;curIndex = 0;}
}

数据格式

public class ContentBeen {private String content;private View childView;public ContentBeen(String content,View childView){this.content = content;this.childView = childView;}public void setChildView(View childView) {this.childView = childView;}public void setContent(String content) {this.content = content;}public String getContent() {return content;}public View getChildView() {return childView;}
}

布局文件内容

	<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".ui.activity.BarrageActivity"><com.example.app_view_model.view.TumbleLayoutandroid:id="@+id/tumble_layout"android:layout_width="match_parent"android:layout_height="300dp"android:background="@color/black"></com.example.app_view_model.view.TumbleLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:layout_marginBottom="20dp"android:orientation="horizontal"><EditTextandroid:id="@+id/txt_edit"android:layout_weight="1"android:layout_width="match_parent"android:layout_height="wrap_content" /><Buttonandroid:layout_weight="4"android:id="@+id/send_btn"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="发送" /></LinearLayout></RelativeLayout>

使用方法

	// 使用方法addUserChildView(); // 添加用户输入弹幕addChildView(); // 加载数据弹幕destroy(); // 资源回收 此方法一定要调用 防止大量动画无法回收导致oom
http://www.lryc.cn/news/143822.html

相关文章:

  • 学习ts(十二)Proxy与Reflect
  • 性能优化之分库分表
  • 每日一学——STP、VRRP 、BFD、POE
  • Spring MVC 一 :从MVC Servlet开始
  • Ansible学习笔记(二)
  • Web安全测试(一):HTTP请求详解
  • Android工具条
  • 【项目实战典型案例】05.前后端分离的好处(发送调查问卷)
  • (Deep Learning)准确率和召回率的基础概念
  • 【业务功能篇85】微服务-springcloud-Nginx-反向代理-网关
  • 深度适配?华为鸿蒙OS智能座舱酷狗音乐车载版5.0,车内尽享K歌
  • 数字孪生体技术--学习笔记
  • proxysql使用心得
  • 【C++ 学习 ⑰】- 继承(下)
  • kafka学习笔记
  • 阀门状态监测和预测性维护的原理和实施步骤
  • 复习之web服务器--apache
  • [Unity] 单例设计模式, 可供继承的单例组件模板类
  • Linux知识点 -- Linux多线程(三)
  • android java 硬编码保存mp4 jni数据转换
  • 那些你不得不知道的HTML知识点
  • 如何复制主播的性格(此乃广告文)
  • 【ES6】—【新特性】—Symbol详情
  • openresty安装与网站发布
  • 创建延时队列、springboot配置多个rabbitmq
  • 在kaggle中用GPU使用CGAN生成指定mnist手写数字
  • 【NI USRP】哪些 USRP 设备支持全双工,哪些支持半双工?
  • 不拼花哨,只拼实用:unittest指南,干货为王!
  • mysql 获取json数组中某个字段根据下标
  • 深入理解Redis缓存穿透、击穿、雪崩及解决方案