拓冰建站拓冰建站
首页 / 资讯中心 / 正文

LVGL lv_style深度解析:从样式创建到优先级调优实战

做嵌入式GUI的老伙计应该都有这种体会LVGL上手很快画个按钮、放个label跑起来毫无压力但一旦要把界面做得像样问题就全堆到“样式”上了。这套号称“类CSS”的lv_style系统是LVGL里最灵活、也最容易让人踩坑的部分。这篇笔记我想把lv_style完整拆一遍分清楚它到底是什么、怎么用、为什么有些坑躲不开再结合实际代码演示从创建到调优的完整过程。无论你是在stm32上裸跑还是用PC模拟器快速调UI这篇都可以当一份能照着抄的参考。因为有不少从Web前端转过来的朋友会天然觉得LVGL样式和CSS长得一样但实际用起来才发现API完全不是一个路子所以这里面的坑其实比想象中多。我尽量不只贴API而是把它背后“为什么这么设计”讲清楚。做嵌入式最烦的就是文档里查不到某个行为背后的原因只能靠试错。我花了大把时间踩过这些坑这篇笔记就把值得记录的东西一次性说透。1. 样式系统整体设计为什么LVGL要搞出一套“样式”而不是直接改颜色1.1 样式到底是什么解决什么问题在讲lv_style之前先明确一个看起来像废话、但很多人根本没搞清楚的事LVGL里的控件长什么样并不是控件自身硬编码的而是由一个叫lv_style_t的结构体对象驱动的。这个结构体里面存的是“属性ID 属性值”的键值对集合比如背景颜色、边框宽度、圆角半径、字体、内边距全都以键值对的形式挂在样式对象里。控件本身不直接保存这些视觉参数而是通过一个链表把若干样式对象串起来渲染的时候再逐层查询、计算最终值。为什么要绕这么一圈直接给控件结构体加几个成员变量比如bg_color、border_width不是更简单吗如果你只做一个几十行的Demo确实更简单。但真实项目里界面少说几十个控件如果每个控件维护一套视觉参数改一个主题色就要满屏改代码状态切换按下、聚焦、禁用还要写一堆if else去手动赋值代码根本没法维护。样式系统的核心价值是“视觉参数与控件逻辑解耦”。你可以先把一组公共视觉参数定义成样式对象然后在不同控件上复用也可以针对同一个控件的不同状态挂不同样式渲染时自动切换开发者完全不用关心控件当前处于什么状态。这其实就是嵌入式世界里的CSS只不过CSS操作的是DOMLVGL操作的是lv_obj_t对象树。1.2 样式与CSS的对照从Web前端带过来的直觉对不对很多从Web前端转过来的朋友看到LVGL文档写着“类似CSS”就以为可以直接套用前端经验这个直觉一半是对的一半是陷阱。先说对的部分。选择器的思路确实一脉相承CSS有class和idLVGL有lv_obj_add_style()把样式对象挂到控件上CSS有伪类:hover、:activeLVGL有状态位LV_STATE_PRESSED、LV_STATE_FOCUSEDCSS有继承LVGL的字体、文字颜色等属性也可以沿对象树往下传递。这种设计让有前端背景的人学习曲线很平缓。再说陷阱。CSS的样式规则是声明式的浏览器帮你做层叠、优先级、继承的计算LVGL虽然也在运行时做类似计算但它更接近“手动管理”的模型。比如CSS里你写background-color: red浏览器知道这个颜色是红LVGL里你需要先lv_style_set_bg_color(style, lv_color_hex(0xFF0000))把属性写进样式对象再lv_obj_add_style(obj, style, 0)把样式挂上去渲染时才会生效。这中间多了明确的一步——把属性和值塞进一个结构体。另外CSS的优先级规则复杂但成体系LVGL的优先级规则相对简化但它把“添加顺序”“状态位”“局部样式”几个维度混在一起用起来反而容易迷惑。这个后面专门讲。1.3 样式进入渲染管线的路径搞清样式系统的位置对理解后面很多行为有帮助。LVGL的渲染流程大致是这样任何控件状态变化或样式变化都会调用lv_obj_invalidate()标记该区域为“脏区”下一次lv_timer_handler()事件循环触发时渲染器会对脏区重绘重绘之前要先遍历控件树把每个控件实际生效的样式属性算出来这个计算过程叫做“样式解析”style resolve。样式解析的核心函数是lv_obj_get_local_style_prop()和lv_obj_get_style_*这一组接口。它们并不是简单地从样式链表的第一个对象取值而是要按照优先级规则逐层查找先查局部样式local style再查通过lv_obj_add_style()添加的普通样式最后查主题默认样式。查到就返回查不到就用属性默认值。这个查找过程在底层有缓存但一旦属性变化相关缓存都会失效所以频繁修改样式属性确实会有性能开销。理解了这条链路你就明白一个经典问题为什么我改了控件的样式属性屏幕却没反应因为样式解析的结果可能在缓存里或者重绘事件没有被正确触发。手动调用lv_obj_report_style_change()可以强制刷新这招后面排障时经常用到。2. lv_style创建与生命周期从代码层面掌握“样式怎么活”2.1 创建样式与绑定控件的标准姿势LVGL里创建一个样式常规操作分三步定义样式对象、初始化、设置属性。static lv_style_t style_btn; lv_style_init(style_btn); lv_style_set_bg_color(style_btn, lv_color_hex(0x2196F3)); lv_style_set_radius(style_btn, 8); lv_style_set_pad_all(style_btn, 12);然后把它挂到控件上lv_obj_t *btn lv_btn_create(lv_scr_act()); lv_obj_add_style(btn, style_btn, 0);这里有两个细节容易出事。一是lv_style_t对象的存储位置如果你在函数内部定义了局部变量lv_style_t style_btn;函数退出后这个样式对象就被销毁了控件还拿着一个悬空指针接下来渲染必崩。所以要么定义成static要么用lv_malloc动态分配要么作为全局变量。这个坑我见过太多次新手老手都踩过。二是第三个参数selector。0表示这个样式对所有状态生效如果你传LV_STATE_PRESSED那这个样式只在按钮按下时才生效。这个参数很多人一开始忽略直接用0后面做状态切换时才发现样式根本不响应就是这里埋下的伏笔。2.2 属性设置API两条路别混着用LVGL从8.0开始实际上提供了两套设置属性的方式。第一套是往lv_style_t对象里写比如lv_style_set_bg_color(style, color)、lv_style_set_border_width(style, 2)第二套是直接往控件上写比如lv_obj_set_style_bg_color(btn, color, 0)、lv_obj_set_style_border_width(btn, 2, 0)。这两套API底层用的是同一套属性系统lv_obj_set_style_xxx本质上就是创建一个内部样式对象然后把它当作局部样式挂到控件上。区别在于第一套适合复用——你定义一次样式对象十个按钮共用第二套适合一次性设置——某个控件临时要改个颜色不值得专门定义一个样式对象。实际项目里的建议是公共视觉规范用第一套控件特殊覆盖用第二套。不要一会儿这套一会儿那套同一个控件同样的属性用两种方式混着写优先级一旦理解不清就会产生“我明明改了颜色为什么不生效”的诡异问题。2.3 样式对象的复制、重置与删除时机样式对象的生命周期管理不复杂但有几个操作容易被忽略。一个是lv_style_copy(dest, src)8.x以后更推荐用lv_style_reset()清理再复制因为样式对象内部可能有动态分配的资源直接复制容易内存泄漏。另一个是lv_obj_remove_style(obj, style, selector)把样式从控件上摘下来但不会销毁样式对象本身样式对象还可以给别的控件用。删除样式对象时要注意必须确保没有控件还在引用它。如果样式对象是静态定义的那它活在静态存储区不需要手动释放如果是动态分配的需要先用lv_obj_remove_style把所有引用摘掉再lv_free释放。LVGL没有引用计数机制不会自动帮你判断样式对象是否还在被使用这块全靠开发者自觉。想清空一个样式对象里的所有属性用lv_style_reset(style)。这个函数的好处是它会把对象重置到初始化状态之前动态分配的内部数据也会被清理。注意它不是简单的清零因为lv_style_t内部可能维护了一些堆上的属性数据直接memset会泄漏。3. 属性体系详解哪些属性最常调哪些参数最容易翻车3.1 位置、尺寸、内边距与变换属性样式属性在LVGL里分成好几大类最容易上手也最常用的是布局类属性pad_top、pad_bottom、pad_left、pad_right、pad_row、pad_column、margin等。pad是内容区到控件边缘的距离在容器布局中起着决定性作用。比如你用一个lv_obj做卡片容器想让里面的文字不贴边直接设lv_style_set_pad_all(style, 16)就完事。尺寸相关属性中width和height可以直接设置固定值也可以设置百分比比如lv_style_set_width(style, lv_pct(50))表示父容器宽度的50%。这里有个容易踩的坑lv_pct()返回的是带特殊标记的坐标值如果你在代码里直接写成lv_style_set_width(style, 50)那控件宽度就是50像素而不是50%。这个标记机制叫lv_coord_t的特殊编码很多人初始化时把lv_pct漏了导致界面比例完全不对。8.x以后还增加了transform_width、transform_height、transform_angle、transform_zoom这类变换属性。它们作用于控件自身不改布局尺寸但会改变绘制尺寸。例如一个图形按钮平时40x40按下时想让视觉上放大一圈就可以用transform_zoom实现。注意缩放的中心点是transform_pivot_x和transform_pivot_y确定的默认是控件左上角想以中心点缩放得单独设置这个细节官方示例里特别容易忽略。3.2 背景、边框、圆角与阴影背景类是另一块高频使用的属性组。bg_color设置背景主色bg_opa设置背景透明度bg_grad_color和bg_grad_dir设置渐变。这里有个特别容易翻车的点如果你只设置了bg_color没设置bg_opa默认透明度是LV_OPA_COVER也就是不透明这没问题但如果你设置bg_opa为0背景就完全透明此时连bg_color也不可见。很多人调透明背景时以为只改颜色里的alpha通道就行结果颜色值带alpha后依然不透明真正管用的是bg_opa这个独立属性。颜色值是颜色值透明度是透明度两套体系别混淆。边框属性组包括border_width、border_color、border_opa、border_side、border_post。其中border_side可以控制只画某一边比如LV_BORDER_SIDE_LEFT | LV_BORDER_SIDE_BOTTOM做列表分割线很实用。border_post决定边框是绘制在内容之上还是之后默认是在背景之上、内容之下设成true后边框会覆盖在子控件上面适合做高亮外框的效果。圆角radius的取值逻辑也值得说下。0是直角数值越大圆角越大。设成LV_RADIUS_CIRCLE实际是INT16_MAX就会变成圆形或胶囊形——矩形控件变成胶囊正方形控件变成圆。这个参数很多人只当装饰用其实它在很多地方影响布局文本控件的圆角会砍掉文字显示区域如果文字贴边圆角部分可能出现锯齿或裁切所以设置了圆角的内边距不能太小。阴影属性是shadow_width、shadow_color、shadow_opa、shadow_offset_x、shadow_offset_y、shadow_spread。阴影不开shadow_width就不会绘制shadow_width是阴影向外扩散的宽度不是总的半径。值得留心的是阴影绘制是软件层的模拟模糊在低端MCU上很消耗性能一个带大阴影的控件频繁重绘帧率可能直接降一半。3.3 渐变、透明度与混合效果渐变用于提升界面质感核心属性是bg_grad_color、bg_grad_dir、bg_grad_stop。方向支持LV_GRAD_DIR_NONE、LV_GRAD_DIR_VER垂直、LV_GRAD_DIR_HOR水平。想要对角线渐变8.x不支持只能靠嵌套控件或旋转实现。到9.x增加了更多渐变模式但仍然没有CSS里那种完整的多色渐变自由度。透明度属性除了背景的bg_opa还有控件整体透明度opa。注意opa影响的是控件整体以及它绘制出来的所有部分但默认情况下不影响子控件。想让一个容器整体半透明、里面的文字也跟着半透明必须用LV_OBJ_FLAG_OPA_SCALE标志或者9.x里的opa_layered。这个坑在实现毛玻璃、弹窗遮罩时特别常见你会遇到“父控件透明了子控件还是实心的”的尴尬。LVGL的opa_layered会把整个图层先渲染到离屏缓冲再按透明度绘制效果正常但开销大。混合模式blend_mode在8.3以后也给了几个选项比如LV_BLEND_MODE_ADDITIVE叠加变亮、LV_BLEND_MODE_MULTIPLY正片叠底。实际项目里用得不多但在做LED电平指示、复古电子风格界面时ADD模式效果非常惊艳。4. 样式叠加、优先级与状态管理整个lv_style里最值钱的部分4.1 样式作用域与叠加顺序一个控件可以同时挂多个样式对象。比如全局主题有个样式负责背景色你又在控件上挂了一个样式负责边框和圆角这两个样式可以共存各自管各自的属性互不干扰。真正冲突的情况是同一个属性在两个样式里都设置了这时候谁生效取决于优先级规则。LVGL里样式的作用域从低到高可以粗略分为三层主题默认样式theme- 普通样式通过lv_obj_add_style添加- 局部样式local style通过lv_obj_set_style_xxx产生。主题默认样式是控件创建时自动套用的。比如lv_btn_create之后按钮已经有背景色了这些值来自当前激活的主题lv_theme_default等。如果你什么都不做控件也有基本外观。但注意主题样式并不是硬编码在控件里的它本身也是样式对象只是由主题管理优先级最低。普通样式是指用lv_obj_add_style(btn, style, selector)挂载的样式。多个普通样式之间的优先级由添加顺序决定后添加的盖先添加的。如果同一个属性在两个样式里都有后挂的生效。局部样式是最特殊的一层它不是开发者手动创建的lv_style_t对象而是LVGL内部为lv_obj_set_style_xxx接口维护的一组“虚拟”样式。它的优先级高于所有普通样式和主题样式所以会产生一个非常实用的效果父级样式定公共规范控件局部覆盖个性参数。4.2 状态选择器按控件状态自动切换样式状态选择器是lv_obj_add_style的第三个参数也是LVGL里最有意思的设计。控件状态位包括LV_STATE_DEFAULT默认、LV_STATE_PRESSED按下、LV_STATE_FOCUSED聚焦、LV_STATE_HOVERED悬停、LV_STATE_DISABLED禁用、LV_STATE_CHECKED选中等这些状态位可以通过按位或组合。状态选择器的效果是控件处于某个状态时挂在该状态位上的样式自动生效状态消失时自动取消。例如一个按钮默认状态是蓝色背景按下状态是深蓝色背景只需要挂两个样式static lv_style_t style_btn_pr; lv_style_init(style_btn_pr); lv_style_set_bg_color(style_btn_pr, lv_color_hex(0x1565C0)); lv_obj_add_style(btn, style_btn, 0); /* 默认状态 */ lv_obj_add_style(btn, style_btn_pr, LV_STATE_PRESSED); /* 按下状态 */用户按下按钮时LVGL会自动切换样式不需要你在事件回调里手动改变颜色。这个机制对交互体验的提升非常大尤其做触屏应用时“按下有反馈”是及格线。状态之间还有一个隐性的叠加关系控件处于PRESSED状态时不只是搜索PRESSED位上的样式还会同时搜索DEFAULT位上的样式两者属性冲突时更“具体”的状态优先。这就引出了优先级规则的另一个维度。4.3 优先级规则别靠猜记住这张表LVGL的样式优先级从低到高我用一张表总结优先级层级判定依据低主题默认样式控件创建时由主题自动挂载中低普通样式lv_obj_add_style添加添加顺序越靠后优先级越高中状态选择器属性同时存在于DEFAULT和PRESSED等状态时非DEFAULT状态优先级更高中高控件类型预设样式某些控件内部会有预设样式优先级高于普通样式高局部样式lv_obj_set_style_xxx直接写入的属性最高内联覆盖局部样式中通过lv_obj_set_local_style_prop设置的属性8.x/ 9.x中统一为局部样式实际应用中最常用的优先级判断就是局部样式 状态样式 普通样式 主题样式。比如我想让一个按钮的宽度在按下时变大直接写lv_obj_set_style_width(btn, 80, LV_STATE_PRESSED)它就比任何普通样式里的宽度优先级都高。理解优先级不是为了应付考题而是排障时必须用的。你写了一个样式没生效第一反应不是怀疑LVGL有bug而是先查看到底哪些样式对象在作用域内、优先级是不是被覆盖了。LVGL有个调试函数lv_obj_dump_tree()能打印控件树和挂载的样式信息遇到“样式不生效”的玄学问题先把它跑一遍至少能少走一半弯路。5. 实操从0到一个可用按钮完整跑一遍样式流程5.1 需求定义与属性选型憋了一堆理论还是得落到代码上。假设我要做一个带状态的卡片式按钮默认状态是浅蓝背景、圆角8、无边框按下状态变成深蓝背景、并加一圈浅色描边同时按钮里放一个label显示文字文字颜色随状态联动。这个需求在嵌入式设备上非常典型比如温控器的模式切换按钮、仪器仪表的菜单按钮。先想清楚样式怎么拆。公共属性——比如字体、文字颜色——适合放在普通样式里因为多个按钮可以复用。状态相关属性——背景色、边框——放在对应的状态选择器上。这里的关键是拆分粒度不要把所有属性堆进一个大样式里否则复用性很差一个界面十几个按钮每个按钮样式都不同代码会膨胀得没法看。属性选型上背景用bg_colorbg_opa圆角用radius边框用border_widthborder_color内边距用pad_all文字间距用pad_row如果lable换行。这些足够满足绝大多数按钮场景。5.2 代码实现从样式定义到控件挂载下面是我的完整实现按功能块拆开讲。/* 1. 定义公共文本样式所有按钮的label共享 */ static lv_style_t style_btn_label; lv_style_init(style_btn_label); lv_style_set_text_color(style_btn_label, lv_color_hex(0xFFFFFF)); lv_style_set_text_font(style_btn_label, lv_font_montserrat_14); /* 2. 定义按钮默认样式 */ static lv_style_t style_btn_default; lv_style_init(style_btn_default); lv_style_set_bg_color(style_btn_default, lv_color_hex(0x2196F3)); lv_style_set_bg_opa(style_btn_default, LV_OPA_COVER); lv_style_set_radius(style_btn_default, 8); lv_style_set_border_width(style_btn_default, 0); lv_style_set_pad_all(style_btn_default, 12); /* 3. 定义按钮按下样式 */ static lv_style_t style_btn_pressed; lv_style_init(style_btn_pressed); lv_style_set_bg_color(style_btn_pressed, lv_color_hex(0x1565C0)); lv_style_set_border_width(style_btn_pressed, 2); lv_style_set_border_color(style_btn_pressed, lv_color_hex(0xBBDEFB)); lv_style_set_border_opa(style_btn_pressed, LV_OPA_COVER);这里有个细节按下样式里的border_width从默认样式的0变成了2。因为按下样式优先级高于默认样式所以会覆盖。但按下的边框会占掉内部空间导致label位置有一两个像素的偏移。如果不想让文字跳动可以考虑把默认样式也设成2像素边框颜色和背景一致做“隐藏式边框”按下时只改颜色。这个细节在做精密UI时非常重要视觉效果稳不稳就看这种细节控不控得住。接下来创建控件并挂样式lv_obj_t *btn lv_btn_create(lv_scr_act()); lv_obj_remove_style_all(btn); /* 去掉主题默认样式避免干扰 */ lv_obj_add_style(btn, style_btn_default, 0); lv_obj_add_style(btn, style_btn_pressed, LV_STATE_PRESSED); lv_obj_set_size(btn, 120, 48); lv_obj_center(btn); /* 添加label */ lv_obj_t *label lv_label_create(btn); lv_obj_add_style(label, style_btn_label, 0); lv_label_set_text(label, 确认); lv_obj_center(label);lv_obj_remove_style_all(btn)有一个很微妙的作用。按钮控件本身有自己的默认主题样式如果不移除主题样式会先给按钮一个背景色和圆角然后普通样式叠加实际效果可能和预期有偏差。移除之后再挂自己的样式能保证外观完全受控。这个函数对lv_btn、lv_switch、lv_checkbox这类控件特别重要因为它们自带主题外观。5.3 移植到stm32和PC模拟器时的差异同样的代码在PC模拟器上跑和stm32真机上跑表现会有不少差异。PC模拟器用的SDL渲染字体资源、颜色格式和真机不完全一致布局效果基本一样但有些细节要提前留意。第一是字体。PC上lv_font_montserrat_14能直接渲染因为模拟器环境有字体文件但stm32上如果没有把该字体的数组文件加入编译label显示的就是方块。更稳妥的做法是用LVGL内置的lv_font_default或者提前用字体转换工具生成小字库。第二是颜色深度。stm32常见配置是16位色深RGB565PC模拟器可能是32位。同一个lv_color_hex(0x2196F3)在两种环境下转换出来的实际颜色会有细微差别设计UI时最好以真机表现为准PC模拟器只能看结构看布局不能完全替代真机校色。第三是内存和刷新率。阴影、透明层叠、大圆角在PC上毫无压力但stm32上每加一个特效都可能在刷新率上付代价。我一般建议先在模拟器上确认交互结构和视觉逻辑再把特效逐个放到真机上看性能真机扛不住的特效就砍掉或者降低参数。6. 常见问题与排查技巧实录6.1 样式没生效先从这三步排查样式不生效是lv_style问题里出现频率最高的我自己排查过几十次归纳下来90%以上逃不出三类原因。先查优先级。看目标属性是否被更高优先级的样式覆盖。比如你用lv_obj_add_style设置了背景色但代码后面又调用了lv_obj_set_style_bg_color后者是局部样式优先级更高前者就会看起来“没生效”。用lv_obj_dump_tree()打印样式链就能看到每个样式的属性和优先级。再查生命期。样式对象是不是局部变量已经销毁尤其是定义在函数内、又没有加static的样式对象函数返回后控件的样式链还指着那块已经失效的栈内存轻则样式不生效重则硬件异常直接跑飞。这个问题在C语言项目里特别隐蔽因为编译不报错逻辑看起来也没问题。最后查状态。确认样式挂载时的selector是不是写对了。如果你把按下的样式挂在LV_STATE_DEFAULT上那控件永远只会显示默认态样式如果你把默认样式挂在LV_STATE_PRESSED上那控件平时看起来就是个“裸控件”。selector 0才是匹配所有状态这个细节一定要记住。6.2 控件重绘闪烁与性能问题嵌入式设备上样式越复杂重绘耗时就越高。闪烁的本质是渲染速度跟不上刷新率或者脏区计算不正确。遇到闪烁第一选择不是调渲染代码而是检查样式属性是否在每帧都被修改。常见误操作是在lv_timer回调里反复调用lv_obj_set_style_bg_color即使颜色值没变属性变更也会使缓存失效、触发重绘。可以先判断新旧值是否一样再决定要不要设置。阴影是另一大性能杀手。shadow_width实现的是带模糊的软阴影软件渲染下每一帧都要对阴影区域做多次模糊处理成本极高。如果真机性能吃紧可以考虑去掉阴影用1像素宽的半透明边框模拟硬阴影视觉效果接近渲染开销小一个数量级。还有圆角过大的问题。圆角裁剪在软件渲染里是按像素做的圆角半径越大边缘像素越多裁剪开销越高。如果控件尺寸不大、圆角值却设得很大性能影响很明显。折中方案是圆角控制在8像素以内或者干脆改成直角加内阴影既保持风格又有性能空间。6.3 版本差异8.x到9.x的坑与迁移注意现在不少项目还在用8.x但新的9.x也已经铺开了特别是PC模拟器上跑LVGL 9.x的人越来越多。9.x在样式系统上做了不少调整有些坑提前知道能省很多时间。9.x里lv_obj_set_style_xxx系列依然存在但内部实现有变化局部样式的优先级模型比以前更完整。以前8.x里局部样式几乎就是“最后加的覆盖一切”9.x里局部样式仍然支持状态选择器写法变化不大但底层存储更规范内存开销略有增加。另一个明显变化是属性的枚举重新编号了如果你在8.x项目里用过lv_style_set_prop()这种按属性枚举号设置的接口迁移到9.x时务必检查枚举值是否一致直接编译会报错但如果用了第三方库或旧代码里硬编码了属性值可能编译通过但运行效果完全不对。9.x还弱化了一些8.x里的旧接口比如lv_style_copy被移除推荐用lv_style_set_prop手动逐个复制或者直接用结构体赋值。迁移时如果遇到编译错误说找不到lv_style_copy别犹豫换成逐个属性赋值或重构样式复用逻辑。从9.x开始主题样式也做了重构主题对象不再是一个简单的默认样式链而是引入了更多回调机制。如果你之前有自定义主题的代码迁移到9.x时这部分改动比较大。我的建议是老项目继续用8.x稳着别动新项目可以直接从9.x起步PC模拟器调UI再移植到板子上这条路目前是最顺畅的。样式这块真正上手以后其实不复杂核心就是“先定义样式对象再挂到控件上用selector控制状态用优先级控制覆盖”。难的是你愿不愿意把每一个属性背后的存储、查找、刷新机制搞清楚。我个人的习惯是在做任何复杂UI之前先花半天时间把公共样式抽出来把状态选择器列全再动手写控件逻辑最后的效果和代码维护性都会好很多。遇到问题别急着怀疑框架先打印样式树看看到底谁在生效问题基本就解决了一半。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门