当前位置: 首页 > 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.单生产者单消费者模…...

通达信资金做多导航指标实战指南:精准捕捉买卖信号与持股策略

1. 资金做多导航指标基础解读 第一次接触这个指标时&#xff0c;我也被它简洁明了的信号系统吸引。这个副图指标最大的特点就是用三种颜色柱线区分市场状态&#xff0c;就像交通信号灯一样直观。紫色代表多头行情&#xff0c;绿色提示空头风险&#xff0c;深灰色则是抄底机会。…...

Jimeng AI Studio快速上手:Streamlit界面中英文提示词输入最佳实践

Jimeng AI Studio快速上手&#xff1a;Streamlit界面中英文提示词输入最佳实践 1. 引言&#xff1a;为什么提示词如此重要&#xff1f; 如果你用过AI绘画工具&#xff0c;一定遇到过这样的情况&#xff1a;脑子里有个很棒的画面&#xff0c;但AI生成出来的却完全不是那么回事…...

从H3C转战华为S5720:一个网管的真实配置手记与命令对比

从H3C到华为S5720&#xff1a;网络工程师的配置迁移实战指南 第一次接触华为S5720交换机的H3C老手们&#xff0c;往往会在熟悉的CLI界面里遭遇微妙的"方言差异"。就像习惯粤语的人突然要说闽南语&#xff0c;明明都是中文&#xff0c;某些发音和用词却让人愣住半秒。…...

AI辅助开发实战:如何安全高效地搭建ChatGPT镜像网站

AI辅助开发实战&#xff1a;如何安全高效地搭建ChatGPT镜像网站 在AI应用开发浪潮中&#xff0c;许多开发者希望构建自己的ChatGPT镜像网站&#xff0c;以提供更稳定、定制化的服务。然而&#xff0c;从零开始搭建一个高性能、安全合规的镜像站&#xff0c;绝非易事。本文将结…...

Qwen3.5-4B-Claude-Opus效果展示:算法题解生成+时间复杂度同步说明

Qwen3.5-4B-Claude-Opus效果展示&#xff1a;算法题解生成时间复杂度同步说明 1. 模型能力概览 Qwen3.5-4B-Claude-4.6-Opus-Reasoning-Distilled-GGUF 是一个专为推理任务优化的轻量级模型&#xff0c;特别擅长处理需要结构化分析和分步骤解答的问题。这个4B参数的模型经过蒸…...

分析 paperlessngx 的典型项目结构(基于其官方仓库)。这是一个基于 Docker 的文档管理系统,主要依赖 Python (Django)、PostgreSQL、Redis 和 Tesse

好的&#xff0c;收到您的项目代号 paperlessngx。我将以资深技术文档工程师/DevOps专家的身份&#xff0c;为您生成一份详细的部署指南。 首先&#xff0c;我将按照预设的工作流&#xff0c;分析项目、推断环境要求&#xff0c;并最终整合成完整的部署文档。第一步&#xff1a…...

深度解析fastMRI开源项目:3大核心技术模块与医学影像重建实战指南

深度解析fastMRI开源项目&#xff1a;3大核心技术模块与医学影像重建实战指南 【免费下载链接】fastMRI A large-scale dataset of both raw MRI measurements and clinical MRI images. 项目地址: https://gitcode.com/gh_mirrors/fa/fastMRI fastMRI是一个由Facebook …...

JLink-V8固件升级实战:解决Keil报错与克隆检测难题

1. JLink-V8固件升级的必要性 最近在调试STM32F407时遇到了一个棘手的问题&#xff1a;Keil突然无法识别JLink-V8调试器。起初以为是驱动问题&#xff0c;反复重装驱动无果后&#xff0c;才意识到可能是固件出了问题。这种情况在嵌入式开发中并不少见&#xff0c;特别是使用老款…...

ANSYS/LS-DYNA求解器设置指南:显式和隐式参数优化全解析(附性能测试数据)

ANSYS/LS-DYNA求解器深度调优&#xff1a;显式与隐式参数实战指南 在工程仿真领域&#xff0c;求解器参数的精细调整往往决定着计算效率与结果可靠性的平衡。当面对手机跌落测试这类典型的高速冲击场景时&#xff0c;工程师们常常陷入两难选择——是采用计算速度更快的显式求解…...

【SAE 出版|EI,scopus双检索|沈阳理工大学主办 | 大咖嘉宾与会交流 | 硕博毕业、职称评审、项目结题可用】2026年机械、车辆与智能控制国际学术会议(ICMVIC 2026)

2026年机械、车辆与智能控制国际学术会议&#xff08;ICMVIC 2026&#xff09; 2026 International Conference on Machinery, Vehicle and Intelligent Control 2026年4月24-26日 中国沈阳 大会官网&#xff1a;www.icmvic.com【参会投稿】 截稿时间&#xff1a;见官网 …...