作者:京東科技 劉寧
一、前言
動態化使用 jue 語言(開發風格與 Vue 一致)開發,對于視圖的布局采用了標準的Flex 布局方式。對于列表類視圖,動態化提供了、、、等標簽,將子視圖的布局管理封裝到標簽中實現,業務只需要針對標簽簡單地設置相關屬性,即可實現列表類布局,大幅提升研發效率。同時動態化也支持絕對布局以及控制視圖的顯示和隱藏等功能,使之能勝任絕大多數業務布局場景。
在京東金融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(以下所說的默認都是指在動態化上的默認設置)。大部分設置看效果圖已經很清晰,故不做過多解釋。
flex-direction: row | row-reverse | column | column-reverse;
不同主軸的顯示效果:
以下是假設主軸是row的情況下,介紹其他屬性
2.2 flex-wrap
items在主軸上一行顯示不下的情況下的折行方式,默認值是nowrap。
flex-wrap: nowrap | wrap | wrap-reverse;
不同 flex-wrap 的顯示效果:
2.3 flex-flow
這是flex-direction和flex-wrap的簡寫方式,默認值是 column nowrap 。
flex-flow:column nowrap;
2.4 justify-content
定義item在主軸上的對齊方式,默認值是 flex-start 。
justify-content: flex-start | center | space-between | space-around | space-evenly | flex-end;
不同 justify-content 的顯示效果:
2.5 align-items
定義item在交叉軸上的對齊方式,默認是stretch。
align-items: flex-start | center | flex-end | stretch;
不同 align-items 的顯示效果:
2.6 align-content
定義多根主軸在在交叉軸上的對齊方式(單條主軸情況下這個屬性不生效)。
align-content: flex-start | flex-end | center | space-between | space-around | stretch;
這個剛接觸的同學不好理解,什么是主軸的對齊方法?拿 stretch 舉個例子
1/text?> 2/text?> 3/text?> 4/text?> 5/text?> 6/text?> 7/text?>
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: none | [ '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。
align-self: auto | flex-start | flex-end | center | stretch;
三、列表類布局
對于列表布局,動態化提供了、、、、、等標簽,對于多頁面管理提供、等標簽。業務僅通過修改標簽屬性,即可實現子視圖在列表內以不同的布局方式顯示。
以下是列表在京東金融中的幾個具體使用場景。
四、絕對布局
對于要脫離視圖文檔流,不受兄弟視圖布局影響,固定顯示于某個指定位置的子視圖,可用絕對布局,比如懸浮圖標等。
1.position: absolute;
.item { position: absolute; right:10px; bottom:20px; width:50px; height:50px; }
絕對定位是相對于父視圖的,使用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等眾多領域的綜合解決方案,我們有各個領域優秀的小伙伴共同前行,大家如果想深入了解某個領域的具體實現,可以隨時留言交流~!
審核編輯 黃宇
-
FLEX
+關注
關注
0文章
46瀏覽量
15215 -
鴻蒙
+關注
關注
57文章
2339瀏覽量
42804
發布評論請先 登錄
相關推薦
評論