1. 页面级一多开发:
核心:
Home 模块:
- 页面适配
- 网络数据整合
1. 页面级一多开发:
1.1. phone-tabs 适配
完成 tabs 部分的 ui 适配
tabs文档
需求:
- sm、md:底部
- lg:左侧
核心步骤:
- products/phone/src/main/ets/entryability/EntryAbility.ets
-
- 根据窗口尺寸获取断点值
- 通过 AppStorage 进行共享
- common:
-
- 整合:commons/basic/src/main/ets/utils/BreakPointType.ets
- 导出:BreakPointType
- phone:
-
- 使用 BreakPointType
-
-
- 导入,实例化工具对象,获取断点值
- 注册,移除事件监听
-
-
- 根据断点值,调整 tabs 的位置
- 涉及Tabs 的属性:
-
-
- barPosition:位置
- vertical:是否垂直
-
// 导入略
export default class EntryAbility extends UIAbility {
private curBp: string = ''
// 根据当前窗口尺寸更新断点
private updateBreakpoint(windowWidth: number): void {
// 将长度的单位由px换算为vp
let windowWidthVp = px2vp(windowWidth)
let newBp: string = ''
if (windowWidthVp < 320) {
newBp = 'xs'
} else if (windowWidthVp < 600) {
newBp = 'sm'
} else if (windowWidthVp < 840) {
newBp = 'md'
} else {
newBp = 'lg'
}
if (this.curBp !== newBp) {
this.curBp = newBp
// 使用状态变量记录当前断点值
AppStorage.setOrCreate('currentBreakpoint', this.curBp)
}
}
// 其他略
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.getMainWindow()
.then((windowObj) => {
// 获取应用启动时的窗口尺寸
this.updateBreakpoint(windowObj.getWindowProperties()
.windowRect
.width)
// 注册回调函数,监听窗口尺寸变化
windowObj.on('windowSizeChange', (windowSize) => {
this.updateBreakpoint(windowSize.width)
})
});
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
});
}
}
declare interface BreakPointTypeOption<T> {
xs?: T
sm?: T
md?: T
lg?: T
}
export class BreakPointType<T> {
options: BreakPointTypeOption<T>
constructor(option: BreakPointTypeOption<T>) {
this.options = option
}
getValue(currentBreakPoint: string) {
if (currentBreakPoint === 'xs') {
return this.options.xs
} else if (currentBreakPoint === 'sm') {
return this.options.sm
} else if (currentBreakPoint === 'md') {
return this.options.md
} else if (currentBreakPoint === 'lg') {
return this.options.lg
} else {
return undefined
}
}
}
// export { MainPage } from './src/main/ets/components/mainpage/MainPage'
export * from './src/main/ets/utils/BreakPointType'
// 1.导入
import { BreakPointType } from 'basic';
@StorageProp('currentBreakpoint') breakPoint: string = 'xs'
// 调整 tabs 的属性
Tabs() {
//....
}
.barPosition(new BreakPointType({
sm: BarPosition.End,
md: BarPosition.End,
lg: BarPosition.Start
}).getValue(this.breakPoint))
.vertical(this.breakPoint == 'lg' ? true : false)
git 记录:
phone-tabs 适配
1.2. phone-抽取常量
上一节代码中的 currentBreakpoint,字符串在使用过程中没有代码提示,为了更加利于后期的维护,咱们抽取为常量。
- 创建用来管理常量的类,并导出
// commons/basic/src/main/ets/constants/index.ets
export class BreakpointConstants {
// 手表等超小屏
static readonly XS: string = 'xs';
// 手机竖屏
static readonly SM: string = 'sm';
// 手机横屏,折叠屏
static readonly MD: string = 'md';
// 平板,2in1 设备
static readonly LG: string = 'lg';
// AppStorage 中的 key
static readonly BREAK_POINT_KEY: string = 'currentBreakpoint'
}
// commons/basic/Index.ets
export { add } from './src/main/ets/utils/Calc'
export { Logger } from './src/main/ets/utils/Logger'
export * from './src/main/ets/utils/BreakpointSystem'
export * from './src/main/ets/constants/index'
- 替换原本代码中写死的字符串
-
- products/phone/src/main/ets/entryability/EntryAbility.ets 中的key和判断的值
// 根据当前窗口尺寸更新断点
private updateBreakpoint(windowWidth: number): void {
// 将长度的单位由px换算为vp
let windowWidthVp = px2vp(windowWidth)
let newBp: string = ''
if (windowWidthVp < 320) {
newBp = BreakpointConstants.XS
} else if (windowWidthVp < 600) {
newBp = BreakpointConstants.SM
} else if (windowWidthVp < 840) {
newBp = BreakpointConstants.MD
} else {
newBp = BreakpointConstants.LG
}
if (this.curBp !== newBp) {
this.curBp = newBp
// 使用状态变量记录当前断点值
AppStorage.setOrCreate(BreakpointConstants.BREAK_POINT_KEY, this.curBp)
}
}
-
- products/phone/src/main/ets/pages/Index.ets 中获取断点值,及设置及方向
import { Logger, BreakPointType, BreakpointConstants } from 'basic'
// 断点值
@StorageProp(BreakpointConstants.BREAK_POINT_KEY) breakPoint: string = ''
Tabs(){
// 略
}
.vertical(this.breakPoint === BreakpointConstants.LG ? true : false)
.barPosition(this.breakPoint === BreakpointConstants.LG ? BarPosition.Start : BarPosition.End)
git记录:
phone-抽取断点常量
1.3. Home 模块
Home 模块的一多开发
1.3.1. 素材整合
将图片资源,页面,组件,模块这些整合进来
核心步骤:
- commons/basic 模块
-
- 通用图片:commons/basic/src/main/resources/base/media
-
-
- 📎common-图片资源.zip
-
-
- 通用数据模型:📎comon-viewmodel.zip
- 导出通用数据模型
- features/home 模块
-
- 页面组件,页面数据模型,页面:📎home ets 目录.zip
- 页面图片资源:📎home 图片资源.zip
- 在 oh-package.json5 导入 common/basic (否则无法引用数据模型和资源)
- 在模块根目录的 Index.ets 导出 HomeView 组件
- products/phone 模块
-
- 在 oh-package.json5 导入 features/home 模块
- 在 pages/Index.ets 导入 HomeView 组件
- 在 module.json5 开启网络权限(保证首页网络图片可以显示)
// export { MainPage } from './src/main/ets/components/mainpage/MainPage'
export * from './src/main/ets/viewmodel/index'
{
"name": "home",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "Index.ets",
"author": "",
"license": "Apache-2.0",
"dependencies": {
"basic": "file:../../commons/basic"
}
}
git 记录:
home-素材整合
1.3.2. 界面适配
完成 home模块的下的页面适配效果
module.json5、轮播图个数、轮播图间隙
需求:
- 无法缩小到 xs 范围(2in1 设备才可以测试效果)
- 轮播图:
-
- sm:1张图,0 间隙,导航点
- md:2 张图,10 间隙,无导航点
- lg:3 张图,20 间隙,无导航点
- 分类
- 特惠推荐
- 新鲜好物
-
- 上面 3 个都是 space:sm:14,md:36,lg:72
- 推荐商品
-
- sm:2 列
- md:3 列
- lg:4 列
注意:
自由窗口模式(2in1,平板)的 minWindowWidth,包括屏幕2 边的间隙,只设置为 320 没用,需要设置到 331,参考官方示例
核心步骤:
- 无法缩小到 xs 范围:module.json5 设置 minWindowWidth 属性(2in1设备才可以测试效果)
- 通过 AppStorage 获取断点值
- 轮播图:
-
- 通过 BreakPointType 设置不同的图片张数 displayCount
- 通过 三元设置导航点的显示
- 通过 BreakPointType 设置间隙 itemSpace
- 分类、特惠推荐、新鲜好物:(别给错了)
-
- 调整 space 可以抽取函数
- sm 14,md 36,lg 72
- 推荐商品:BreakPointType设置 columnsTemplate 即可
{
"module": {
"name": "phone",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
],
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"minWindowWidth": 331,// 最小窗口宽度 2in1 设备才可以测试
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
]
}
}
import { BreakpointConstants, BreakPointType } from 'basic'
import { DiscountGoodsComp, DiscountType } from '../components/DiscountGoodsComp'
import { MkGoods } from '../components/MkGoods'
import { Banner, CategoryItem, MkGoodsItem, Params } from '../viewmodel'
@Component
export struct HomeView {
// 轮播图
@State banners: Banner[] = []
// 分类
@State categories: CategoryItem[] = []
// 特惠推荐
@State saleGoods: MkGoodsItem[] = []
// 爆款推荐
@State hotGoods: MkGoodsItem[] = []
// 一站买全
@State oneGoods: MkGoodsItem[] = []
// 新鲜好物
@State newGoods: MkGoodsItem[] = []
// 推荐商品
@State recommendGoods: MkGoodsItem[] = []
// 断点值
@StorageProp(BreakpointConstants.BREAK_POINT_KEY) breakPoint: string = ''
// 生成模拟数据
mockData() {
this.banners = new Array(6).fill({
id: "42",
imgUrl: "https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/meikou/banner/nuandong_sj.png",
hrefUrl: "/category/1181622006",
type: "1"
})
this.categories = new Array(10).fill({
id: "1181622001",
name: "气质女装",
picture: "https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/meikou/c1/qznz.png"
})
this.saleGoods = new Array(6).fill({
"id": "1111002",
"name": "剪出精致感,金致圆柄复古剪刀",
"desc": "轻薄设计,简约大方",
"price": "9.90",
"picture": "https://yanxuan-item.nosdn.127.net/4c6a9c8a579b00e5e9c7b002d15a33a2.jpg",
"orderNum": 172
},)
this.hotGoods = new Array(8).fill({
"id": "1135059",
"name": "手工八年老陈醋500毫升",
"desc": "地道醇香,醋酸浓郁",
"price": "30.00",
"picture": "https://yanxuan-item.nosdn.127.net/77da20e77b02830a26f931901ac1a9e0.png",
"orderNum": 147
},)
this.oneGoods = new Array(8).fill({
"id": "1135079",
"name": "免浸泡,12种谷物一次同享,五谷米400克",
"desc": "无需浸泡,同煮同熟",
"price": "9.90",
"picture": "https://yanxuan-item.nosdn.127.net/bfe70bd66efe94f2f18061c707d2a097.png",
"orderNum": 144
})
this.newGoods = new Array(8).fill({
"id": "4027998",
"name": "亮碟多效合一洗涤块495g",
"desc": "洗碗机专用,强力去污",
"price": "69.90",
"picture": "https://yanxuan-item.nosdn.127.net/e07c2b63765cf9f4a46d489c6e09c1c1.jpg",
"orderNum": 643
})
this.recommendGoods = new Array(8).fill({
"id": "4033959",
"name": "3秒快速拆琴轻松保养odin奥丁12半音阶口琴",
"price": 329,
"picture": "https://yanxuan-item.nosdn.127.net/937a8e46a9284e8f7e00e13911ecfbe7.png",
"payCount": 0
})
}
aboutToAppear(): void {
// mock数据
this.mockData()
}
@Builder
DiscountBuilder(params: Params) {
Column() {
Row({ space: 10 }) {
Text(params.title)
.fontColor($r('[basic].color.black'))
.fontSize(14)
Text(params.subTitle)
.fontColor($r('[basic].color.text'))
.fontSize(11)
}
.width('100%')
.margin({ bottom: 10 })
List({ space: 10 }) {
ForEach(params.list, (item: MkGoodsItem) => {
ListItem() {
DiscountGoodsComp({ type: DiscountType.DISCOUNT, goods: item })
}
})
}
.width('100%')
.height(116)
.scrollBar(BarState.Off)
.listDirection(Axis.Horizontal)
}
.height(160)
.layoutWeight(1)
.padding(10)
.backgroundColor(params.bg)
.borderRadius(8)
}
getSpace(): number | undefined {
return new BreakPointType({ sm: 14, md: 36, lg: 72 }).getValue(this.breakpoint)
}
build() {
Scroll() {
Column() {
// 轮播图 + 搜索
Stack({ alignContent: Alignment.Top }) {
Swiper() {
ForEach(this.banners, (item: Banner) => {
Image(item.imgUrl)
})
}
.indicator(
this.breakpoint == BreakpointConstants.SM ? DotIndicator.dot()
.itemWidth(8)
.itemHeight(4)
.color('#33191919')
.selectedItemWidth(24)
.selectedItemHeight(4)
.selectedColor('#191919') : false
)
.displayCount(
new BreakPointType({
sm: 1,
md: 2,
lg: 3
})
.getValue(this.breakpoint)
)
.itemSpace(
new BreakPointType({
sm: 0,
md: 10,
lg: 20
})
.getValue(this.breakpoint)
)
Row() {
Row({ space: 4 }) {
Image($r('[basic].media.ic_public_search'))
.width(16)
.height(16)
.fillColor($r('[basic].color.white'))
Text('搜索...')
.fontSize(14)
.fontColor($r('[basic].color.white'))
}
.backgroundColor('#33191919')
.width('100%')
.height(40)
.borderRadius(20)
.padding({ left: 12 })
}
.padding({ left: 16, right: 16 })
}
.width('100%')
// 分类
Column({ space: 10 }) {
// 分类
List({ space: this.getSpace() }) {
ForEach(this.categories, (item: CategoryItem) => {
ListItem() {
Column() {
Image(item.picture)
.width(56)
.aspectRatio(1)
Text(item.name)
.fontSize(10)
.fontColor('#CC191919')
}
.width(60)
.height(80)
.borderRadius(30)
.clip(true)
.backgroundImage(item.picture)
.backgroundImageSize(ImageSize.Contain)
.backgroundImagePosition(Alignment.Center)
.backgroundBlurStyle(
BlurStyle.BACKGROUND_ULTRA_THICK,
{ scale: 0.25 }
)
}
})
}
.width('100%')
.height(92)
.scrollBar(BarState.Off)
.listDirection(Axis.Horizontal)
.alignListItem(ListItemAlign.Center)
// 特惠推荐
Column({ space: 10 }) {
Image($r('app.media.home_cmd_title'))
.width(150)
.height(20)
Row() {
Image($r('app.media.home_cmd_inner'))
.width(86)
.height(116)
List({ space: this.getSpace() }) {
ForEach(this.saleGoods, (item: MkGoodsItem) => {
ListItem() {
DiscountGoodsComp({ goods: item })
}
})
}
.layoutWeight(1)
.width('100%')
.height(116)
.backgroundColor($r('[basic].color.white'))
.borderRadius({
topRight: 8,
bottomRight: 8
})
.padding({ right: 10, left: 10 })
.scrollBar(BarState.Off)
.listDirection(Axis.Horizontal)
}
}
.width('100%')
.height(166)
.backgroundImage($r('app.media.home_cmd_sm'))
.backgroundImageSize(ImageSize.Cover)
.borderRadius(8)
.padding(10)
.alignItems(HorizontalAlign.Start)
// 爆款推荐+一站买全
Row({ space: 10 }) {
this.DiscountBuilder({
title: '爆款推荐',
subTitle: '最受欢迎',
bg: '#EDF1FB',
list: this.hotGoods
})
this.DiscountBuilder({
title: '一站买全',
subTitle: '精心优选',
bg: '#FCF6EA',
list: this.oneGoods
})
}
// 新鲜好物
Column({ space: 10 }) {
Image($r('app.media.home_new'))
.width(146)
.height(19)
List({ space: this.getSpace() }) {
ForEach(this.newGoods, (item: MkGoodsItem) => {
ListItem() {
DiscountGoodsComp({ type: DiscountType.NEW, goods: item })
}
})
}
.width('100%')
.height(116)
.scrollBar(BarState.Off)
.listDirection(Axis.Horizontal)
}
.width('100%')
.height(156)
.padding(10)
.backgroundColor('#F7EFF5')
.borderRadius(8)
.alignItems(HorizontalAlign.Start)
// 推荐商品
WaterFlow() {
ForEach(this.recommendGoods, (item: MkGoodsItem) => {
FlowItem() {
MkGoods({ goods: item })
}
})
}
.columnsTemplate(
new BreakPointType({
sm: '1fr 1fr',
md: '1fr 1fr 1fr',
lg: '1fr 1fr 1fr 1fr'
}).getValue(this.breakpoint)
)
.columnsGap(8)
.rowsGap(10)
}
.padding({
left: 8,
right: 8,
bottom: 10,
top: 10
})
}
}
.scrollBar(BarState.Off)
}
}