一. 样例介绍
本篇Codelab基于自适应布局和响应式布局,实现购物利用在手机、折叠屏、平板不同屏幕尺寸设施上按不同设计显示。通过三层工程构造组织代码,实现一次开发,多端部署 。

手机运行成果如图所示:

折叠屏运行效果图:

平板运行效果图:

相干概念
● 一次开发,多端部署:一套代码工程,一次开发上架,多端按需部署。撑持开发者疾速高效的开发反对多种终端设备状态的利用。
● 自适应布局:当内部容器大小发生变化时,元素能够依据绝对关系主动变动以适应内部容器变动的布局能力。绝对关系如占比、固定宽高比、显示优先级等。以后自适应布局能力有7种:拉伸能力、均分能力、占比能力、缩放能力、延长能力、暗藏能力、折行能力。自适应布局能力能够实现界面显示随内部容器大小间断变动。
● 响应式布局:当内部容器大小发生变化时,元素能够依据断点、栅格或特定的特色(如屏幕方向、窗口宽低等)主动变动以适应内部容器变动的布局能力。以后响应式布局能力有3种:断点、媒体查问、栅格布局。
● GridRow:栅格容器组件,仅能够和栅格子组件(GridCol)在栅格布局场景中应用。
● GridCol:栅格子组件,必须作为栅格容器组件(GridRow)的子组件应用。

残缺示例:gitee源码地址

二. 环境搭建
咱们首先须要实现HarmonyOS开发环境搭建,可参照如下步骤进行。

软件要求
● DevEco Studio版本:DevEco Studio 3.1 Release及以上版本。
● HarmonyOS SDK版本:API version 9及以上版本。

硬件要求
● 设施类型:华为手机或运行在DevEco Studio上的华为手机设施模拟器。
● HarmonyOS零碎:3.1.0 Developer Release及以上版本。

环境搭建
1.  装置DevEco Studio,详情请参考下载和装置软件。
2.  设置DevEco Studio开发环境,DevEco Studio开发环境须要依赖于网络环境,须要连贯上网络能力确保工具的失常应用,能够依据如下两种状况来配置开发环境:
● 如果能够间接拜访Internet,只需进行下载HarmonyOS SDK操作。
● 如果网络不能间接拜访Internet,须要通过代理服务器才能够拜访,请参考配置开发环境。
1.  开发者能够参考以下链接,实现设施调试的相干配置:
● 应用真机进行调试
● 应用模拟器进行调试

三.代码构造解读
本篇Codelab只对外围代码进行解说,common为公共能力层,feature为功能模块层,本示例分为六个模块,product则为产品层。对于残缺代码,咱们会在源码下载或gitee中提供。

├──common/src/main/ets               // 公共能力层│  ├──bean│  │  ├──CommodityModel.ets          // 商品数据实体类│  │  ├──OrderModel.ets              // 订单数据实体类│  │  └──ProductModel.ets            // 购物车商品数据实体类│  ├──components│  │  ├──CommodityList.ets           // 商品列表组件│  │  ├──CounterProduct.ets          // 数量加减组件│  │  └──EmptyComponent.ets          // 无数据显示组件│  ├──constants│  │  ├──BreakpointConstants.ets     // 断点常量类│  │  ├──GridConstants.ets           // 栅格常量类│  │  └──StyleConstants.ets          // 款式常量类│  ├──utils│  │  ├──BreakpointSystem.ets        // 断点工具类│  │  ├──CommonDataSource.ets        // 数据封装类│  │  ├──LocalDataManager.ets        // 数据操作治理类│  │  ├──Logger.ets.ets              // 日志工具类│  │  └──Utils.ets                   // 办法工具类│  └──viewmodel│     └──ShopData.ets                // 商品利用数据├──features                          // 功能模块层│  ├──commoditydetail/src/main/ets   // 商品详情内容区│  │  ├──bean│  │  │  └──TypeModel.ets            // 实体类│  │  ├──components│  │  │  ├──CapsuleGroupButton.ets   // 自定义按钮组件│  │  │  ├──CommodityDetail.ets      // 商品详情组件│  │  │  └──SpecificationDialog.ets  // 商品规格弹框│  │  ├──constants│  │  │  └──CommodityConstants.ets   // 商品详情区常量类│  │  └──viewmodel│  │     └──CommodityDetailData.ets  // 商品详情数据类│  ├──home/src/main/ets              // 首页内容区│  │  ├──components│  │  │  └──Home.ets                 // 首页内容组件│  │  └──viewmodel│  │     └──HomeData.ets             // 首页数据│  ├──newproduct/src/main/ets        // 新品内容区│  │  ├──components│  │  │  └──NewProduct.ets           // 新品内容组件│  │  └──viewmodel│  │     └──NewProductData.ets       // 新品数据│  ├──orderdetail/src/main/ets       // 订单相干内容区│  │  ├──components│  │  │  ├──AddressInfo.ets          // 收件人信息组件│  │  │  ├──CommodityOrderItem.ets   // 商品订单信息组件│  │  │  ├──CommodityOrderList.ets   // 商品订单列表组件│  │  │  ├──ConfirmOrder.ets         // 确认订单组件│  │  │  ├──HeaderBar.ets            // 题目组件│  │  │  ├──OrderDetailList.ets      // 订单分类列表组件│  │  │  ├──OrderListContent.ets     // 订单分类列表内容组件│  │  │  └──PayOrder.ets             // 领取订单组件│  │  ├──constants│  │  │  └──OrderDetailConstants.ets // 订单区常量类│  │  └──viewmodel│  │     └──OrderData.ets            // 订单数据│  ├──personal/src/main/ets          // 我的内容区│  │  ├──bean│  │  │  └──IconButtonModel.ets      // 按钮图标实体类│  │  ├──components│  │  │  ├──IconButton.ets           // 图片按钮组件│  │  │  ├──LiveList.ets             // 直播列表组件│  │  │  └──Personal.ets             // 我的内容组件│  │  ├──constants│  │  │  └──PersonalConstants.ets    // 我的常量类│  │  └──viewmodel│  │     └──PersonalData.ets         // 我的数据│  └──shopcart/src/main/ets          // 购物车内容区│     ├──components│     │  └──ShopCart.ets             // 购物车内容组件│     └──constants│        └──ShopCartConstants.ets    // 购物车常量类└──products                          // 产品层   └──phone/src/main/ets             // 反对手机、平板      ├──constants      │  └──PageConstants.ets        // 页面常量类      ├──entryability      │  └──EntryAbility.ets          // 程序入口类      ├──pages      │  ├──CommodityDetailPage.ets  // 订单详情页      │  ├──ConfirmOrderPage.ets     // 确认订单页      │  ├──MainPage.ets             // 主页      │  ├──OrderDetailListPage.ets  // 订单分类列表页      │  ├──PayOrderPage.ets         // 领取订单页      │  └──SplashPage.ets           // 启动过渡页      └──viewmodel         └──MainPageData.ets         // 主页数据

四. 利用主框架
4.1 启动页
在工程pages目录中,选中Index.ets,点击鼠标右键 > Refactor > Rename,改名为SplashPage.ets。改名后,批改工程entryability目录下EntryAbility.ets文件中windowStage.loadContent办法第一个参数为pages/SplashPage。在该页面的周期函数aboutToAppear里增加一个2秒的定时工作跳转到主页实现。 

// EntryAbility.etswindowStage.loadContent('pages/SplashPage', (err, data) => {  if (err.code) {    ...  }});// SplashPage.etsbuild() {  Column() {    ...  }  .height(StyleConstants.FULL_HEIGHT)  .width(StyleConstants.FULL_WIDTH)  .backgroundColor($r('app.color.page_background'))}aboutToAppear() {  this.breakpointSystem.register();  this.timeOutId = setTimeout(() => {    router.replaceUrl({ url: PageConstants.MAIN_PAGE_URL })      .catch(err => {        Logger.error(JSON.stringify(err));      })  }, PageConstants.DELAY_TIME);}aboutToDisappear() {  this.breakpointSystem.unregister();  clearTimeout(this.timeOutId);}

手机运行效果图:

折叠屏运行效果图:

平板运行效果图:

4.2 主页
本篇Codelab主页由Tabs容器组件和四个TabContent子组件组成,四个TabContent页签的内容视图别离为首页(Home)、新品(NewProduct)、购物车(ShopCart)、我的(Personal)。依据用户应用场景,通过响应式布局的媒体查问,监听利用窗口宽度变动,获取以后利用所处的断点值,设置Tabs的页签地位,lg断点如平板则显示侧边栏,其余断点的则显示底部栏。

/// MainPage.etsbuild() {  Column() {    Tabs({      barPosition: this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG ?        BarPosition.Start : BarPosition.End,      index: this.currentPageIndex    }) {           ...      .barWidth(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG ?      $r('app.float.bar_width') : StyleConstants.FULL_WIDTH)    .barHeight(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG ?      StyleConstants.SIXTY_HEIGHT : $r('app.float.vp_fifty_six'))    .vertical(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG)  }  .backgroundColor($r('app.color.page_background'))}

主页页面展现:

五. 页面介绍

5.1 首页标签页
首页标签页通过自适应布局的均分、拉伸等能力实现搜寻框、分类等布局,通过响应式布局的媒体查问、断点能力设置轮播图数、商品列表数。

通过响应式布局的媒体查问,监听利用窗口宽度变动,获取以后利用所处的断点值,设置商品列表列数和轮播图数,lg断点显示4列3张轮播图,md断点显示3列2张轮播图,sm断点显示2列1张轮播图。

// Home.ets@Builder CustomSwiper() {  Swiper() {    ForEach(swiperImage, (item: Resource) => {      Image(item)        .width(StyleConstants.FULL_WIDTH)        .aspectRatio(StyleConstants.IMAGE_ASPECT_RATIO)    }, item => JSON.stringify(item))  }  .itemSpace(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM ? 0 :    StyleConstants.ITEM_SPACE)  .indicator(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM)  .displayCount(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG ?    StyleConstants.DISPLAY_THREE :    (this.currentBreakpoint === BreakpointConstants.BREAKPOINT_MD ? StyleConstants.DISPLAY_TWO :    StyleConstants.DISPLAY_ONE))}// Home.etsCommodityList({  commodityList: $commodityList,  column: this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG ? StyleConstants.DISPLAY_FOUR :    (this.currentBreakpoint === BreakpointConstants.BREAKPOINT_MD ?    StyleConstants.DISPLAY_THREE : StyleConstants.DISPLAY_TWO),  onClickItem: (data: Commodity) => this.onClickItem(data)})// CommodityList.etsbuild() {  if (this.commodityList.length > 0) {    List({ space: StyleConstants.TWELVE_SPACE }) {      LazyForEach(new CommonDataSource(this.commodityList), (item: Commodity) => {        ...      }, item => JSON.stringify(item))    }    ...    .lanes(this.column)  } else {    EmptyComponent({ outerHeight: StyleConstants.FIFTY_HEIGHT })  }}

应用自适应布局的均分能力,在Flex布局中设置主轴上的对齐形式为FlexAlign.SpaceAround,使循环渲染的组件之间间隔雷同,第一个元素到行首的间隔和最初一个元素到行尾的间隔是相邻元素之间间隔的一半。

// Home.ets@Builder ActivityTitle() {  Flex({ justifyContent: FlexAlign.SpaceAround }) {    ForEach(activityTitle, (item: ActivityTitleModel, index: number) => {      Flex({        direction: FlexDirection.Column,        justifyContent: FlexAlign.Center,        alignItems: ItemAlign.Center      }) {        ...      }    }, item => JSON.stringify(item))  }  ...}

手机运行效果图:

折叠屏运行效果图

平板运行效果图

5.2 新品标签页
新品标签页由轮播图、分类、新品列表组成,通过响应式布局的媒体查问、断点能力和自适应布局的均分能力,实现不同设施类型显示不同成果,实现逻辑同主页。

通过响应式布局的媒体查问,监听利用窗口宽度变动,获取以后利用所处的断点值设置新品列表,sm断点显示2列,md、lg断点显示3列。

// NewProduct.ets@Builder ProductList() {  List({ space: StyleConstants.TWELVE_SPACE }) {    LazyForEach(new CommonDataSource(productData), (item: ProductDataModel) => {      ListItem() {        ...    }, item => JSON.stringify(item))  }  .lanes(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM ?    StyleConstants.DISPLAY_TWO : StyleConstants.DISPLAY_THREE)  .padding({ left: $r('app.float.vp_twelve'), right: $r('app.float.vp_twelve') })}

手机运行效果图:

折叠屏运行效果图

平板运行效果图

5.3 购物车标签页
购物车标签页,由购物车列表和商品列表组成,商品列表实现逻辑同主页的商品列表,购物车列表应用自适应布局的均分能力实现。 

// ShopCart.ets@Builder CartItem(item: Product, index: number) {  Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {    ...    Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceAround }) {      Text($r('app.string.commodity_piece_description', item.name, item.description))        ...      Text(`${Object.values(item.specifications).join(',')}`)        ...      Flex({ justifyContent: FlexAlign.SpaceBetween }) {        Text() {          ...        }        CounterProduct({          count: item.count,          onNumberChange: (num: number) => {            this.onChangeCount(num, item);          }        })      }    }    ...  }}

手机运行效果图:

折叠屏运行效果图

平板运行效果图

5.4 我的标签页
我的标签页次要由个人信息、我的订单、文字图片按钮、直播列表组成,直播列表实现计划同主页商品列表,其余则应用自适应布局的均分能力,Flex布局设置主轴上的对齐形式为FlexAlign.SpaceAround。 

// Personal.ets@Builder Order() {  Flex({direction: FlexDirection.Column}) {    Flex({      justifyContent: FlexAlign.SpaceBetween,      alignItems: ItemAlign.Center    }) {      Text($r('app.string.order_mine'))        .fontSize($r('app.float.middle_font_size'))      Row() {        ...      }      ...      })    }  Flex({    justifyContent: FlexAlign.SpaceAround,    alignItems: ItemAlign.Center  }) {    ForEach(this.orderIconButton, (iconButton: IconButtonModel) => {      IconButton({        props: iconButton,        click: this.onOrderButtonClick.bind(this, iconButton.key)      })    }, (iconButton) => JSON.stringify(iconButton))  }  .width(StyleConstants.FULL_WIDTH)}@Builder IconDock(buttons: IconButtonModel[]) {  Flex({    justifyContent: FlexAlign.SpaceAround,    alignItems: ItemAlign.Center  }) {    ForEach(buttons, (iconButton: IconButtonModel) => {      IconButton({        props: iconButton      })    }, (iconButton) => JSON.stringify(iconButton))  }  .height($r('app.float.icon_dock_height'))  .padding($r('app.float.vp_twelve'))  .cardStyle()}

手机运行效果图:

折叠屏运行效果图:

平板运行效果图:

5.5 商品详情页
商品详情页整体由轮播图、商品信息、底部按钮栏组成,通过响应式布局能力的栅格布局,实现不同设施类型显示不同的成果,并通过自适应布局的拉伸能力,设置flexGrow属性使按钮位于底部。
● 在sm断点下,轮播图占4个栅格,商品信息占4个栅格,底部按钮栏占4个栅格。
● 在md断点下,轮播图占8个栅格,商品信息占8个栅格,底部按钮栏占8个栅格。
● 在lg断点下,轮播图占12个栅格,商品信息占8个栅格偏移2个栅格,底部按钮栏占8个栅格偏移2个栅格。

// CommodityDetail.etsbuild() {  Stack({ alignContent: Alignment.TopStart }) {    Flex({ direction: FlexDirection.Column }) {      Scroll() {        GridRow({          columns: {            sm: GridConstants.COLUMN_FOUR,            md: GridConstants.COLUMN_EIGHT,            lg: GridConstants.COLUMN_TWELVE          },          gutter: GridConstants.GUTTER_TWELVE        }) {          GridCol({            span: {              sm: GridConstants.SPAN_FOUR,              md: GridConstants.SPAN_EIGHT,              lg: GridConstants.SPAN_TWELVE }          }) {            this.CustomSwiper(this.info.images)          }          GridCol({            span: {              sm: GridConstants.SPAN_FOUR,              md: GridConstants.SPAN_EIGHT,              lg: GridConstants.SPAN_EIGHT            },            offset: { lg: GridConstants.OFFSET_TWO }          }) {            Column() {              ...            }          }        }      }      .flexGrow(StyleConstants.FLEX_GROW)      GridRow({        columns: {          sm: GridConstants.COLUMN_FOUR,          md: GridConstants.COLUMN_EIGHT,          lg: GridConstants.COLUMN_TWELVE        },        gutter: GridConstants.GUTTER_TWELVE      }) {        GridCol({          span: {            sm: GridConstants.SPAN_FOUR,            md: GridConstants.SPAN_EIGHT,            lg: GridConstants.SPAN_EIGHT          },          offset: { lg: GridConstants.OFFSET_TWO } }) {          this.BottomMenu()        }      }    }    ...  }}

手机运行效果图:

折叠屏运行效果图:

平板运行效果图:

5.6 订单确认页
订单确认页由上方收件信息、订单信息、底部的总价和提交订单按钮组成,通过响应式布局的栅格布局,实现不同设施类型显示不同的成果,并通过自适应布局的拉伸能力,设置flexGrow属性使总价和提交订单按钮位于底部。
● 在sm断点下,上方收件信息和订单信息占4个栅格,底部总价占2个栅格,底部提交订单按钮占2个栅格。
● 在md断点下,上方收件信息和订单信息占8个栅格,底部总价占2个栅格,底部按钮占3个栅格偏移3个栅格。
● 在lg断点下,上方收件信息和订单信息占8个栅格偏移2个栅格,底部总价占2个栅格偏移2个栅格,底部按钮占3个栅格偏移3个栅格。

// ConfirmOrder.etsbuild() {  Flex({ direction: FlexDirection.Column }) {    HeaderBar({      ...    })    Column(){      Scroll() {        GridRow({          columns: {            sm: GridConstants.COLUMN_FOUR,            md: GridConstants.COLUMN_EIGHT,            lg: GridConstants.COLUMN_TWELVE            }          }) {          GridCol({            span: {              sm: GridConstants.SPAN_FOUR,              md: GridConstants.SPAN_EIGHT,              lg: GridConstants.SPAN_EIGHT            },            offset: { lg: GridConstants.OFFSET_TWO }          }) {            Column() {              AddressInfo()              CommodityOrderList()            }          }        }      }      .scrollBar(BarState.Off)    }    .flexGrow(StyleConstants.FLEX_GROW)    .padding({ left: $r('app.float.vp_twelve'), right: $r('app.float.vp_twelve') })    GridRow({      columns: {        sm: GridConstants.COLUMN_FOUR,        md: GridConstants.COLUMN_EIGHT,        lg: GridConstants.COLUMN_TWELVE      },      gutter: GridConstants.GUTTER_TWELVE    }) {      GridCol({        span: {          sm: GridConstants.SPAN_TWO,          md: GridConstants.SPAN_TWO,          lg: GridConstants.SPAN_TWO        },        offset: { lg: GridConstants.OFFSET_TWO }      }) {        Text($r('app.string.bottom_bar_amount', this.amount))        ...      }      GridCol({        span: {          sm: GridConstants.SPAN_TWO,          md: GridConstants.SPAN_THREE,          lg: GridConstants.SPAN_THREE        },        offset: {          md: GridConstants.OFFSET_THREE,          lg: GridConstants.OFFSET_THREE        }      }) {        Button($r('app.string.bottom_bar_button'))          ...      }    }    ...  }  ...}

手机运行效果图:

折叠屏运行效果图:

平板运行效果图:

5.7 订单领取页
订单领取页整体由上方订单信息和底部的去领取按钮组成,通过应用响应式布局的栅格布局实现不同设施类型显示不同成果,并通过自适应布局的拉伸能力,设置flexGrow属性使去领取按钮位于底部。
● 在sm断点下,上方订单信息占4个栅格,底部去领取按钮占2个栅格偏移2个栅格。
● 在md断点下,上方订单信息占8个栅格,底部去领取按钮占2个栅格偏移6个栅格。
● 在lg断点下,上方订单信息占8个栅格偏移2个栅格,底部去领取按钮占2个栅格偏移8个栅格。

// PayOrder.etsbuild() {  Flex({ direction: FlexDirection.Column }) {    HeaderBar({      ...    })    Stack({ alignContent: Alignment.TopStart }) {      ...      Column() {        Scroll() {          GridRow({            columns: {              sm: GridConstants.COLUMN_FOUR,              md: GridConstants.COLUMN_EIGHT,              lg: GridConstants.COLUMN_TWELVE            }          }) {            GridCol({              span: {                sm: GridConstants.SPAN_FOUR,                md: GridConstants.SPAN_EIGHT,                lg: GridConstants.SPAN_EIGHT              },              offset: { lg: GridConstants.OFFSET_TWO }            }) {              Column() {                this.OrderStatus()                ...              }            }          }        }        .scrollBar(BarState.Off)      }      .padding({ left: $r('app.float.vp_twelve'), right: $r('app.float.vp_twelve') })    }    .flexGrow(StyleConstants.FLEX_GROW)    GridRow({      columns: {        sm: GridConstants.COLUMN_FOUR,        md: GridConstants.COLUMN_EIGHT,        lg: GridConstants.COLUMN_TWELVE      }    }) {      GridCol({        span: {          sm: GridConstants.SPAN_TWO,          md: GridConstants.SPAN_TWO,          lg: GridConstants.SPAN_TWO        },        offset: {          sm: GridConstants.OFFSET_TWO,          md: GridConstants.OFFSET_SIX,          lg: GridConstants.OFFSET_EIGHT        }      }) {        this.BottomBar()      }    }  }  ...}

手机运行效果图:

折叠屏运行效果图:

平板运行效果图:

5.8 订单列表页
订单列表页整体布局通过响应式布局的栅格布局,实现不同设施类型显示不同的成果。
● 在sm断点下,整体UX占4个栅格。
● 在md断点下,整体UX占8个栅格。
● 在lg断点下,整体UX占8个栅格偏移2个栅格。

// OrderListContent.etsbuild() {  Column() {    Scroll() {      GridRow({        columns: {          sm: GridConstants.COLUMN_FOUR,          md: GridConstants.COLUMN_EIGHT,          lg: GridConstants.COLUMN_TWELVE        }      }) {        GridCol({          span: {            sm: GridConstants.SPAN_FOUR,            md: GridConstants.SPAN_EIGHT,            lg: GridConstants.SPAN_EIGHT          },          offset: { lg: GridConstants.OFFSET_TWO }        }) {          Column() {            ...          }        }      }    }    .scrollBar(BarState.Off)  }  ...}

手机运行效果图:

折叠屏运行效果图:

平板运行效果图:

六.  总结
您曾经实现了本次Codelab的学习,并理解到以下知识点:
1.  针对不同屏幕尺寸的设施实现一次开发,多端部署页面设计。
2.  依照三层工程构造划分模块、组织代码。
3.  通过自适应布局、响应式布局、栅格布局实现一次开发,多端部署。