一. 样例介绍
本篇 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.ets
windowStage.loadContent('pages/SplashPage', (err, data) => {if (err.code) {...}
});
// SplashPage.ets
build() {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.ets
build() {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.ets
CommodityList({
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.ets
build() {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.ets
build() {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.ets
build() {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.ets
build() {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.ets
build() {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. 通过自适应布局、响应式布局、栅格布局实现一次开发,多端部署。