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

android java 硬编码保存mp4 jni数据转换

目录 java imagereader编码保存 java NV21toYUV420SemiPlanar 编码保存视频用&#xff1a; imageReader获取nv21 jni NV12toYUV420SemiPlanar函数&#xff1a; 代码来自博客&#xff1a; 【Android Camera2】彻底弄清图像数据YUV420_888转NV21问题/良心教学/避坑必读!_yuv…...

那些你不得不知道的HTML知识点

目录 1、行内元素有哪些&#xff1f;块级元素有哪些&#xff1f; 空(void)元素有哪些&#xff1f;2、页面导入样式时&#xff0c;使用link和import有什么区别&#xff1f;3、title与h1的区别、b与strong的区别、i与em的区别&#xff1f;3.1 title与h1的区别&#xff1a;3.2 b与…...

如何复制主播的性格(此乃广告文)

上面这份ppt写于Fay开源之前。当然&#xff0c;以现在的认知再去评价当时的设计&#xff0c;会发现有诸多的不严谨&#xff0c;甚至缺憾。比如&#xff0c;以单层的网络结构肯定无法拟合人性这个复杂的东西&#xff0c;人性也不是只受已知的几个参数所作用。但我现在想说的是&a…...

【ES6】—【新特性】—Symbol详情

一、一种新的原始数据类型 定义&#xff1a;独一无二的字符串 二、 声明方式 1. 无描述声明 let s1 Symbol() let s2 Symbol() console.log(s1, s2) // Symbol() Symbol() console.log(s1 s2) // falsePS: Symbol 声明的值是独一无二的 2. 有描述的声明 let s1 Symb…...

openresty安装与网站发布

文章目录 安装依赖下载安装包解压安装包安装启动nginx配置环境变量配置开机启动发布静态网站 OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台&#xff0c;其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动…...

创建延时队列、springboot配置多个rabbitmq

创建延时队列 queue.file_delay_destroy x-dead-letter-exchange: exchange.file_delay_destroy x-message-ttl: 259200000 259200000为3天,1000为1秒创建普通队列 queue.file_destroy创建普通交换机 exchange.file_delay_destroytype选择fanout 交换机绑定普通队列 (图中…...

在kaggle中用GPU使用CGAN生成指定mnist手写数字

文章目录 1项目介绍2参考文章3代码的实现过程及对代码的详细解析独热编码定义生成器定义判别器打印我们的引导信息模型训练迭代过程中生成的图片损失函数的变化 4总结5 模型相关的文件 1项目介绍 在GAN的基础上进行有条件的引导生成图片cgan 2参考文章 GAN实战之Pytorch 使用…...

【NI USRP】哪些 USRP 设备支持全双工,哪些支持半双工?

译者 东枫电子科技 设备构成 NI USRPEttus USRPUSRP-2900B200USRP-2901B210USRP-2920N210 WBXUSRP-2921N210 XCVR 2450USRP-2922N210 SBXUSRP-2930N210 WBX GPSDOUSRP-2932N210 SBX GPSDOUSRP-2940RX310 WBX (x2)USRP-2942RX310 SBX (x2)USRP-2943RX310 CBX (x2)U…...

不拼花哨,只拼实用:unittest指南,干货为王!

Python为开发者提供了内置的单元测试框架 unittest&#xff0c;它是一种强大的工具&#xff0c;能够有效地编写和执行单元测试。unittest 提供了完整的测试结构&#xff0c;支持自动化测试的执行&#xff0c;能够对测试用例进行组织&#xff0c;并且提供了丰富的断言方法。最终…...

mysql 获取json数组中某个字段根据下标

MySQL获取JSON数组中某个字段根据下标 在MySQL中&#xff0c;JSON数据类型可以方便地存储、操作和查询包含复杂结构的数据。当我们需要从JSON数组中获取某个字段时&#xff0c;可以使用MySQL的JSON函数来实现。 1. JSON数据类型简介 JSON&#xff08;JavaScript Object Nota…...