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实现横向滚动弹幕
参考文章 此方案使用动画方式实现,只适合轻量级别的弹幕滚动效果实现,数据量过大时会出现内存激增的情况。 效果: 自定义view代码 public class TumbleLayout extends ViewGroup {private final String TAG "TumbleLayout";priva…...
学习ts(十二)Proxy与Reflect
定义 Proxy 为开发者提供了拦截并向基本操作嵌入额外行为的能力。具体的说,可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。在对目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控…...
性能优化之分库分表
1、什么是分库分表 1.1、分表 将同一个库中的一张表(比如SPU表)按某种方式(垂直拆分、水平拆分)拆分成SPU1、SPU2、SPU3、SPU4…等若干张表,如下图所示: 1.2、分库 在表数据不变的情况下,对…...
每日一学——STP、VRRP 、BFD、POE
STP (Spanning Tree Protocol): STP是一种用于构建安全和冗余的网络拓扑的协议。 它能够检测并防止网络中的环路形成,从而防止数据包在网络中无限循环。STP通过选择一个主桥和确定最短路径来实现拓扑稳定。STP有多种版本,如STP、RSTP和PVST等。 VRRP (V…...
Spring MVC 一 :从MVC Servlet开始
甩开膀子,继续干活。 今天开始Spring Framework中的另外一部分重头戏:Spring Web MVC,借助Spring Web MVC,Spring Framework可以通过Servlet API轻松构建基于web的应用。 在开始Spring Web MVC之前,我们还是要简单了…...
Ansible学习笔记(二)
3.ansible的使用示例(playbook) 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工具条
在底层,所有通过主题得到应用条的活动都使用ActionBar类实现它的应用条。不过最新的应用条特性已经增加到AppCompat支持库中的Toolbar类。这意味着,如果你想在应用中使用最新的应用条特性,就需要使用支持库中的ToolBar类。 如何增加工具条 1…...
【项目实战典型案例】05.前后端分离的好处(发送调查问卷)
目录 一、背景二、思路三、过程1、主要的业务逻辑2、解决问题的思路 四、总结五、面向对象的好处 一、背景 以下流程图是给用户发送调查问的整体流程,将不必要的业务逻辑放到前端进行处理。这样导致逻辑混乱难以维护。前后端分离的其中一个目的是将功能的样式放在了…...
(Deep Learning)准确率和召回率的基础概念
算法模型极大的提升了对各类结果的预测效率。 【算法模型的本质】 算法模型的本质,是基于输入的各类变量因子,通过计算规则(模型or公式),得出预测结果。 典型的预测结果比如: 1.(通过历史行为…...
【业务功能篇85】微服务-springcloud-Nginx-反向代理-网关
Nginx域名 1.hosts文件 在c:/window/system32/drivers/etc/hosts文件,我们在这个文件中添加 192.168.56.100 msb.mall.com注意如果是没有操作权限,那么点击该文件右击属性,去掉只读属性即可 通过这个域名访问到Nginx服务 2.Nginx的方向代…...
深度适配?华为鸿蒙OS智能座舱酷狗音乐车载版5.0,车内尽享K歌
此次华为 HarmonyOS 智能座舱酷狗音乐车载版 5.0 升级为搭载了 HarmonyOS 车机系统的多款车型带来了更丰富的功能和互动体验。新版本的升级内容主要包括创新交互设计和高品质音质两个方面。 在创新交互设计方面,华为 HarmonyOS 智能座舱酷狗音乐车载版 5.0 深度适配…...
数字孪生体技术--学习笔记
一.数字孪生体技术概述 数字孪生体技术是跨层级,跨尺度的现实世界和虚拟世界的建立沟通的桥梁,是第四次工业革命的通用目的技术和核心技术体系之一,是支撑万物互联的综合技术系统,是数字经济发展的基础,是未来智能时代…...
proxysql使用心得
proxySQL 多层配置系统结构 -------------------------| RUNTIME |-------------------------/|\ || |[1] | [2] || \|/-------------------------| MEMORY |------------------------- _/|\ | …...
【C++ 学习 ⑰】- 继承(下)
目录 一、派生类的默认成员函数 二、继承与友元 三、继承与静态成员 四、复杂的菱形继承及菱形虚拟继承 五、继承和组合 一、派生类的默认成员函数 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认构造函数,那么必须在派生…...
kafka学习笔记
1、kafka是什么? kafka是一个高吞吐,分布式,基于发布/订阅的消息系统,最大的特性就是可以实时的处理大量的数据以满足各种需求场景:日志收集,离线和在线的消息消费,等等 2、kakfa的基础架构&am…...
阀门状态监测和预测性维护的原理和实施步骤
随着制造业数字化转型的推进,预测性维护(Predictive Maintenance,简称PdM)成为提高生产效率和设备可靠性的关键策略之一。在流程工厂中,阀门作为重要的设备之一,起着控制流体流动的关键作用。本文将探讨如何…...
复习之web服务器--apache
PS: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多线程(三) 文章目录 Linux知识点 -- Linux多线程(三)一、线程同步1.概念理解2.条件变量3.使用条件变量进行线程同步 二、生产者消费者模型1.概念2.基于BlockingQueue的生产者消费者模型3.单生产者单消费者模…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
