当前位置: 首页 > 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

相关文章:

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

参考文章 此方案使用动画方式实现&#xff0c;只适合轻量级别的弹幕滚动效果实现&#xff0c;数据量过大时会出现内存激增的情况。 效果&#xff1a; 自定义view代码 public class TumbleLayout extends ViewGroup {private final String TAG "TumbleLayout";priva…...

学习ts(十二)Proxy与Reflect

定义 Proxy 为开发者提供了拦截并向基本操作嵌入额外行为的能力。具体的说&#xff0c;可以给目标对象定义一个关联的代理对象&#xff0c;而这个代理对象可以作为抽象的目标对象来使用。在对目标对象的各种操作影响目标对象之前&#xff0c;可以在代理对象中对这些操作加以控…...

性能优化之分库分表

1、什么是分库分表 1.1、分表 将同一个库中的一张表&#xff08;比如SPU表&#xff09;按某种方式&#xff08;垂直拆分、水平拆分&#xff09;拆分成SPU1、SPU2、SPU3、SPU4…等若干张表&#xff0c;如下图所示&#xff1a; 1.2、分库 在表数据不变的情况下&#xff0c;对…...

每日一学——STP、VRRP 、BFD、POE

STP (Spanning Tree Protocol): STP是一种用于构建安全和冗余的网络拓扑的协议。 它能够检测并防止网络中的环路形成&#xff0c;从而防止数据包在网络中无限循环。STP通过选择一个主桥和确定最短路径来实现拓扑稳定。STP有多种版本&#xff0c;如STP、RSTP和PVST等。 VRRP (V…...

Spring MVC 一 :从MVC Servlet开始

甩开膀子&#xff0c;继续干活。 今天开始Spring Framework中的另外一部分重头戏&#xff1a;Spring Web MVC&#xff0c;借助Spring Web MVC&#xff0c;Spring Framework可以通过Servlet API轻松构建基于web的应用。 在开始Spring Web MVC之前&#xff0c;我们还是要简单了…...

Ansible学习笔记(二)

3.ansible的使用示例&#xff08;playbook&#xff09; 1.创建mysql 账户和mysql 组的 playbook ---#create mysql user and group - hosts: allremote_user: roottasks:- name: create groupgroup: namemysql systemyes gid306- name: create useruser: namemysql systemyes…...

Web安全测试(一):HTTP请求详解

一、前言 结合内部资料,与安全渗透部门同事合力整理的安全测试相关资料教程,全方位涵盖电商、支付、金融、网络、数据库等领域的安全测试,覆盖Web、APP、中间件、内外网、Linux、Windows多个平台。学完后一定能成为安全大佬! 全部文章请访问专栏:《全栈安全测试教程(0基…...

Android工具条

在底层&#xff0c;所有通过主题得到应用条的活动都使用ActionBar类实现它的应用条。不过最新的应用条特性已经增加到AppCompat支持库中的Toolbar类。这意味着&#xff0c;如果你想在应用中使用最新的应用条特性&#xff0c;就需要使用支持库中的ToolBar类。 如何增加工具条 1…...

【项目实战典型案例】05.前后端分离的好处(发送调查问卷)

目录 一、背景二、思路三、过程1、主要的业务逻辑2、解决问题的思路 四、总结五、面向对象的好处 一、背景 以下流程图是给用户发送调查问的整体流程&#xff0c;将不必要的业务逻辑放到前端进行处理。这样导致逻辑混乱难以维护。前后端分离的其中一个目的是将功能的样式放在了…...

(Deep Learning)准确率和召回率的基础概念

算法模型极大的提升了对各类结果的预测效率。 【算法模型的本质】 算法模型的本质&#xff0c;是基于输入的各类变量因子&#xff0c;通过计算规则&#xff08;模型or公式&#xff09;&#xff0c;得出预测结果。 典型的预测结果比如&#xff1a; 1.&#xff08;通过历史行为…...

【业务功能篇85】微服务-springcloud-Nginx-反向代理-网关

Nginx域名 1.hosts文件 在c:/window/system32/drivers/etc/hosts文件&#xff0c;我们在这个文件中添加 192.168.56.100 msb.mall.com注意如果是没有操作权限&#xff0c;那么点击该文件右击属性&#xff0c;去掉只读属性即可 通过这个域名访问到Nginx服务 2.Nginx的方向代…...

深度适配?华为鸿蒙OS智能座舱酷狗音乐车载版5.0,车内尽享K歌

此次华为 HarmonyOS 智能座舱酷狗音乐车载版 5.0 升级为搭载了 HarmonyOS 车机系统的多款车型带来了更丰富的功能和互动体验。新版本的升级内容主要包括创新交互设计和高品质音质两个方面。 在创新交互设计方面&#xff0c;华为 HarmonyOS 智能座舱酷狗音乐车载版 5.0 深度适配…...

数字孪生体技术--学习笔记

一.数字孪生体技术概述 数字孪生体技术是跨层级&#xff0c;跨尺度的现实世界和虚拟世界的建立沟通的桥梁&#xff0c;是第四次工业革命的通用目的技术和核心技术体系之一&#xff0c;是支撑万物互联的综合技术系统&#xff0c;是数字经济发展的基础&#xff0c;是未来智能时代…...

proxysql使用心得

proxySQL 多层配置系统结构 -------------------------| RUNTIME |-------------------------/|\ || |[1] | [2] || \|/-------------------------| MEMORY |------------------------- _/|\ | …...

【C++ 学习 ⑰】- 继承(下)

目录 一、派生类的默认成员函数 二、继承与友元 三、继承与静态成员 四、复杂的菱形继承及菱形虚拟继承 五、继承和组合 一、派生类的默认成员函数 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认构造函数&#xff0c;那么必须在派生…...

kafka学习笔记

1、kafka是什么&#xff1f; kafka是一个高吞吐&#xff0c;分布式&#xff0c;基于发布/订阅的消息系统&#xff0c;最大的特性就是可以实时的处理大量的数据以满足各种需求场景&#xff1a;日志收集&#xff0c;离线和在线的消息消费&#xff0c;等等 2、kakfa的基础架构&am…...

阀门状态监测和预测性维护的原理和实施步骤

随着制造业数字化转型的推进&#xff0c;预测性维护&#xff08;Predictive Maintenance&#xff0c;简称PdM&#xff09;成为提高生产效率和设备可靠性的关键策略之一。在流程工厂中&#xff0c;阀门作为重要的设备之一&#xff0c;起着控制流体流动的关键作用。本文将探讨如何…...

复习之web服务器--apache

PS&#xff1a;Vim复制小技巧 一、实验环境 两台虚拟机 (nodea,nodeb)配置ip搭建软件仓库关闭selinux [rootftp Desktop]# hostnamectl set-hostname nodea.westos.org [rootftp Desktop]# hostname nodea.westos.org [rootftp Desktop]# ifconfig enp1s0: flags4163<UP,B…...

[Unity] 单例设计模式, 可供继承的单例组件模板类

一个可供继承的单例组件模板类: public class SingletonComponent<TComponent> : Componentwhere TComponent : SingletonComponent<TComponent> {static TComponent _instance;private static TComponent GetOrFindOrCreateComponent(){// 双检索if (_instance …...

Linux知识点 -- Linux多线程(三)

Linux知识点 – Linux多线程&#xff08;三&#xff09; 文章目录 Linux知识点 -- Linux多线程&#xff08;三&#xff09;一、线程同步1.概念理解2.条件变量3.使用条件变量进行线程同步 二、生产者消费者模型1.概念2.基于BlockingQueue的生产者消费者模型3.单生产者单消费者模…...

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

算法:模拟

1.替换所有的问号 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; ​遍历字符串​&#xff1a;通过外层循环逐一检查每个字符。​遇到 ? 时处理​&#xff1a; 内层循环遍历小写字母&#xff08;a 到 z&#xff09;。对每个字母检查是否满足&#xff1a; ​与…...

【C++进阶篇】智能指针

C内存管理终极指南&#xff1a;智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...

WEB3全栈开发——面试专业技能点P7前端与链上集成

一、Next.js技术栈 ✅ 概念介绍 Next.js 是一个基于 React 的 服务端渲染&#xff08;SSR&#xff09;与静态网站生成&#xff08;SSG&#xff09; 框架&#xff0c;由 Vercel 开发。它简化了构建生产级 React 应用的过程&#xff0c;并内置了很多特性&#xff1a; ✅ 文件系…...