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

Android 使用.9图 NinePatchDrawable实现动态聊天气泡

最近一段时间,在做一个需求,需要实现一个聊天气泡的动画效果,如下图所示:

GitHub源码demo ,建议下载demo,运行查看。

动态聊天气泡动画

在这里插入图片描述

静态聊天气泡

在这里插入图片描述

经过一段时间调研,实现方案如下:

实现方案

  • 从服务端下载zip文件,文件中包含配置文件和多张png图片,配置文件定义了图片的横向拉伸拉伸区域、纵向拉伸区域、padding信息等。
  • 从本地加载配置文件,加载多张png图片为bitmap。
  • 将bitmap存储在内存里。LruCache,避免多次解析。
  • 根据配置文件,将png图片转换为.9图,NinePatchDrawable。
  • 使用多张NinePatchDrawable创建一个帧动画对象AnimationDrawable
  • 将AnimationDrawable设置为控件的背景,并让AnimationDrawable播放动画,执行一定的次数后停止动画。

其中的难点在于第3步,将png图片转换为.9图 NinePatchDrawable

NinePatchDrawable 的构造函数。

/*** Create drawable from raw nine-patch data, setting initial target density* based on the display metrics of the resources.*/
public NinePatchDrawable(Resources res,Bitmap bitmap,byte[]chunk,Rect padding,String srcName){this(new NinePatchState(new NinePatch(bitmap,chunk,srcName),padding),res);
}

其中最关键的点在于构建byte[] chunk参数。通过查看这个类NinePatchChunk.java,并参阅了许多博客,通过反向分析NinePatchChunk类的deserialize方法,得到了如何构建byte[] chunk的方法。

// See "frameworks/base/include/utils/ResourceTypes.h" for the format of
// NinePatch chunk.
class NinePatchChunk {public static final int NO_COLOR = 0x00000001;public static final int TRANSPARENT_COLOR = 0x00000000;public Rect mPaddings = new Rect();public int mDivX[];public int mDivY[];public int mColor[];private static void readIntArray(int[] data, ByteBuffer buffer) {for (int i = 0, n = data.length; i < n; ++i) {data[i] = buffer.getInt();}}private static void checkDivCount(int length) {if (length == 0 || (length & 0x01) != 0) {throw new RuntimeException("invalid nine-patch: " + length);}}//注释1处,解析byte[]数据,构建NinePatchChunk对象public static NinePatchChunk deserialize(byte[] data) {ByteBuffer byteBuffer =ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());byte wasSerialized = byteBuffer.get();if (wasSerialized == 0)//第一个字节不能为0return null;NinePatchChunk chunk = new NinePatchChunk();chunk.mDivX = new int[byteBuffer.get()];//第二个字节为x方向上的切割线的个数chunk.mDivY = new int[byteBuffer.get()];//第三个字节为y方向上的切割线的个数chunk.mColor = new int[byteBuffer.get()];//第四个字节为颜色的个数checkDivCount(chunk.mDivX.length);//判断x方向上的切割线的个数是否为偶数checkDivCount(chunk.mDivY.length);//判断y方向上的切割线的个数是否为偶数// skip 8 bytes,跳过8个字节byteBuffer.getInt();byteBuffer.getInt();//注释2处,处理padding,发现都设置为0也可以。chunk.mPaddings.left = byteBuffer.getInt();//左边的paddingchunk.mPaddings.right = byteBuffer.getInt();//右边的paddingchunk.mPaddings.top = byteBuffer.getInt();//上边的paddingchunk.mPaddings.bottom = byteBuffer.getInt();//下边的padding// skip 4 bytesbyteBuffer.getInt();//跳过4个字节readIntArray(chunk.mDivX, byteBuffer);//读取x方向上的切割线的位置readIntArray(chunk.mDivY, byteBuffer);//读取y方向上的切割线的位置readIntArray(chunk.mColor, byteBuffer);//读取颜色return chunk;}
}

注释1处,解析byte[]数据,构建NinePatchChunk对象。我们添加了一些注释,意思已经很清晰了。

然后我们根据这里类来构建byte[] chunk参数。

private fun buildChunk(): ByteArray {// 横向和竖向端点的数量 = 线段数量 * 2,这里只有一个线段,所以都是2val horizontalEndpointsSize = 2val verticalEndpointsSize = 2//这里计算的 arraySize 是 int 值,最终占用的字节数是 arraySize * 4val arraySize = 1 + 2 + 4 + 1 + horizontalEndpointsSize + verticalEndpointsSize + COLOR_SIZE//这里乘以4,是因为一个int占用4个字节val byteBuffer = ByteBuffer.allocate(arraySize * 4).order(ByteOrder.nativeOrder())byteBuffer.put(1.toByte()) //第一个字节无意义,不等于0就行byteBuffer.put(horizontalEndpointsSize.toByte()) //mDivX x数组的长度byteBuffer.put(verticalEndpointsSize.toByte()) //mDivY y数组的长度byteBuffer.put(COLOR_SIZE.toByte()) //mColor数组的长度// skip 8 bytesbyteBuffer.putInt(0)byteBuffer.putInt(0)//Note: 目前还没搞清楚,发现都 byteBuffer.putInt(0),也没问题。//左右paddingbyteBuffer.putInt(mRectPadding.left)byteBuffer.putInt(mRectPadding.right)//上下paddingbyteBuffer.putInt(mRectPadding.top)byteBuffer.putInt(mRectPadding.bottom)//byteBuffer.putInt(0)//byteBuffer.putInt(0)//上下padding//byteBuffer.putInt(0)//byteBuffer.putInt(0)//skip 4 bytesbyteBuffer.putInt(0)//mDivX数组,控制横向拉伸的线段数据,目前只支持一个线段patchRegionHorizontal.forEach {byteBuffer.putInt(it.start * width / originWidth)byteBuffer.putInt(it.end * width / originWidth)}//mDivY数组,控制竖向拉伸的线段数据,目前只支持一个线段patchRegionVertical.forEach {byteBuffer.putInt(it.start * height / originHeight)byteBuffer.putInt(it.end * height / originHeight)}//mColor数组for (i in 0 until COLOR_SIZE) {byteBuffer.putInt(NO_COLOR)}return byteBuffer.array()
}

完整的类请参考 AnimationDrawableFactory.kt

使用

完整的使用请查看 ChatAdapter 类。

AnimationDrawableFactory 支持从文件构建动画,也支持从Android的资源文件夹构建动画。

!!!注意,从文件构建动画,需要将请把工程下的bubbleframe文件夹拷贝到手机的Android/data/包名/files
目录下val fileDir = getExternalFilesDir(null),否则会报错。

从文件构建动画

 return AnimationDrawableFactory(context).setDrawableDir(pngsDir)//图片文件所在的目录.setHorizontalStretchBean(PatchStretchBean(60, 61))//水平拉伸区域.setVerticalStretchBean(PatchStretchBean(52, 53))//垂直拉伸区域.setOriginSize(128, 112)//原始图片大小.setPadding(Rect(31, 37, 90, 75))//padding区域.setHorizontalMirror(isSelf)//是否水平镜像,不是必须的.setScaleFromFile(true)//是否从文件中读取图片的缩放比例,不是必须的.setFinishCount(3)//动画播放次数.setFrameDuration(100)//每帧动画的播放时间.buildFromFile()

这里注意一下:因为文件中的图片是一倍图,所以这里需要放大,所以设置了setScaleFromFile(true)
如果文件中的图片是3倍图,就不需要设置这个参数了。如果需要更加精细的缩放控制,后面再增加支持。

从Android的资源文件夹构建动画


private val resIdList = mutableListOf<Int>().apply {add(R.drawable.bubble_frame1)add(R.drawable.bubble_frame2)add(R.drawable.bubble_frame3)add(R.drawable.bubble_frame4)add(R.drawable.bubble_frame5)add(R.drawable.bubble_frame6)add(R.drawable.bubble_frame7)add(R.drawable.bubble_frame8)add(R.drawable.bubble_frame9)add(R.drawable.bubble_frame10)add(R.drawable.bubble_frame11)add(R.drawable.bubble_frame12)
}/*** 从正常的资源文件加载动态气泡*/
return AnimationDrawableFactory(context).setDrawableResIdList(resIdList)//图片资源id列表.setHorizontalStretchBean(PatchStretchBean(60, 61))//水平拉伸区域.setVerticalStretchBean(PatchStretchBean(52, 53))//垂直拉伸区域.setOriginSize(128, 112)//原始图片大小.setPadding(Rect(31, 37, 90, 75))//padding区域.setHorizontalMirror(isSelf)//是否水平镜像,不是必须的.setFinishCount(3)//动画播放次数,不是必须的.setFrameDuration(100)//每帧动画的播放时间,不是必须的.buildFromResource()

有时候可能我们只需要构建静态气泡,也就是只需要一张 NinepatchDrawable,我们提供了一个类来构建静态气泡,NinePatchDrawableFactory.kt

从文件加载

return NinePatchDrawableFactory(context).setDrawableFile(pngFile)//图片文件.setHorizontalStretchBean(PatchStretchBean(60, 61))//水平拉伸区域.setVerticalStretchBean(PatchStretchBean(52, 53))//垂直拉伸区域.setOriginSize(128, 112)//原始图片大小.setScaleFromFile(true)//是否从文件中读取图片的缩放比例,不是必须的.setPadding(Rect(31, 37, 90, 75))//padding区域.setHorizontalMirror(isSelf)//是否水平镜像,不是必须的.buildFromFile()

从资源加载

return NinePatchDrawableFactory(context).setDrawableResId(R.drawable.bubble_frame1)//图片资源id.setHorizontalStretchBean(PatchStretchBean(60, 61))//水平拉伸区域.setVerticalStretchBean(PatchStretchBean(52, 53))//垂直拉伸区域.setOriginSize(128, 112)//原始图片大小.setPadding(Rect(31, 37, 90, 75))//padding区域.setHorizontalMirror(isSelf)//是否水平镜像,不是必须的.buildFromResource()

padding 取值

如图所示:宽高是128*112。横向padding取值为31、90,纵向padding取值为37、75。

在这里插入图片描述

其他

在实现过程中发现Android 的 帧动画 AnimationDrawable无法控制动画执行的次数。最后自定义了一个类,CanStopAnimationDrawable.kt 解决。

参考链接:

  • Carson带你学Android:关于逐帧动画的使用都在这里了!-腾讯云开发者社区-腾讯云
  • 聊天气泡图片的动态拉伸、镜像与适配 - 掘金
  • Android 点九图机制讲解及在聊天气泡中的应用 - 掘金
  • Android动态布局入门及NinePatchChunk解密
  • Android点九图总结以及在聊天气泡中的使用-腾讯云开发者社区-腾讯云
  • https://developer.android.com/studio/write/draw9patch?utm_source=android-studio&hl=zh-cn

相关文章:

Android 使用.9图 NinePatchDrawable实现动态聊天气泡

最近一段时间&#xff0c;在做一个需求&#xff0c;需要实现一个聊天气泡的动画效果&#xff0c;如下图所示&#xff1a; GitHub源码demo &#xff0c;建议下载demo&#xff0c;运行查看。 动态聊天气泡动画 静态聊天气泡 经过一段时间调研&#xff0c;实现方案如下: 实现方…...

力扣 LCR 024. 反转链表两种解法

目录 1.解题思路Ⅰ2.代码实现Ⅰ3.解题思路Ⅱ4.代码实现Ⅱ 1.解题思路Ⅰ 利用头插法&#xff0c;遍历数组将后面的元素头插到前面的元素. 2.代码实现Ⅰ struct ListNode* reverseList(struct ListNode* head) { struct ListNode*curhead;;struct ListNode*newheadNULL;whil…...

掌握Capture One 23 Pro,打造专业级图片编辑体验!

作为一位摄影师&#xff0c;您是否曾经为自己的照片无法达到预期效果而烦恼&#xff1f;或者您是否在寻找一种能够让您轻松处理和编辑照片的工具&#xff1f;如果是&#xff0c;那么您一定不能错过Capture One 23 Pro这款图片编辑软件&#xff01; Capture One 23 Pro的特点 …...

MFC-TCP网络编程服务端-Socket

目录 1、通过Socket建立服务端&#xff1a; 2、UI设计&#xff1a; 3、代码的实现&#xff1a; &#xff08;1&#xff09;、CListenSocket类 &#xff08;2&#xff09;、CConnectSocket类 &#xff08;3&#xff09;、CTcpServerDlg类 1、通过Socket建立服务端&#xff…...

ChatGPT辅助下的小组学习

1 网上分享会-主题 1.9曾子曰&#xff1a;“慎终追远&#xff0c;民德归厚矣。” Master Zeng said:“Be circumspect in funerary services and continue sacrifices to the distant ancestors, and the virtue (de 德) of the common people will thrive.” 2 过程记录 听…...

Linux相关命令

切换root用户&#xff1a;sudo su 串口功能测试&#xff1a;cutecom 某某驱动查询&#xff1a;nvidia-smi #xxx-smi查询某某驱动 在线安装某某程序&#xff1a;apt install xxx 设置文件权限chmod 常用&#xff1a;chmod 777 sudo chmod 600 &#xff08;只有所有者…...

详解卷积神经网络结构

前言 卷积神经网络是以卷积层为主的深度网路结构&#xff0c;网络结构包括有卷积层、激活层、BN层、池化层、FC层、损失层等。卷积操作是对图像和滤波矩阵做内积&#xff08;元素相乘再求和&#xff09;的操作。 1. 卷积层 常见的卷积操作如下&#xff1a; 卷积操作解释图解…...

java读取pdf数据

目录 读取方式有两种: 方式一: 方式一所需要的maven依赖如下: 方式一读取的Java代码如下:<...

arcmap / arcgis 安装教程

ArcGIS 10.8 for Desktop 完整安装教程&#xff08;含win7/8/10 32/64位下载地址亲测可用汉化&#xff09; | 麻辣GIS (malagis.com) 关于GIS语言汉化包&#xff08;中文&#xff09;安装失败的解决办法_arcgis中文语言包_miumiuniya的博客-CSDN博客 检查安装路径&#xff1a;…...

CMake中的变量: 改变构建行为的变量

文章目录 变量名称描述BUILD_SHARED_LIBS全局标志&#xff0c;用于在启用时使add_library()创建共享库。 如果存在并且为true&#xff0c;则这将导致所有库被构建为共享库&#xff0c;除非该库被明确添加为静态库。这个变量通常作为option()添加到项目中&#xff0c;这样项目的…...

台式电脑怎么无损备份迁移系统到新硬盘(使用傲梅,免费的就可以)

文章目录 前言一、想要将源硬盘上的系统原封不动地迁移到新硬盘上二、准备工作2.具体步骤 总结 前言 半路接手公司一台台式电脑&#xff0c;C盘&#xff08;120g&#xff09;爆红&#xff0c;仅剩几个G&#xff0c;优化了几次&#xff0c;无果后。准备换一个大一点的增到500g。…...

【紫光同创国产FPGA教程】【PGC1/2KG第七章】7.数字钟实验例程

本原创教程由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处 适用于板卡型号&#xff1a; 紫光同创PGC1/2KG开发平台&#xff08;盘古1K/2K&#xff09; 一&#xff1a;盘古1K/2K开发板&#xff08;紫光同创PGC…...

【星海随笔】git的使用

1.在终端&#xff0c;检查git是否安装 git --version 2.没有安装的话去&#xff0c;官网&#xff0c;下载git 3.一直点下一步即可 4.安装后在终端检查git是否安装好 5.设置用户名和邮件地址(最好和GitHub的用户名/邮箱保持一致) git config --global user.name “自己的用户名”…...

安卓常见设计模式------装饰器模式(Kotlin版)

1. W1 是什么&#xff0c;什么是装饰器模式&#xff1f; 思想&#xff1a;动态地给对象添加额外的功能&#xff0c;通过将对象包装在一个装饰器类中&#xff0c;使装饰器类在不改变原始对象结构的情况下&#xff0c;扩展其功能。 2. W2 为什么&#xff0c;为什么需要使用装饰…...

将网站上的点击作为转化操作进行跟踪-官方指导文档

您可以使用转化跟踪功能&#xff0c;在用户点击您网站上的某个按钮或链接时进行跟踪。例如&#xff0c;您可以在用户点击“立即购买”按钮或点击您移动网站上的电话号码时进行跟踪。 本文介绍如何添加和修改转化跟踪代码&#xff0c;以便跟踪客户在您网站上的点击操作。如果希…...

Go相关命令说明

目录 Go相关命令说明go mod tidy &#xff1a;清理未使用依赖项&#xff0c;并更新模块文件主要功能好处 go clean -modcache &#xff1a;清除模块缓存go clean -testcache &#xff1a;清除测试缓存go test -v ./client &#xff1a;测试当前目录下client目录中的所有测试函数…...

3D全景技术,为我们打开全新宣传领域

随着科技的发展&#xff0c;3D全景技术正在融入我们的生活&#xff0c;这种全新视觉体验方式为我们打开了一扇全新的宣传领域&#xff0c;可以让我们多方位、多视角地探索各个行业&#xff0c;无论是对教育、商业、还是其他领域&#xff0c;都产生了深远的影响。 3D全景技术结合…...

【3D 图像分割】基于 Pytorch 的 VNet 3D 图像分割10(测试推理篇)

对于直接将裁剪的patch,一个个的放到训练好的模型中进行预测,这部分代码可以直接参考前面的训练部分就行了。其实说白了,就是验证部分。不使用dataloader的方法,也只需要修改少部分代码即可。 但是,这种方法是不end to end的。我们接下来要做的,就是将一个CT数组作为输入…...

PyCharm+Miniconda3安装配置教程

PyCharm是Python著名的Python集成开发环境&#xff08;IDE&#xff09; conda有Miniconda和Anaconda&#xff0c;前者应该是类似最小化版本&#xff0c;后者可能是功能更为强大的版本&#xff0c;我们这里安装Miniconda 按官方文档的说法conda相当于pip与virtualenv的结合&am…...

【慢SQL性能优化】 一条SQL的生命周期 | 京东物流技术团队

一、 一条简单SQL在MySQL执行过程 一张简单的图说明下&#xff0c;MySQL架构有哪些组件和组建间关系&#xff0c;接下来给大家用SQL语句分析 例如如下SQL语句 SELECT department_id FROM employee WHERE name Lucy AND age > 18 GROUP BY department_id其中name为索引&a…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器

一.自适应梯度算法Adagrad概述 Adagrad&#xff08;Adaptive Gradient Algorithm&#xff09;是一种自适应学习率的优化算法&#xff0c;由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率&#xff0c;适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

YSYX学习记录(八)

C语言&#xff0c;练习0&#xff1a; 先创建一个文件夹&#xff0c;我用的是物理机&#xff1a; 安装build-essential 练习1&#xff1a; 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后…...

React Native在HarmonyOS 5.0阅读类应用开发中的实践

一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强&#xff0c;React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 &#xff08;1&#xff09;使用React Native…...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)

船舶制造装配管理现状&#xff1a;装配工作依赖人工经验&#xff0c;装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书&#xff0c;但在实际执行中&#xff0c;工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...