鸿蒙跨端实践 - 布局方案介绍
作者:京东科技 刘宁
一、前言
动态化使用 jue 语言(开发风格与 Vue 一致)开发,对于视图的布局采用了标准的 Flex 布局方式。对于列表类视图,动态化提供了<scroll>、<slider>、<recycle-list>、<waterfall>等标签,将子视图的布局管理封装到标签中实现,业务只需要针对标签简单地设置相关属性,即可实现列表类布局,大幅提升研发效率。同时动态化也支持绝对布局以及控制视图的显示和隐藏等功能,使之能胜任绝大多数业务布局场景。
在京东金融 App 使用动态化方案适配鸿蒙系统的过程中发现,鸿蒙提供的 Flex 布局和 W3C 标准的 Flex 布局,在一些常用的默认属性上表现不一致。导致原本在 Android、iOS 和 web 端显示正确的 UI 布局在鸿蒙端显示错乱。经与鸿蒙的工程师沟通,华为出于多方面的考虑,并没有调整的计划。故而决定废弃鸿蒙提供的布局方式,使用标准的 Flex 布局,引入 yoga 布局库(由 FaceBook 提供一个开源的跨平台的布局引擎),yoga 库的功能是解析视图的 CSS 设置,获取视图的位置(x,y)和尺寸(width,height),直接设置给视图,确保视图的正确显示。
下面详细介绍一下鸿蒙业务在实际开发中高频使用的布局方式,这些布局方式可以涵盖 95%以上的业务布局场景。
二、Flex 布局
在动态化上,视图默认使用的就是 flex 布局方式(相当于默认设置了 display:flex;)。
1. 概念
容器(container) :是指开启了 flex 布局的视图
项目(item): 是指容器的子视图
容器(container)有几个重要的概念
主轴: item 在 container 上的排列方向,主轴开始的位置叫 main start, 主轴结束的位置叫 main end;item 在主轴上占据的尺寸叫 main size
交叉轴: 与主轴垂直的方向;交叉轴开始的位置叫 cross start ,交叉轴结束的位置叫 cross end;item 在交叉轴上占据的尺寸叫 cross size
2. 容器(container)的常用属性
2.1 flex-direction
设置主轴方向,默认值是 column(以下所说的默认都是指在动态化上的默认设置)。大部分设置看效果图已经很清晰,故不做过多解释。
不同主轴的显示效果:
以下是假设主轴是 row 的情况下,介绍其他属性
2.2 flex-wrap
items 在主轴上一行显示不下的情况下的折行方式,默认值是 nowrap。
不同 flex-wrap 的显示效果:
2.3 flex-flow
这是 flex-direction 和 flex-wrap 的简写方式,默认值是 column nowrap 。
2.4 justify-content
定义 item 在主轴上的对齐方式,默认值是 flex-start 。
不同 justify-content 的显示效果:
2.5 align-items
定义 item 在交叉轴上的对齐方式,默认是 stretch。
不同 align-items 的显示效果:
2.6 align-content
定义多根主轴在在交叉轴上的对齐方式(单条主轴情况下这个属性不生效)。
这个刚接触的同学不好理解,什么是主轴的对齐方法?拿 stretch 举个例子
UI 显示效果如下:
蓝色为 container,主轴为 row(flex-direction: row;),支持折行(flex-wrap: wrap;),多行主轴的对齐方式为 stretch(align-content: stretch;),此例中有两条主轴,因此两条主轴的高将充满 container 的高;
第一条主轴中包含视图 1,2,3,4,5。第二条主轴包含视图 6,7。单条主轴的在竖直方向上的对齐方式为从上到下(align-items: flex-start;),而 1 改变了所在主轴的交叉轴方向的对齐方式为从下到上(align-self: flex-end),因此 1 底部对齐,2,3,4,5 顶部对齐。 6,7 同理。
3.项目(item)常用属性
3.1 flex-grow
item 在 container 中的主轴方向上的剩余空间中占据的分配比例系数(默认值为 0,即不放大)。初次听起来不理解,看个例子就清楚了:
UI 效果如下:
由于 item1 宽度为 100px,item2 宽度为 50px, container 在主轴剩余空间为 300-100-50 = 150px;根据 flex-grow 的设置,item1 占 1/3,为 50px;item2 占 2/3,为 100px;故最终 item1 与 item2 平分了主轴空间。
3.2 flex-shrink
item 在 container 中的主轴方向上的缩小系数,默认值为 0,即空间不够,也不缩小。这个属性仅在 container 无法承载 item 的情况下才生效。举个例子:
由于 item1 与 item2 的宽度一致,item1 压缩的比例为 item1 的 2 倍,因此最终 item1 的尺寸是 item 的 2 倍。UI 效果如下:
3.3 flex-basis
container 在分配多余空间之前,item 在主轴方向上占据的空间,默认值是 auto,即本身默认的大小。举个例子:
由于 item1 宽度为 150px;flex-basis 默认为 auto,因此默认为 150px; item2 宽度为 150px,但 flex-basis 为 50px,因此在计算所占空间时按 50px 算,这样在 container 的主轴上的剩余空间为 300 - 150 - 50 = 100px;item1 和 item2 的 flex-grow 都为 1,因此,各站剩余空间的 1/2。因此 item1 的宽为 150+50=200px;item2 为 50+50=100px;UI 效果如下:
3.4 flex
这是一个复合属性,是 flex-grow、flex-shrink 、flex-basis 的简写形式。
flex:1 等价于 flex: 1 1 auto; flex:none 等价于 flex : 0 0 auto;
注意:在使用的时候经常习惯性的使用 flex:1;很多时候大家想要的仅仅是 flex-grow:1;如非必要,尽量不要写无用约束。
3.4 align-self
item 在交叉轴上的布局方式由 container 的 align-items 属性设置,如果某个 item 想使用别的对齐方式,可以给 item 设置 align-self,就会覆盖 container 上的设置。默认值为 auto,表示按照 container 的 align-items 样式,如果没有父元素,则用 stretch。
三、列表类布局
对于列表布局,动态化提供了<scroll>、<slider>、<recycle-list>、<waterfall>、<lego-container>、<feed-container>等标签,对于多页面管理提供<page-container>、<tab-bar>等标签。业务仅通过修改标签属性,即可实现子视图在列表内以不同的布局方式显示。
以下是列表在京东金融中的几个具体使用场景。
四、绝对布局
对于要脱离视图文档流,不受兄弟视图布局影响,固定显示于某个指定位置的子视图,可用绝对布局,比如悬浮图标等。
1.position: absolute;
绝对定位是相对于父视图的,使用 left、right、top、bottom、width 和 height 来设置。
五、视图的显示和隐藏控制
在开发过冲中,经常会用让视图显示或者隐藏的需求。在动态化上提供如下几种方式来实现,可根据具体的使用场景选择使用哪一种。
1. v-if = true | false
这种隐藏方式最彻底,会销毁视图,不占据任何空间,后面如果再需要显示,则元素会重新创建。这种显示/隐藏元素比较彻底,会带来的内存的开辟和回收,一般用在只显示某一种具体元素的场景下使用。
2. display : flex | none;
这种隐藏方式,不会销毁元素,仍存在于 HTML 文档中,仍可通过 JavaScript 访问和操作,但元素会从布局上移除,元素不占据任何空间,不能参与用户交互事件。
3. v-show=true | false ;
在动态化上会被解析为 display:flex | none;
4. visibility : visible | hidden;
这种隐藏方式,只是从视觉上移除,仍然参与页面布局,保留元素的位置,无法参与用户交互。
5. opacity : 0 | 1
这种方式是设置视图的透明度,在透明度为 0 时,完全隐藏,视图仍参与页面布局,保留元素位置,在 Web、Android 和 Harmony 端仍可参与用户交互,但在 iOS 端只有当透明度大于 0.1 时可以。这个一般用作视图显示或者消失的过度动画。
6. overflow: hidden | visible
****规定视图超出父视图边界时如何处理。在 Harmony/iOS/Web 端默认值为 visible,即超出可见,Android 端默认值为 hidden,超出不可见。如果设置了 visible ,如果视图本身添加了事件,对于超出父视图的部分,Harmony/Android/Web 仍可响应事件,在 iOS 端无法响应事件。
多说一句:动态化作为一个跨端框架,会最大限度的抹平各端差异,但在有些系统特性上,还是会保持各端的默认实现。业务在使用时,可根据具体情况,具体处理。
六、布局问题分析
动态化在京东金融 App 里已经大范围使用,截止到 2024 年 8 月 26 日已有 200+ 页面,200+卡片使用动态化技术开发。在这个过程中与各业务一起解决了很多布局相关的问题。选择几个有共性的问题分享给大家。
1. 为什么没有给 item 设置宽,item 也没有子视图,但 item 却有宽呢?
因为在动态化上视图的主轴默认是 column,align-items 默认是 stretch,因此 item 会填充父容器。
2. 为什么给 item 设置了宽,item 显示的宽却是别的值?
这个情况就要具体分析了,如果你在设置了宽的情况下,还设置了 flex:1;或者 flex-grow:1;再或者 flex-shrink 等,item 可能会被拉伸或者压缩,导致宽度变化。也有可能其他 item 的缩放级别更高,优先满足其他 item 的显示,当前 item 的尺寸也有可能被压缩。
3. item 既可以从 container 的设置中获取尺寸,也可以被子 item 撑开获取尺寸,也可以自己设置尺寸,还可以被拉伸或者压缩。那 item 的尺寸最终是由谁来决定呢?
首先假设主轴为 column。container 的 align-items 默认是 stretch,因此 item 会填充父容器。但是如果 item 自身设置了宽,则会覆盖 stretch。如果 item 的 align-self 不是 stretch,本身也没有设置宽,但子视图有宽,可以被子视图撑开。如果自身设置了宽,则不受子视图影响。
在主轴方向上 : 如果 item 本身没有设置高,但子视图有高,则可以子视图撑开。如果自身设置了高,就不受子视图的影响。如果自身设置了 flex-grow:1 则会填充父视图,同样如果设置了 flex-shrink:1,空间不足则被压缩,都会覆盖自身设置的高。
最后:max-width 和 max-height 优先级最高,如果触发边界条件,就会生效,从而覆盖其他设置。
4. 如何让 n 个 item 按照比例占据 container 的主轴空间?
对于这类比例问题,可设置所有 item 的 flex-basis 为 0,把自身空间全部收回,通过 flex-grow 分配对应比例即可。
5. 对于一个复杂的视图,我该如何下手?
其实越是复杂的视图实现的方式就越多,没有固定的、标准的实现方式。下面说一下我的一点看法,我将视图分为以下几种场景:
1.如果视图自身知道具体的宽高,那就直接设置,这是效率最高的方式。
2.如果视图自身不知道宽高,但是父视图有固定的尺寸,想办法从父视图获取。
3.如果视图自身不知道宽高,但子视图有宽高,则可以靠子视图撑开。
4.如果必要,使用 max-width 和 max-height 做最高层级的条件约束。
其他的情况一般都是以上四种情况的组合。只要理清视图间约束条件,排除约束矛盾点,确定优先级。先建立清晰的布局思路,明确视图的尺寸来源,按照既定思路布局,不写无效约束,一切就清晰自然。
七、写在最后
动态化是一个涉及 JavaScript、iOS、Android、Harmony、Java、Vue、Node、Webpack 等众多领域的综合解决方案,我们有各个领域优秀的小伙伴共同前行,大家如果想深入了解某个领域的具体实现,可以随时留言交流~!
版权声明: 本文为 InfoQ 作者【京东科技开发者】的原创文章。
原文链接:【http://xie.infoq.cn/article/344209d02dff5eb788ec3eff6】。文章转载请联系作者。
评论