如何使用GriRow和GriCol开发自适应布局 原创 精华

野生菌君
发布于 2023-11-15 12:14
浏览
4收藏

场景说明

开发者经常需要将一个应用适配到不同的设备上运行,比如手机、平板、折叠屏等等。为了保证用户的浏览体验,就需要根据不同设备的屏幕尺寸设计相应的UI布局变化。常见的如阅读软件,在手机上显示一页内容,在折叠屏上就可以显示两页内容,这样才能给用户更好的阅读体验。
针对上述场景,OpenHarmony为开发者提供了较为灵活的自适应布局能力,本文即为大家做一个简单的介绍。

两个重要的自适应布局组件

使用OpenHarmony进行自适应布局的开发离不开以下两个组件:GridRow、GridCol。

  • GridRow用来将屏幕等分为特定列数,并设置区分屏幕大小的临界点(breakpoints),比如可以将屏幕列数根据屏幕大小划分为:小屏设备4列,中屏8列,大屏12列(假设屏幕宽度<=520vp为小屏,520vp<屏幕宽度<=840vp为中屏,屏幕宽度>840vp为大屏)。那么就可以将520vp和840vp设置为临界点,当屏幕尺寸由[0,520vp]区间跨越到[520vp,840vp]区间时,屏幕划分的列数也会由4列变为8列。临界点的详细介绍可参考GridRow指南

  • GridCol是Gridrow的子组件,可以根据屏幕大小设置其所覆盖的列数,那么GridCol中的子组件就可以相应的随屏幕的大小变化而变化。比如界面中有一个搜索框,我们希望其长度在小屏设备上占2列,中屏设备占4列,大屏设备占8列,那么就可以将搜索框放在一个GridCol组件中,并设置GridCol组件在小、中、大设备上所占的列数分别为2、4、8。

    可参考如下GridRow和GridCol的示意图理解两者之间的关系:
    如何使用GriRow和GriCol开发自适应布局-鸿蒙开发者社区

一个小示例

接下来用一个小示例来展示GridRow和GridCol是怎么协同实现自适应布局的。

页面元素

页面中包含一个文本框,一个搜索框,一个滑动条。

实现效果

  • 小屏页面时,文本框、搜索框和滚动条纵向排列,搜索框和滚动条跟屏幕等宽,滚动条一次显示一个页面。
  • 当拖动页面至中屏大小时,文本框和搜索框在一行显示,滚动条一次显示两个页面。

效果图如下:
如何使用GriRow和GriCol开发自适应布局-鸿蒙开发者社区

开发步骤

接下来我们来看如何完成上述效果的开发。开发步骤中仅展示相关步骤代码,全量代码请参考完整代码章节的内容。

  1. 首先,使用GridRow对屏幕进行划分,本例按照不同屏幕大小进行如下划分:最小屏4列、小屏4列、中屏8列。设置最小屏到小屏,小屏到中屏两个断点(最小屏到小屏的断点本例效果没有演示)

    GridRow({
      // 划分屏幕:最小屏4列、小屏4列、中屏8列
      columns:{xs: 4, sm: 4, md: 8},
      gutter:{x:$r('app.float.gutter_home')},
      // 设置最小屏到小屏,小屏到中屏两个断点
      breakpoints: { value: ["320vp", "520vp"] }
    }){
    //...
    }
    .height("100%")
    .width("100%")
    .padding({top:10,bottom:10,left:10,right:10})
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
    • 11.
    • 12.
  2. 补充UI元素,将文本框、搜索框、滑动条放在GridCol中,GridCol为GridRow的子组件。

    GridRow({
      // 划分屏幕:最小屏4列、小屏4列、中屏8列
      columns:{xs: 4, sm: 4, md: 8},
      gutter:{x:$r('app.float.gutter_home')},
      // 设置最小屏到小屏,小屏到中屏两个断点
      breakpoints: { value: ["320vp", "520vp"] }
    }){
    
      // 文本框:首页
      GridCol(){
        Row() {
          Text("首页")
           //...
        }
      }
      .height(56)
      .margin({ bottom: 8})
    
      // 搜索框
      GridCol(){
        Search({placeholder:'搜索...'})
          //...
      }
      .height(48)
    
      // 滚动条
      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})
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
    • 11.
    • 12.
    • 13.
    • 14.
    • 15.
    • 16.
    • 17.
    • 18.
    • 19.
    • 20.
    • 21.
    • 22.
    • 23.
    • 24.
    • 25.
    • 26.
    • 27.
    • 28.
    • 29.
    • 30.
    • 31.
    • 32.
    • 33.
    • 34.
    • 35.
    • 36.
    • 37.
    • 38.
    • 39.
    • 40.
  3. 分别设置文本框、搜索框、滚动条所在GridCol在不同屏幕尺寸下所占的列数。

    • 由于小屏时文本框、搜索框和滚动条纵向排列,且搜索框和滚动条跟屏幕等宽,所以可以让三个元素占满屏幕划分的列数,即4列。
    • 中屏大小时,文本框和搜索框在一行显示,此时屏幕总共划分为8列,所以可以让文本框占2列,搜索框占6列(只要两个元素占用的列数之和小于8都可以在一行显示)。
    • 中屏大小时,滚动条一次显示两个页面。滚动条占满8列,然后通过displayCount属性控制显示的页数。

    代码如下:

    GridRow({
      // 划分屏幕:最小屏4列、小屏4列、中屏8列
      columns:{xs: 4, sm: 4, md: 8},
      gutter:{x:$r('app.float.gutter_home')},
      // 设置最小屏到小屏,小屏到中屏两个断点
      breakpoints: { value: ["320vp", "520vp"] }
    }){
    
      // 文本框:首页
      // 小屏sm:4列,中屏md:2列
      GridCol({span:{ xs: 2, sm: 4, md: 2}}){
        Row() {
          Text("首页")
           //...
        }
      }
      .height(56)
      .margin({ bottom: 8})
    
      // 搜索框
      // 小屏sm:4列,中屏md:6列
      GridCol({span:{xs: 2, sm: 4, md: 6}}){
        Search({placeholder:'搜索...'})
          //...
      }
      .height(48)
    
      // 滚动条
      // 小屏sm:4列,中屏md:8列
      GridCol({span:{ xs: 4, sm: 4, md:8 }}){
        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})
    // 获取断点
    .onBreakpointChange((breakpoint)=>{
      this.currentbp = breakpoint
    })
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
    • 11.
    • 12.
    • 13.
    • 14.
    • 15.
    • 16.
    • 17.
    • 18.
    • 19.
    • 20.
    • 21.
    • 22.
    • 23.
    • 24.
    • 25.
    • 26.
    • 27.
    • 28.
    • 29.
    • 30.
    • 31.
    • 32.
    • 33.
    • 34.
    • 35.
    • 36.
    • 37.
    • 38.
    • 39.
    • 40.
    • 41.
    • 42.
    • 43.
    • 44.
    • 45.
    • 46.
    • 47.
    • 48.

完整代码

本例完整代码如下:
示例中用到的资源请替换为开发者本地资源.

//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},
      // 设置最小屏到小屏,小屏到中屏两个断点
      breakpoints: { value: ["320vp", "520vp"] }
    }){
    
      // 文本框:首页
      // 小屏sm:4列,中屏md:2列
      GridCol({span:{ xs: 2, sm: 4, md: 2}}){
        Row() {
          Text("首页")
            .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)
    
      // 滚动条
      // 小屏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)
        // 小屏时显示一个页面,中屏时显示两个页面
        .displayCount(this.currentbp == 'md' ? 2 : 1)
      }
      .height(144)
    
    }
    .height('100%')
    .width('100%')
    .padding({top:10,bottom:10,left:10,right:10})
    // 获取断点
    .onBreakpointChange((breakpoint)=>{
      this.currentbp = breakpoint
    })
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.

参考

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2023-11-15 14:22:24修改
6
收藏 4
回复
举报
6
4
4
4条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

跨端要做的事情会多不少

回复
2023-11-15 13:47:53
hmyxd
hmyxd

效果很nb!

回复
2023-11-17 18:30:44
FlashinMiami
FlashinMiami

还是不太懂为啥要把屏幕等分列

回复
2023-11-20 10:44:27
野生菌君
野生菌君 回复了 FlashinMiami
还是不太懂为啥要把屏幕等分列

分列用来控制页面元素的布局,比如一个按钮,在小屏设备上可以占满屏幕,但是在大屏设备上还占满屏幕的话就太大了,所以可以根据需要把大屏分为10列,只让按钮占5列(打个比方)就可以了。

回复
2023-11-20 14:21:49


回复
    相关推荐