LinuxAndroid: 旋转编码器input输入事件适配(旋转输入)
rk3588s: 旋转编码器input输入事件适配
基于Android 12 + kernel-5.10版本
参考文档:
https://blog.csdn.net/szembed/article/details/131551950
Linux 输入设备调试详解(零基础开发)Rotary_Encoder旋转编码器驱动 通用GPIO为例 挂载input输入子系统
https://source.android.google.cn/docs/core/interaction/input?hl=zh-cn
https://developer.android.google.cn/reference/android/support/wearable/input/RotaryEncoder
https://developer.android.google.cn/training/wearables/user-input/rotary-input?hl=zh-cn
旋转输入
某些 Wear OS 设备包含实体侧面旋钮。当用户旋转此类旋钮时,应用的当前视图会向上或向下滚动。此类输入称为“旋转输入”。
1,驱动层配置
配置设备树,使用已有的rotary_encoder.c驱动代码。
linux驱动设备树配置参考:
https://elixir.bootlin.com/linux/latest/source/drivers/input/misc/rotary_encoder.c
https://elixir.bootlin.com/linux/latest/source/arch/arm64/boot/dts/freescale/imx8mn-dimonoff-gateway-evk.dtsrotary: rotary-encoder {compatible = "rotary-encoder";pinctrl-names = "default";pinctrl-0 = <&pinctrl_rotary>;gpios = <&gpio5 12 GPIO_ACTIVE_LOW>, /* A */<&gpio5 13 GPIO_ACTIVE_LOW>; /* B */linux,axis = <0>; /* REL_X */rotary-encoder,relative-axis;};pinctrl_rotary: rotarygrp {fsl,pins = <MX8MN_IOMUXC_ECSPI2_MISO_GPIO5_IO12 0x00000156MX8MN_IOMUXC_ECSPI2_SS0_GPIO5_IO13 0x00000156>;};
2,framework层适配
上面驱动层配置好设备树后,通过getevent能看到rotary encoder事件。
但是,应用App层却收不到。
旋转编码器input输入事件和鼠标滚轮类似,设备上鼠标滚轮事件是正常的。
于是,先看鼠标滚轮事件。
鼠标滚轮:
$ adb shell getevent -lpi
add device 2: /dev/input/event2bus: 0003vendor 093aproduct 2533version 0111name: "Gaming Mouse"location: "usb-fc840000.usb-1/input0"id: ""version: 1.0.1events:KEY (0001): BTN_MOUSE BTN_RIGHT BTN_MIDDLE BTN_SIDE BTN_EXTRA REL (0002): REL_X REL_Y REL_WHEEL REL_WHEEL_HI_RES MSC (0004): MSC_SCAN input props:<none>adb shell dumpsys input2: Gaming MouseClasses: CURSOR | EXTERNALPath: /dev/input/event2Enabled: trueDescriptor: 922b2be403d5734c3dacd1c480566209f0f39e80Location: usb-fc840000.usb-1/input0ControllerNumber: 0UniqueId: Identifier: bus=0x0003, vendor=0x093a, product=0x2533, version=0x0111KeyLayoutFile: KeyCharacterMapFile: ConfigurationFile: VideoDevice: <none>01-11 03:13:33.710 569 663 I EventHub: New device: id=6, fd=181, path='/dev/input/event2', name='Gaming Mouse', classes=CURSOR | EXTERNAL, configuration='', keyLayout='', keyCharacterMap='', builtinKeyboard=false,
01-11 03:13:33.714 569 663 I InputReader: Device added: id=5, eventHubId=6, name='Gaming Mouse', descriptor='922b2be403d5734c3dacd1c480566209f0f39e80',sources=0x00002002
rotary encoder事件信息
rotary encoder事件信息
$ adb shell getevent -lpi
add device 2: /dev/input/event0bus: 0019vendor 0000product 0000version 0000name: "rotary" // 设备名是rotarylocation: ""id: ""version: 1.0.1events:REL (0002): REL_X input props:<none>
添加rotary.idc文件,用于framework层识别rotary encoder设备
framework层代码流程分析:
frameworks/native/services/inputflinger/reader/EventHub.cpp// Load the configuration file for the device.device->loadConfigurationLocked();// 要想rotary encoder旋转编码器被framework层识别到需要的条件:要有configuration文件且device.type为rotaryEncoder// See if this is a rotary encoder type device.String8 deviceType = String8();if (device->configuration &&device->configuration->tryGetProperty(String8("device.type"), deviceType)) {if (!deviceType.compare(String8("rotaryEncoder"))) {device->classes |= InputDeviceClass::ROTARY_ENCODER;}}// 根据设备名找configuration配置文件,adb shell getevent -lpi 查看到设备名是name: "rotary"// Try device name.return getInputDeviceConfigurationFilePathByName(deviceIdentifier.getCanonicalName(), type); 先在android源代码device目录grep -r "rotaryEncoder"搜索看看是否有类似配置。
搜索到virtio_input_rotary.idc,所以,执行如下操作验证framework层就能识别到旋转编码器设备了,
cp device/generic/goldfish/input/virtio_input_rotary.idc rotary.idc
adb push rotary.idc /system/usr/idc/
添加rotary.idc文件,虽然framework层识别到了rotary encoder设备,但是事件还是报不到App层。继续分析。(原因是:rotary encoder报的事件是 EV_REL REL_X,而RotaryEncoderInputMapper没有解析REL_X事件。因此,需要适配解析REL_X事件)
打开DEBUG_INBOUND_EVENT_DETAILS log开关后,验证旋转编码器的input事件,
能看到log时,说明App层就能收到事件,如果打印不出该log,则App层收不到事件。
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
#if DEBUG_INBOUND_EVENT_DETAILSALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, ""displayId=%" PRId32 ", policyFlags=0x%x, ""action=0x%x, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, ""edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, xCursorPosition=%f, ""yCursorPosition=%f, downTime=%" PRId64,args->id, args->eventTime, args->deviceId, args->source, args->displayId,args->policyFlags, args->action, args->actionButton, args->flags, args->metaState,args->buttonState, args->edgeFlags, args->xPrecision, args->yPrecision,args->xCursorPosition, args->yCursorPosition, args->downTime);for (uint32_t i = 0; i < args->pointerCount; i++) {ALOGD(" Pointer %d: id=%d, toolType=%d, ""x=%f, y=%f, pressure=%f, size=%f, ""touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, ""orientation=%f",i, args->pointerProperties[i].id, args->pointerProperties[i].toolType,args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));}
#endif
RotaryEncoderInputMapper解析旋转编码器的input事件数据
RotaryEncoderInputMapper::sync() 函数解析旋转编码器的input事件
$ adb shell getevent -l
add device 1: /dev/input/event0name: "rotary"/dev/input/event0: EV_REL REL_X 00000001
/dev/input/event0: EV_SYN SYN_REPORT 00000000 /dev/input/event0: EV_REL REL_X ffffffff
/dev/input/event0: EV_SYN SYN_REPORT 00000000
86 void RotaryEncoderInputMapper::process(const RawEvent* rawEvent) {
87 mRotaryEncoderScrollAccumulator.process(rawEvent);
88
89 if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
90 sync(rawEvent->when, rawEvent->readTime);
91 }
92 }
93
94 void RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readTime) {
95 PointerCoords pointerCoords;
96 pointerCoords.clear();
97
98 PointerProperties pointerProperties;
99 pointerProperties.clear();
100 pointerProperties.id = 0;
101 pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
102
+ ALOGI("RotaryEncoderInputMapper::sync");// scroll 返回的是0,导致下面notifyMotion走不到。需要在getRelativeVWheel函数里适配
103 float scroll = mRotaryEncoderScrollAccumulator.getRelativeVWheel();
104 bool scrolled = scroll != 0;
105
106 // This is not a pointer, so it's not associated with a display.
107 int32_t displayId = ADISPLAY_ID_NONE;
108
109 // Moving the rotary encoder should wake the device (if specified).
110 uint32_t policyFlags = 0;
111 if (scrolled && getDeviceContext().isExternal()) {
112 policyFlags |= POLICY_FLAG_WAKE;
113 }
114
115 if (mOrientation == DISPLAY_ORIENTATION_180) {
116 scroll = -scroll;
117 }
118
119 // Send motion event.
120 if (scrolled) {
121 int32_t metaState = getContext()->getGlobalMetaState();
122 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_SCROLL, scroll * mScalingFactor);// 添加x的值,否则,无论正向旋转还是反向旋转,x值都是0,导致应用App无法识别方向
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, scroll);
123
124 NotifyMotionArgs scrollArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
125 mSource, displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0,
126 0, metaState, /* buttonState */ 0, MotionClassification::NONE,
127 AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
128 &pointerCoords, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
129 AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {});
130 getListener()->notifyMotion(&scrollArgs);
+ ALOGI("RotaryEncoderInputMapper::sync notifyMotion");
131 }
132
133 mRotaryEncoderScrollAccumulator.finishSync();
134 }42 void CursorScrollAccumulator::process(const RawEvent* rawEvent) {
43 if (rawEvent->type == EV_REL) {
44 switch (rawEvent->code) {
45 case REL_WHEEL:
46 mRelWheel = rawEvent->value;
47 break;
48 case REL_HWHEEL:
49 mRelHWheel = rawEvent->value;
50 break;
+ case REL_X: // 由于自己的旋转编码器报的事件是REL_X,所以,需要添加该类型解析
+ mRelWheel = rawEvent->value;
+ break;
51 }
52 }
53 }
log: 旋转编码器正向旋转:x=1.000000 SOURCE_ROTARY_ENCODER = 0x00400000 <==> source=0x400000
04-07 05:47:32.041 575 667 D InputDispatcher: notifyMotion - id=b0eb158 eventTime=1947954629000, deviceId=4, source=0x400000, displayId=-1, policyFlags=0x0, action=0x8, actionButton=0x0, flags=0x0, metaState=0x0, buttonState=0x0, edgeFlags=0x0, xPrecision=0.000000, yPrecision=0.000000, xCursorPosition=nan, yCursorPosition=nan, downTime=0
04-07 05:47:32.041 575 667 D InputDispatcher: Pointer 0: id=0, toolType=0, x=1.000000, y=0.000000, pressure=0.000000, size=0.000000, touchMajor=0.000000, touchMinor=0.000000, toolMajor=0.000000, toolMinor=0.000000, orientation=0.000000
04-07 05:47:32.041 575 666 D InputDispatcher: dispatchMotion - eventTime=1947954629000, deviceId=4, source=0x400000, displayId=-1, policyFlags=0x62000000, action=0x8, actionButton=0x0, flags=0x0, metaState=0x0, buttonState=0x0,edgeFlags=0x0, xPrecision=0.000000, yPrecision=0.000000, downTime=0
04-07 05:47:32.041 575 666 D InputDispatcher: Pointer 0: id=0, toolType=0, x=1.000000, y=0.000000, pressure=0.000000, size=0.000000, touchMajor=0.000000, touchMinor=0.000000, toolMajor=0.000000, toolMinor=0.000000, orientation=0.000000旋转编码器反向旋转:x=-1.000000 SOURCE_ROTARY_ENCODER = 0x00400000 <==> source=0x400000
04-07 05:47:35.923 575 667 I InputReader: lqy111 RotaryEncoderInputMapper::sync
04-07 05:47:35.923 575 667 I InputReader: lqy111 RotaryEncoderInputMapper::sync: scroll:-1.000000
04-07 05:47:35.923 575 667 I InputReader: lqy111 RotaryEncoderInputMapper::sync notifyMotion
04-07 05:47:35.923 575 667 D InputDispatcher: notifyMotion - id=45c55f eventTime=1951836828000, deviceId=4, source=0x400000, displayId=-1, policyFlags=0x0, action=0x8, actionButton=0x0, flags=0x0, metaState=0x0, buttonState=0x0, edgeFlags=0x0, xPrecision=0.000000, yPrecision=0.000000, xCursorPosition=nan, yCursorPosition=nan, downTime=0
04-07 05:47:35.923 575 667 D InputDispatcher: Pointer 0: id=0, toolType=0, x=-1.000000, y=0.000000, pressure=0.000000, size=0.000000, touchMajor=0.000000, touchMinor=0.000000, toolMajor=0.000000, toolMinor=0.000000, orientation=0.000000
04-07 05:47:35.923 575 666 D InputDispatcher: dispatchMotion - eventTime=1951836828000, deviceId=4, source=0x400000, displayId=-1, policyFlags=0x62000000, action=0x8, actionButton=0x0, flags=0x0, metaState=0x0, buttonState=0x0,edgeFlags=0x0, xPrecision=0.000000, yPrecision=0.000000, downTime=0
04-07 05:47:35.923 575 666 D InputDispatcher: Pointer 0: id=0, toolType=0, x=-1.000000, y=0.000000, pressure=0.000000, size=0.000000, touchMajor=0.000000, touchMinor=0.000000, toolMajor=0.000000, toolMinor=0.000000, orientation=0.000000core/java/android/view/InputDevice.java: public static final int SOURCE_ROTARY_ENCODER = 0x00400000 | SOURCE_CLASS_NONE; <==> source=0x400000
App层监听旋转输入事件

App层监听旋转输入事件:
developer.android.google.cn/training/wearables/user-input/rotary-input
myView.setOnGenericMotionListener
onGenercMotion
或者 在Activity也可以。framework层监听旋转输入事件:
在NativeInputManager::interceptMotionBeforeQueueing()添加适配代码。
com_android_server_input_InputManagerService.cpp
void NativeInputManager::interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when,uint32_t& policyFlags) {
调试总结
make libinputreader -j3
make libinputflinger -j3 && make libinputflinger_base -j3while true; do echo "######$(date)######";adb logcat -b all | grep -i -E "EventHub|InputDispatcher|InputReader|WindowManager"; doneadb shell getevent
adb shell getevent -l
adb shell getevent -lip
adb shell dumpsys input
相关文章:
LinuxAndroid: 旋转编码器input输入事件适配(旋转输入)
rk3588s: 旋转编码器input输入事件适配 基于Android 12 kernel-5.10版本 参考文档: https://blog.csdn.net/szembed/article/details/131551950 Linux 输入设备调试详解(零基础开发)Rotary_Encoder旋转编码器驱动 通用GPIO为例 挂载input输…...
机器学习和深度学习-- 李宏毅(笔记与个人理解)Day10
Day 10 Genaral GUidance training Loss 不够的case Loss on Testing data over fitting 为什么over fitting 留到下下周哦~~ 期待 solve CNN卷积神经网络 Bias-Conplexiy Trade off cross Validation how to split? N-fold Cross Validation mismatch 这节课总体听下来比较…...
perl 交叉编译
前言 Perl是一种高级、通用、解释型、动态的编程语言。Perl设计的初衷是为了更好地处理文本处理任务,但随着时间的发展,现在它已经变成了一种强大的一般目的编程语言。Perl支持面向过程和面向对象的编程风格。 Perl的特点: 强大的字符串处…...
浅谈.版本管理工具
定义: 版本控制是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史,方便查看更改历史记录,备份以便恢复以前的版本的软件工程技术。 特点: 1、方便用于管理多人协同开发项目 2、并行开发,可实现跨区…...
【汇编语言实战】已知10个整数求最大值
C语言描述该程序流程: #include <stdio.h> int main() {int a[]{11,33,23,54,12,51,2,4,34,45};int maxa[0];for(int i1;i<9;i){if(a[i]>max){maxa[i];}}printf("%d",max); }汇编语言: include irvine32.inc .data arr dword 11…...
在 CentOS 7 上安装 Redis
在 CentOS 7 上安装 Redis 可以通过几个简单的步骤完成。以下是一种常用的方法: 更新系统: 在安装任何新软件之前,最好先更新系统的软件包列表,以确保安装的软件版本是最新的。可以使用以下命令来更新: sudo yum up…...
『51单片机』蜂鸣器
🚩 WRITE IN FRONT 🚩 🔎 介绍:"謓泽"正在路上朝着"攻城狮"方向"前进四" 🔎🏅 荣誉:2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2222年获评…...
计算机视觉 | 基于二值图像数字矩阵的距离变换算法
Hi,大家好,我是半亩花海。本实验基于 OpenCV 实现了二值图像数字矩阵的距离变换算法。首先生成一个 480x480 的黑色背景图像(定义黑色为0,白色为1),在其中随机选择了三个白色像素点作为距离变换的原点&…...
Arcgis windows webadaptor配置
注意windows下安装细节 1、电脑必须添加限定域名及dns后缀。 准备工作 a、安装webadaptor,获取jar文件 b、tomcat中部署两个jar,名字不相同,一个用server配置,一个用于portal配置 c、geoserver用来配置server d、geoscene用来配置…...
对接阿里云实时语音转文字的思路
将上述概念转化为详细代码需要一定的步骤。这里,我们将根据之前讨论的服务划分,创建一个简化的框架来模拟这个流程。注意,由于空间限制和简化目的,某些实现细节会被省略或简化,你可能需要根据实际情况进行调整。 1. 配…...
如何转行成为产品经理?
转行NPDP也是很合适的一条发展路径,之后从事新产品开发相关工作~ 一、什么是NPDP? NPDP 是产品经理国际资格认证,美国产品开发与管理协会(PDMA)发起的,是目前国际公认的唯一的新产品开发专业认证ÿ…...
SpringCloudAlibaba-整合nacos(二)
目录地址: SpringCloudAlibaba整合-CSDN博客 一、nacos服务部分 1.下载nacos,并执行数据库脚本:nacos-mysql.sql 2.修改配置文件,配置mysql 3.启动nacos ./startup.sh -m standalone 4.访问:http://127.0.0.1:884…...
STM32H7通用定时器计数功能的使用
目录 概述 1 STM32定时器介绍 1.1 认识通用定时器 1.2 通用定时器的特征 1.3 递增计数模式 1.4 时钟选择 2 STM32Cube配置定时器时钟 2.1 配置定时器参数 2.2 配置定时器时钟 3 STM32H7定时器使用 3.1 认识定时器的数据结构 3.2 计数功能实现 4 测试案例 4.1 代码…...
信息系统项目管理师0044:IT治理方法与标准(3信息系统治理—3.1 IT治理—3.1.4 IT治理方法与标准)
点击查看专栏目录 文章目录 3.1.4 IT治理方法与标准1. ITSS中1T服务治理 3.1.4 IT治理方法与标准 考虑到IT治理对组织战略目标达成的重要性,国内外各类机构持续研究并沉淀IT治理相关的最佳实践方法、定义相关标准,这里面比较典型的是我国信息技术服务标准…...
探索Linux:在VMware虚拟机上安装Linux操作系统
探索Linux:在VMware虚拟机上安装Linux操作系统 在计算机领域,Linux操作系统以其稳定性、安全性和自由开源的特点备受青睐。通过在VMware虚拟机上安装Linux,您可以轻松体验Linux操作系统的强大功能。本文将详细介绍在VMware虚拟机上安装Linux…...
JavaScript进阶6之函数式编程与ES6ESNext规范
函数式编程 柯里化currycurrycompose示例:简化版展开写: debug示例一:示例二: 模板字符串css in js方案 箭头函数问题 生成器 generator应用场景 反射 Reflect 柯里化curry compose是curry的应用 在 lodash/fp underscore ramba …...
AcWing 1381. 阶乘
解题思路 最后一位数相乘的变化。注意:为什么不是ss%10,如果12 * 15, 12的最后一位时2, * 1530,则为3,问题是12*15180,为8,两 者不符,说明ss%10中的10要多加0. import j…...
Leetcode 394. 字符串解码
心路历程: 这道题看到括号直接想到栈,五分钟新题直接秒了,一开始以为需要两个栈分别存储数字和非数字,后来发现一个栈就够了,思路如图: 这道题考察的应该是队栈这两种数据结构的转换,因为每次…...
LeetCode - 1702. 修改后的最大二进制字符串
文章目录 解析AC CODE 题目链接:LeetCode - 1702. 修改后的最大二进制字符串 解析 详细题解:贪心,简洁写法(Python/Java/C/Go/JS/Rust) 思路很牛b。 简单来说我们需要想办法将0配对,将其变为10࿰…...
虹科Pico汽车示波器 | 免拆诊断案例 | 2011款东风悦达起亚K5车发动机偶尔起动困难
一、故障现象 一辆2011款东风悦达起亚K5车,搭载G4KD发动机,累计行驶里程约为24.5万km。车主反映,第1次起动发动机时偶尔无法起动着机,第2次能够正常起动着机,但发动机故障灯异常点亮。为此在其他维修厂维修过…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...
关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...
uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...
