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

TDengine+OpenVINO+AIxBoard,助力时序数据分类

时间序列数据分析在工业,能源,医疗,交通,金融,零售等多个领域都有广泛应用。其中时间序列数据分类是分析时序数据的常见任务之一。本文将通过一个具体的案例,介绍 Intel 团队如何使用 TDengine 作为基础软件存储实验数据,并通过 TDengine 高效的查询能力在 OpenVINO 部署深度学习模型,最终在 AIxBoard 开发板上实时运行分类任务。

模型简介

近年来机器学习和深度学习在时序数据分类任务中取得了显著进展,HIVE-COTE 和 InceptionTime 模型都取得了不错的成果。相比基于 Nearest Neighbor 和 DTW 算法的 HIVE-COTE 模型,基于一维卷积 (Conv1D) 的 InceptionTime 模型成果更为显著,其在极大降低计算复杂度的基础上,还达到了与 HIVE-COTE 相当的分类精度。

如下图所示,Inception 模块是 InceptionTime 模型的基本组成模块,由多个一维卷积 (Conv1D) 操作堆叠,并于残差连接而成。

TDengine+OpenVINO+AIxBoard,助力时序数据分类 - TDengine Database 时序数据库

完整的 InceptionTime 模型由多个 Inception 模块连接而成。

TDengine+OpenVINO+AIxBoard,助力时序数据分类 - TDengine Database 时序数据库

关于 InceptionTime 的更多细节请参考论文:https://arxiv.org/abs/1909.04939。

数据集

TDengine+OpenVINO+AIxBoard,助力时序数据分类 - TDengine Database 时序数据库

本文采用的数据集来自 Time Series Classification Website,由 128 个时间序列分类任务组成。其中的 Wafer 数据集包含 1000 条训练数据和和 6164 条测试数据,每条数据均包含标签值和长度 152 的时间序列数据。数据通过程序提前写入到 TDengine 中。

这里描述的时序数据是晶片生成过程中同一个工具通过单个传感器记录的时间序列数据。下图展示了正常 (class 1) 和异常 (class 0) 两种标签对应的时序数据示例。

TDengine+OpenVINO+AIxBoard,助力时序数据分类 - TDengine Database 时序数据库

不难看出,这是一个标准的监督学习分类任务。我们希望找到一个模型,在每输入长度 152 的时序数据时,模型输出 0 或 1,以此判断输入时序数据对应的晶片在生成过程是否存在异常。

模型训练

本文中我们将使用 Wafer 数据集训练一个 InceptionTime 模型。训练得到的模型可以根据晶片生产过程中传感器记录的时序数据,判断某个晶片的生产过程是否存在异常。

InceptionTime 的作者开源了基于 tensorflow.keras 的实现,本文的模型代码基于 InceptionTime 开源版本并集成 TDengine 支持 GitHub - sangshuduo/InceptionTime: InceptionTime: Finding AlexNet for Time Series Classification。

首先加载 Python 库。

from os import path
import numpy as np
from sklearn import preprocessingfrom tensorflow import keras
from tensorflow.keras.layers import (Activation, Add, BatchNormalization, Concatenate,Conv1D, Dense, Input, GlobalAveragePooling1D, MaxPool1D
)from sqlalchemy import create_engine, text

然后使用 TDengine 的 SQLAlchemy 驱动加载 Wafer 数据集并进行预处理。

def readucr(conn, dbName, tableName):data = pd.read_sql(text("select * from " + dbName + "." + tableName),conn,)y = data[:, 0]x = data[:, 1:]return x, ydef load_data(db):engine = create_engine("taos://root:taosdata@localhost:6030/" + db)try:conn = engine.connect()except Exception as e:print(e)exit(1)if conn is not None:print("Connected to the TDengine ...")else:print("Failed to connect to taos")exit(1)x_train, y_train = readucr(conn, db + '_TRAIN.tsv')x_test, y_test = readucr(conn, db + '_TEST.tsv')n_classes = len(np.unique(y_train))enc = preprocessing.OneHotEncoder()y = np.concatenate((y_train, y_test), axis=0).reshape(-1,1)enc.fit(y)y_tr = enc.transform(y_train.reshape(-1,1)).toarray()y_te = enc.transform(y_test.reshape(-1,1)).toarray()x_tr, x_te = map(lambda x: x.reshape(x.shape[0], x.shape[1], 1), [x_train, x_test])return x_tr, y_tr, x_te, y_te, n_classesx_tr, y_tr, x_te, y_te, n_classes = load_data('Wafer')

再使用 tensorflow.keras 实现 IncetionTime,并创建模型。

def inception_module(input_tensor, filters, kernel_size, bottleneck_size,activation='relu', use_bottleneck=True):if use_bottleneck and int(input_tensor.shape[-1]) > 1:input_inception = Conv1D(filters=bottleneck_size, kernel_size=1, padding='same',activation=activation, use_bias=False)(input_tensor)else:input_inception = input_tensorkernel_size_s = [kernel_size // (2 ** i) for i in range(3)] # [40, 20, 10]conv_list = []for i in range(len(kernel_size_s)):conv = Conv1D(filters=filters, kernel_size=kernel_size_s[i],strides=1, padding='same', activation=activation,use_bias=False)(input_inception)conv_list.append(conv)max_pool = MaxPool1D(pool_size=3, strides=1, padding='same')(input_tensor)conv_6 = Conv1D(filters=filters, kernel_size=1, padding='same',activation=activation, use_bias=False)(max_pool)conv_list.append(conv_6)x = Concatenate(axis=2)(conv_list)x = BatchNormalization()(x)x = Activation(activation='relu')(x)return xdef shortcut_layer(input_tensor, output_tensor):y = Conv1D(filters=int(output_tensor.shape[-1]), kernel_size=1,padding='same', use_bias=False)(input_tensor)y = BatchNormalization()(y)x = Add()([y, output_tensor])x = Activation(activation='relu')(x)return xdef build_model(input_shape, n_classes, depth=6,filters=32, kernel_size=40, bottleneck_size=32,use_residual=True):input_layer = Input(input_shape)x = input_layerinput_res = input_layerfor d in range(depth):x = inception_module(x, filters, kernel_size, bottleneck_size)if use_residual and d % 3 == 2:x = shortcut_layer(input_res, x)input_res = xgap_layer = GlobalAveragePooling1D()(x)output_layer = Dense(n_classes, activation="softmax")(gap_layer)model = keras.Model(input_layer, output_layer)return modelmodel = build_model(x_tr.shape[1:], n_classes)model.compile(optimizer='adam',loss='categorical_crossentropy',metrics=['accuracy']
)

训练模型:

ckpt_path = path.sep.join(['.', 'models', 'inception_wafer.h5'])callbacks = [keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=20, min_lr=0.0001),keras.callbacks.EarlyStopping(monitor='val_loss', patience=20, verbose=1),keras.callbacks.ModelCheckpoint(filepath=ckpt_path, monitor='val_loss', save_best_only=True)
]batch_size = 32
epochs = 500history = model.fit(x_tr, y_tr, batch_size, epochs, verbose='auto', shuffle=True, validation_split=0.2, callbacks=callbacks)

简单显示一下训练过程:

metric = 'accuracy'
plt.figure(figsize=(10, 5))
plt.plot(history.history[metric])
plt.plot(history.history['val_'+metric])
plt.title("model " + metric)
plt.ylabel(metric, fontsize='large')
plt.xlabel('epoch', fontsize='large')
plt.legend(["train", "val"], loc="best")
plt.show()
plt.close()

TDengine+OpenVINO+AIxBoard,助力时序数据分类 - TDengine Database 时序数据库

使用测试数据验证模型的推理精度。

classifier = keras.models.load_model(ckpt_path)
test_loss, test_acc = classifier.evaluate(x_te, y_te)
print("Test accuracy: ", test_acc)
print("Test loss: ", test_loss)
193/193 [==============================] - 2s 11ms/step - loss: 0.0142 - accuracy: 0.9958
Test accuracy: 0.9957819581031799
Test loss: 0.014155667275190353

我们的模型在 Wafer 测试数据上取得了 99.58% 的精度。

模型转换

为了达成使用 OpenVINO Runtime 进行推理计算的目的,我们需要将 tensorflow 模型转换为 OpenVINO IR 格式。

from pathlib import Path
from openvino.tools import mo
from tensorflow import kerasmodel = keras.models.load_model('models/inception_wafer.h5')model_path = Path('models/inception.0_float')
model.save(model_path)model_dir = Path("ov")
model_dir.mkdir(exist_ok=True)
ir_path = Path("ov/inception.xml")input_shape = [1, 152, 1]if not ir_path.exists():print("Exporting TensorFlow model to IR...")ov_model = mo.convert_model(saved_model_dir=model_path, input_shape=input_shape, compress_to_fp16=True)serialize(ov_model, ir_path)
else:print(f"IR model {ir_path} already exists.")

转换完成后,生成的 IR 格式模型被存储为模型定义文件 inception.xml 和二进制文件 inception.bin。

模型部署

接下来我们在 AIxBoard 开发板上部署刚刚训练的 IncetpionTime 模型。首先将 inception.bin、inception.xml 和 Wafer_TEST.tsv 几个文件复制到 AIxBoard 板上。

加载 Python 库。

from pathlib import Path
import numpy as np
from openvino.runtime import Core, serialize

使用 OpenVINO 运行 Inception 模型。

ir_path = Path("inception.xml")
core = Core()
model = core.read_model(ir_path)
import ipywidgets as widgetsdevice = widgets.Dropdown(options=core.available_devices + ["AUTO"],value='AUTO',description='Device:',disabled=False
)device
0.995782

使用OpenVINO推理的精度跟tensorflow模型推理精度一致,同样达到了99.58%。我们在模型转换时将原模型数据格式压缩为 FP16,这一操作并没有导致精度下降。

性能测试

使用 OpenVINO 自带的 benchmark 工具可以轻松地在 AIxBoard 上进行性能测试。

benchmark_app -m inception.xml -hint latency -d CPU
[ INFO ] First inference took 8.59 ms
[Step 11/11] Dumping statistics report
[ INFO ] Execution Devices:['CPU']
[ INFO ] Count:            8683 iterations
[ INFO ] Duration:         60012.27 ms
[ INFO ] Latency:
[ INFO ]    Median:        6.44 ms
[ INFO ]    Average:       6.81 ms
[ INFO ]    Min:           6.34 ms
[ INFO ]    Max:           37.13 ms
[ INFO ] Throughput:   144.69 FPS
benchmark_app -m inception.xml -hint latency -d GPU
[ INFO ] First inference took 10.58 ms
[Step 11/11] Dumping statistics report
[ INFO ] Execution Devices:['GPU.0']
[ INFO ] Count:            7151 iterations
[ INFO ] Duration:         60026.34 ms
[ INFO ] Latency:
[ INFO ]    Median:        7.50 ms
[ INFO ]    Average:       8.23 ms
[ INFO ]    Min:           7.04 ms
[ INFO ]    Max:           21.78 ms
[ INFO ] Throughput:   119.13 FPS

从上面结果可以看出,使用AIxBoard的CPU运行InceptionTime模型推理,平均时长为6.81ms。使用集成 GPU 推理,平均时长为 8.23ms。

总结

本文介绍了如何利用 TDengine 支持时间序列数据的底层存储,以及如何通过分类模型 InceptionTime 在 UCR 时序数据集的 Wafer 分类任务上进行训练。最后,我们使用 OpenVINO 将该模型部署在 AIxBoard 开发板上,实现了高效的实时时序数据分类任务。希望本文的内容能够帮助大家在项目中利用 TDengine、OpenVINO 和 AIxBoard 来解决更多的时间序列分析问题。


关于 AIxBoard

英特尔开发者套件 AIxBoard(爱克斯开发板)是专为支持入门级边缘 AI 应用程序和设备而设计,能够满足人工智能学习、开发、实训等应用场景。该开发板是类树莓派的 x86 主机,可支持 Linux Ubuntu 及完整版 Windows 操作系统,板载一颗英特尔 4 核处理器,最高运行频率可达 2.9 GHz,且内置核显(iGPU),板载 64GB eMMC 存储及 LPDDR4x 2933MHz(4GB/6GB/8GB),内置蓝牙和 Wi-Fi 模组,支持 USB 3.0、HDMI 视频输出、3.5mm 音频接口,1000Mbps 以太网口,完全可把它作为一台 mini 小电脑来看待,且其可集成一块 Arduino Leonardo 单片机,可外拓各种传感器模块。此外,其接口与 Jetson Nano 载板兼容,GPIO 与树莓派兼容,能够最大限度地复用树莓派、Jetson Nano 等生态资源,无论是摄像头物体识别,3D 打印,还是 CNC 实时插补控制都能稳定运行,不仅可作为边缘计算引擎用于人工智能产品验证、开发,也可作为域控核心用于机器人产品开发。

产品链接:首页_蓝蛙智能

关于 TDengine

TDengine 核心是一款高性能、集群开源、云原生的时序数据库(Time Series Database,TSDB),专为物联网、工业互联网、电力、IT 运维等场景设计并优化,具有极强的弹性伸缩能力。同时它还带有内建的缓存、流式计算、数据订阅等系统功能,能大幅减少系统设计的复杂度,降低研发和运营成本,是一个高性能、分布式的物联网、工业大数据平台。当前 TDengine 主要提供两大版本,分别是支持私有化部署的 TDengine Enterprise 以及全托管的物联网、工业互联网云服务平台 TDengine Cloud,两者在开源时序数据库 TDengine OSS 的功能基础上有更多加强,用户可根据自身业务体量和需求进行版本选择。

关于作者

冯伟,英特尔软件架构师,16 年软件研发经验,涵盖浏览器、计算机视觉、虚拟机等多个领域。2015 年加入英特尔,近年来专注于边缘计算、深度学习模型落地,以及时序数据分析等方向。


了解更多 TDengine Database的具体细节,可在GitHub上查看相关源代码。

相关文章:

TDengine+OpenVINO+AIxBoard,助力时序数据分类

时间序列数据分析在工业,能源,医疗,交通,金融,零售等多个领域都有广泛应用。其中时间序列数据分类是分析时序数据的常见任务之一。本文将通过一个具体的案例,介绍 Intel 团队如何使用 TDengine 作为基础软件…...

设计模式——16. 迭代器模式

1. 说明 迭代器模式(Iterator Pattern)是一种行为型设计模式,它用于提供一种访问聚合对象(如列表、数组、集合等)元素的统一接口,而不需要了解底层数据结构的具体实现。迭代器模式将遍历聚合对象的操作封装在一个独立的迭代器对象中,这样可以隔离遍历算法和数据结构,使…...

flink redis connector需要防止包冲突

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 <dependency><groupId>org.apache.bahir</groupId><artifactId...

socket can查看详细信息 命令 ip -details -statistics link show can0

ip -details -statistics link show can0 ip -details link show can0 ip -statistics link show can0 也可以像第一行那样结合使用...

打造虚拟企业展厅,开启商务活动新时代

引言: 虚拟企业展厅是一种基于数字技术的全新商务模式&#xff0c;正在改变传统商务活动的方式&#xff0c;它比传统的企业展厅更便利&#xff0c;也更能凸显企业优势&#xff0c;展示企业风貌。 一&#xff0e;虚拟企业展厅的好处 1.打破地域限制 传统的商务活动通常需要参…...

03黑马店评-添加商户缓存和商户类型的缓存到Redis

商户查询缓存 什么是缓存 实际开发过程中数据量可以达到几千万,缓存可以作为避震器防止过高的数据访问猛冲系统,避免系统内的操作线程无法及时处理信息而瘫痪 缓存(Cache)就是数据交换的缓冲区(储存临时数据的地方),我们俗称的"缓存"实际就是缓冲区内的数据(一般从…...

LabVIEW玩转魔方

LabVIEW玩转魔方 使用LabVIEW创建一个3D魔方&#xff0c;并找出解谜题的秘密&#xff0c;给朋友留下深刻深刻的印象。游戏中内置的机制使每张脸都能独立转动&#xff0c;从而混合颜色。要解决难题&#xff0c;每个面必须是相同的纯色 魔方的奥秘在于它的简单性和不可解性。这是…...

大数据学习(1)-Hadoop

&&大数据学习&& &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 承认自己的无知&#xff0c;乃是开启智慧的大门 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一下博>主哦&#x…...

常用时序模型

常用时序模型 RNN (Recurrent Neural Network): 基本概念: RNN是一种可以处理序列数据的神经网络。它在每一时间步都接收一个新的输入,并将前一个时间步的隐藏状态作为额外的输入。问题: 它的主要问题是在处理长序列时遇到的梯度消失和梯度爆炸。这使得RNN难以捕获长期依赖关…...

阿里云/腾讯云国际站:私服服务器:什么是游戏虚拟服务器及用途讲解?

游戏虚拟服务器是一种新兴的技术&#xff0c;它可以为玩家提供更好的游戏体验。私服服务器它可以将游戏服务器的负载分散到多台服务器上&#xff0c;从而提高游戏的流畅度和稳定性。此外&#xff0c;游戏虚拟服务器还可以提供更多的游戏功能&#xff0c;比如游戏聊天室、游戏排…...

ssti 前置学习

python venv环境 可以把它想象成一个容器&#xff0c;该容器供你用来存放你的Python脚本以及安装各种Python第三方模块&#xff0c;容器里的环境和本机是完全分开的 创建venv环境安装flask #apt install python3.10-venv #cd /opt #python3 -m venv flask1 #cd /opt 选…...

uni-app:服务器端数据绘制echarts图标(renderjs解决手机端无法显示问题)

效果 代码 <template><view click"echarts.onClick" :prop"option" :change:prop"echarts.updateEcharts" id"echarts" class"echarts"></view> </template><script>export default {data()…...

Python集合魔法:解锁数据去重技巧

更多资料获取 &#x1f4da; 个人网站&#xff1a;涛哥聊Python 在Python编程的魔法世界中&#xff0c;有一种数据类型几乎被忽视&#xff0c;但却拥有强大的超能力&#xff0c;那就是集合&#xff08;Set&#xff09;。 集合是一种无序、唯一的数据类型&#xff0c;它以其独…...

flutter开发实战-inappwebview实现flutter与Javascript的交互JSBridge

flutter开发实战-inappwebview实现flutter与Javascript的交互JSBridge 在使用webview中&#xff0c;需要实现flutter与Javascript交互&#xff0c;在使用webview_flutter插件的时候&#xff0c;整理了一下webview与Javascript的交互JSBridge&#xff0c;具体可以查看 https:/…...

私有云盘:lamp部署nextcloud+高可用集群

目录 一、实验准备&#xff1a; 二、配置mariadb主从复制 三台主机下载mariadb 1&#xff09;主的操作 2&#xff09;从的操作 3&#xff09;测试数据是否同步 三、配置nfs让web服务挂载 1、安装 2、配置nfs服务器 3、配置web服务的httpd 4、测试 四、web 服务器 配…...

在线制作课程表

失业在家&#xff0c;开启一天一个应用的创作节奏&#xff0c;最近学了uniapp&#xff0c;特别想做点啥&#xff0c;正好家里小孩子要打印课程表&#xff0c;而且课程表还有调课的需求&#xff0c;就寻思做一个方便大家&#xff0c;到目前位置服务完全免费的&#xff0c;新鲜上…...

聊聊分布式架构06——[NIO入门]简单的Netty NIO示例

目录 Java NIO和Netty NIO比较 Java NIO&#xff1a; Netty&#xff1a; Netty NIO中的主要模块 Transport&#xff08;传输层&#xff09; Buffer&#xff08;缓冲区&#xff09; Codec&#xff08;编解码器&#xff09; Handler&#xff08;处理器&#xff09; Even…...

H5逆向之远程RPC

引言前一讲说过H5 怎么去抓包,逆向分析。其中说到RPC。这一节详细讲一下。有一种情况,JS 比较复杂,混淆的厉害。 这个时候就用到RPC。原理就是,hook web 浏览器,直接调用js 里边的方法。 Node 服务。为什么用到Node 服务,先来看下这架构 Node 对外提供各种接口,外部可以…...

解决Ubuntu18.04安装好搜狗输入法后无法打出中文的问题

首先下载安装 搜狗拼音输入法 &#xff0c;下载选择&#xff1a; x86_64 在ubuntu中设置 fcitx 最后发现安装好了&#xff0c;图标有了 &#xff0c;但是使用时不能输入中文&#xff0c;使用下面的命令解决&#xff1a; sudo apt install libqt5qml5 libqt5quick5 libqt5qu…...

Ubuntu LabelMe AI 识别

1.创建虚拟环境 conda create -n labelme python3.9 2.激活虚拟环境 conda activate labelme 3.安装labelme pip install pyqt5 -i https://pypi.tuna.tsinghua.edu.cn/simple pip install pillow -i https://pypi.tuna.tsinghua.edu.cn/simple pip install labelme -i ht…...

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法

深入浅出&#xff1a;JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中&#xff0c;随机数的生成看似简单&#xff0c;却隐藏着许多玄机。无论是生成密码、加密密钥&#xff0c;还是创建安全令牌&#xff0c;随机数的质量直接关系到系统的安全性。Jav…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

测试markdown--肇兴

day1&#xff1a; 1、去程&#xff1a;7:04 --11:32高铁 高铁右转上售票大厅2楼&#xff0c;穿过候车厅下一楼&#xff0c;上大巴车 &#xffe5;10/人 **2、到达&#xff1a;**12点多到达寨子&#xff0c;买门票&#xff0c;美团/抖音&#xff1a;&#xffe5;78人 3、中饭&a…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

Linux云原生安全:零信任架构与机密计算

Linux云原生安全&#xff1a;零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言&#xff1a;云原生安全的范式革命 随着云原生技术的普及&#xff0c;安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测&#xff0c;到2025年&#xff0c;零信任架构将成为超…...

今日科技热点速览

&#x1f525; 今日科技热点速览 &#x1f3ae; 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售&#xff0c;主打更强图形性能与沉浸式体验&#xff0c;支持多模态交互&#xff0c;受到全球玩家热捧 。 &#x1f916; 人工智能持续突破 DeepSeek-R1&…...

多种风格导航菜单 HTML 实现(附源码)

下面我将为您展示 6 种不同风格的导航菜单实现&#xff0c;每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

实现弹窗随键盘上移居中

实现弹窗随键盘上移的核心思路 在Android中&#xff0c;可以通过监听键盘的显示和隐藏事件&#xff0c;动态调整弹窗的位置。关键点在于获取键盘高度&#xff0c;并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...