如何使用GriRow和GriCol開(kāi)發(fā)自適應(yīng)布局
想了解更多關(guān)于開(kāi)源的內(nèi)容,請(qǐng)?jiān)L問(wèn):
場(chǎng)景說(shuō)明
開(kāi)發(fā)者經(jīng)常需要將一個(gè)應(yīng)用適配到不同的設(shè)備上運(yùn)行,比如手機(jī)、平板、折疊屏等等。為了保證用戶(hù)的瀏覽體驗(yàn),就需要根據(jù)不同設(shè)備的屏幕尺寸設(shè)計(jì)相應(yīng)的UI布局變化。常見(jiàn)的如閱讀軟件,在手機(jī)上顯示一頁(yè)內(nèi)容,在折疊屏上就可以顯示兩頁(yè)內(nèi)容,這樣才能給用戶(hù)更好的閱讀體驗(yàn)。
針對(duì)上述場(chǎng)景,OpenHarmony為開(kāi)發(fā)者提供了較為靈活的自適應(yīng)布局能力,本文即為大家做一個(gè)簡(jiǎn)單的介紹。
兩個(gè)重要的自適應(yīng)布局組件
使用OpenHarmony進(jìn)行自適應(yīng)布局的開(kāi)發(fā)離不開(kāi)以下兩個(gè)組件:GridRow、GridCol。
- GridRow用來(lái)將屏幕等分為特定列數(shù),并設(shè)置區(qū)分屏幕大小的臨界點(diǎn)(breakpoints),比如可以將屏幕列數(shù)根據(jù)屏幕大小劃分為:小屏設(shè)備4列,中屏8列,大屏12列(假設(shè)屏幕寬度<=520vp為小屏,520vp<屏幕寬度<=840vp為中屏,屏幕寬度>840vp為大屏)。那么就可以將520vp和840vp設(shè)置為臨界點(diǎn),當(dāng)屏幕尺寸由[0,520vp]區(qū)間跨越到[520vp,840vp]區(qū)間時(shí),屏幕劃分的列數(shù)也會(huì)由4列變?yōu)?列。臨界點(diǎn)的詳細(xì)介紹可參考GridRow指南。
- GridCol是Gridrow的子組件,可以根據(jù)屏幕大小設(shè)置其所覆蓋的列數(shù),那么GridCol中的子組件就可以相應(yīng)的隨屏幕的大小變化而變化。比如界面中有一個(gè)搜索框,我們希望其長(zhǎng)度在小屏設(shè)備上占2列,中屏設(shè)備占4列,大屏設(shè)備占8列,那么就可以將搜索框放在一個(gè)GridCol組件中,并設(shè)置GridCol組件在小、中、大設(shè)備上所占的列數(shù)分別為2、4、8。
可參考如下GridRow和GridCol的示意圖理解兩者之間的關(guān)系:
一個(gè)小示例
接下來(lái)用一個(gè)小示例來(lái)展示GridRow和GridCol是怎么協(xié)同實(shí)現(xiàn)自適應(yīng)布局的。
頁(yè)面元素
頁(yè)面中包含一個(gè)文本框,一個(gè)搜索框,一個(gè)滑動(dòng)條。
實(shí)現(xiàn)效果
- 小屏頁(yè)面時(shí),文本框、搜索框和滾動(dòng)條縱向排列,搜索框和滾動(dòng)條跟屏幕等寬,滾動(dòng)條一次顯示一個(gè)頁(yè)面。
- 當(dāng)拖動(dòng)頁(yè)面至中屏大小時(shí),文本框和搜索框在一行顯示,滾動(dòng)條一次顯示兩個(gè)頁(yè)面。
效果圖如下:
開(kāi)發(fā)步驟
接下來(lái)我們來(lái)看如何完成上述效果的開(kāi)發(fā)。開(kāi)發(fā)步驟中僅展示相關(guān)步驟代碼,全量代碼請(qǐng)參考完整代碼章節(jié)的內(nèi)容。
首先,使用GridRow對(duì)屏幕進(jìn)行劃分,本例按照不同屏幕大小進(jìn)行如下劃分:最小屏4列、小屏4列、中屏8列。設(shè)置最小屏到小屏,小屏到中屏兩個(gè)斷點(diǎn)(最小屏到小屏的斷點(diǎn)本例效果沒(méi)有演示)。
GridRow({
// 劃分屏幕:最小屏4列、小屏4列、中屏8列
columns:{xs: 4, sm: 4, md: 8},
gutter:{x:$r('app.float.gutter_home')},
// 設(shè)置最小屏到小屏,小屏到中屏兩個(gè)斷點(diǎn)
breakpoints: { value: ["320vp", "520vp"] }
}){
//...
}
.height("100%")
.width("100%")
.padding({top:10,bottom:10,left:10,right:10})
補(bǔ)充UI元素,將文本框、搜索框、滑動(dòng)條放在GridCol中,GridCol為GridRow的子組件。
GridRow({
// 劃分屏幕:最小屏4列、小屏4列、中屏8列
columns:{xs: 4, sm: 4, md: 8},
gutter:{x:$r('app.float.gutter_home')},
// 設(shè)置最小屏到小屏,小屏到中屏兩個(gè)斷點(diǎn)
breakpoints: { value: ["320vp", "520vp"] }
}){
// 文本框:首頁(yè)
GridCol(){
Row() {
Text("首頁(yè)")
//...
}
}
.height(56)
.margin({ bottom: 8})
// 搜索框
GridCol(){
Search({placeholder:'搜索...'})
//...
}
.height(48)
// 滾動(dòng)條
GridCol(){
Swiper(){
//...
}
.height(144)
.itemSpace(12)
.autoPlay(true)
.displayCount(this.currentbp == 'md' ? 2 : 1)
}
.height(144)
}
.height('100%')
.width('100%')
.padding({top:10,bottom:10,left:10,right:10})
分別設(shè)置文本框、搜索框、滾動(dòng)條所在GridCol在不同屏幕尺寸下所占的列數(shù)。
- 由于小屏?xí)r文本框、搜索框和滾動(dòng)條縱向排列,且搜索框和滾動(dòng)條跟屏幕等寬,所以可以讓三個(gè)元素占滿(mǎn)屏幕劃分的列數(shù),即4列。
- 中屏大小時(shí),文本框和搜索框在一行顯示,此時(shí)屏幕總共劃分為8列,所以可以讓文本框占2列,搜索框占6列(只要兩個(gè)元素占用的列數(shù)之和小于8都可以在一行顯示)。
- 中屏大小時(shí),滾動(dòng)條一次顯示兩個(gè)頁(yè)面。滾動(dòng)條占滿(mǎn)8列,然后通過(guò)displayCount屬性控制顯示的頁(yè)數(shù)。
代碼如下:
GridRow({
// 劃分屏幕:最小屏4列、小屏4列、中屏8列
columns:{xs: 4, sm: 4, md: 8},
gutter:{x:$r('app.float.gutter_home')},
// 設(shè)置最小屏到小屏,小屏到中屏兩個(gè)斷點(diǎn)
breakpoints: { value: ["320vp", "520vp"] }
}){
// 文本框:首頁(yè)
// 小屏sm:4列,中屏md:2列
GridCol({span:{ xs: 2, sm: 4, md: 2}}){
Row() {
Text("首頁(yè)")
//...
}
}
.height(56)
.margin({ bottom: 8})
// 搜索框
// 小屏sm:4列,中屏md:6列
GridCol({span:{xs: 2, sm: 4, md: 6}}){
Search({placeholder:'搜索...'})
//...
}
.height(48)
// 滾動(dòng)條
// 小屏sm:4列,中屏md:8列
GridCol({span:{ xs: 4, sm: 4, md:8 }}){
Swiper(){
//...
}
.height(144)
.itemSpace(12)
.autoPlay(true)
// 小屏?xí)r顯示一個(gè)頁(yè)面,中屏?xí)r顯示兩個(gè)頁(yè)面
.displayCount(this.currentbp == 'md' ? 2 : 1)
}
.height(144)
}
.height('100%')
.width('100%')
.padding({top:10,bottom:10,left:10,right:10})
// 獲取斷點(diǎn)
.onBreakpointChange((breakpoint)=>{
this.currentbp = breakpoint
})
完整代碼
本例完整代碼如下:
示例中用到的資源請(qǐng)?zhí)鎿Q為開(kāi)發(fā)者本地資源。
//AdaptiveUI.ets
@Entry
@Component
export struct HomePage{
@State currentbp: string = ''
build(){
GridRow({
// 劃分屏幕:最小屏4列、小屏4列、中屏8列
columns:{xs: 4, sm: 4, md: 8},
gutter:{x:24},
// 設(shè)置最小屏到小屏,小屏到中屏兩個(gè)斷點(diǎn)
breakpoints: { value: ["320vp", "520vp"] }
}){
// 文本框:首頁(yè)
// 小屏sm:4列,中屏md:2列
GridCol({span:{ xs: 2, sm: 4, md: 2}}){
Row() {
Text("首頁(yè)")
.fontSize(24)
.fontWeight(500)
.width('100%')
.margin({
top: 12,
bottom: 12,
left: 12
})
}
}
.height(56)
.margin({ bottom: 8})
// 搜索框
// 小屏sm:4列,中屏md:6列
GridCol({span:{xs: 2, sm: 4, md: 6}}){
Search({placeholder:'搜索...'})
.width('100%')
.height(40)
.margin({ bottom: 8})
.placeholderFont({ size: 16 })
}
.height(48)
// 滾動(dòng)條
// 小屏sm:4列,中屏md:8列
GridCol({span:{ xs: 4, sm: 4, md:8 }}){
Swiper(){
ForEach(SwiperList, (item: CardItem) => {
Stack({ alignContent: Alignment.TopStart }) {
Image(item.img)
.width('100%')
.height('100%')
.borderRadius(16)
.objectFit(ImageFit.Cover)
Column() {
Text(item.title)
.fontSize(16)
.fontColor(Color.White)
.margin({ bottom: 4 })
Text(item.info)
.fontSize(12)
.fontColor(Color.White)
.opacity(0.6)
}
.alignItems(HorizontalAlign.Start)
.margin({ top: 20, left: 14 })
}
}, item => JSON.stringify(item))
}
.height(144)
.itemSpace(12)
.autoPlay(true)
// 小屏?xí)r顯示一個(gè)頁(yè)面,中屏?xí)r顯示兩個(gè)頁(yè)面
.displayCount(this.currentbp == 'md' ? 2 : 1)
}
.height(144)
}
.height('100%')
.width('100%')
.padding({top:10,bottom:10,left:10,right:10})
// 獲取斷點(diǎn)
.onBreakpointChange((breakpoint)=>{
this.currentbp = breakpoint
})
}
}