• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 应用市场首页
2
3
4本小节将以应用市场首页为例,介绍如何使用自适应布局能力和响应式布局能力适配不同尺寸窗口。
5
6
7## 页面设计
8
9一个典型的应用市场首页的UX设计如下所示。
10
11  | sm | md | lg |
12| -------- | -------- | -------- |
13| ![zh-cn_image_0000001328579522](figures/zh-cn_image_0000001328579522.png) | ![zh-cn_image_0000001328259918](figures/zh-cn_image_0000001328259918.png) | ![zh-cn_image_0000001379179861](figures/zh-cn_image_0000001379179861.png) |
14
15观察应用市场首页的页面设计,不同断点下的页面设计有较多相似的地方。
16
17据此,我们可以将页面分拆为多个组成部分。
18
191. 底部/侧边导航栏
20
212. 标题栏与搜索栏
22
233. 运营横幅
24
254. 快捷入口
26
275. 精品应用
28
29  | sm | md | lg |
30| -------- | -------- | -------- |
31| ![zh-cn_image_0000001379299533](figures/zh-cn_image_0000001379299533.png) | ![zh-cn_image_0000001328259922](figures/zh-cn_image_0000001328259922.png) | ![zh-cn_image_0000001379179865](figures/zh-cn_image_0000001379179865.png) |
32
33接下来我们逐一分析各部分的实现。
34
35
36## 底部/侧边导航栏
37
38在sm和md断点下,导航栏在底部;在lg断点下,导航栏在左侧。可以通过[Tab组件](../../reference/apis-arkui/arkui-ts/ts-container-tabs.md)的barPosition和vertical属性控制TabBar的位置,同时还可以通过barWidth和barHeight属性控制TabBar的尺寸。
39
40
41```ts
42import Home from '../common/Home';//组件请参考相关实例
43import TabBarItem from '../common/TabBarItem';
44
45@Entry
46@Component
47struct Index {
48  @State currentIndex: number = 0;
49  @StorageProp('currentBreakpoint') currentBreakpoint: string = 'md';
50  @Builder
51  tabItem(index: number, title: Resource, icon: Resource, iconSelected: Resource) {
52    TabBarItem({
53      index: index,
54      currentIndex: this.currentIndex,
55      title: title,
56      icon: icon,
57      iconSelected: iconSelected
58    })
59  }
60
61  build() {
62    // 设置TabBar在主轴方向起始或结尾位置
63    Tabs({ barPosition: this.currentBreakpoint === "lg" ? BarPosition.Start : BarPosition.End }) {
64      // 首页
65      TabContent() {
66        Home()
67      }.tabBar(this.tabItem(0, $r('app.string.tabBar1'), $r('app.media.ic_home_normal'), $r('app.media.ic_home_actived')))
68      TabContent() {}.tabBar(this.tabItem(1, $r('app.string.tabBar2'), $r('app.media.ic_app_normal'), $r('app.media.ic_app_actived')))
69      TabContent() {}.tabBar(this.tabItem(2, $r('app.string.tabBar3'), $r('app.media.ic_game_normal'), $r('app.media.ic_mine_actived')))
70      TabContent() {}.tabBar(this.tabItem(3, $r('app.string.tabBar4'), $r('app.media.ic_search_normal'), $r('app.media.ic_search_actived')))
71      TabContent() {}.tabBar(this.tabItem(4, $r('app.string.tabBar4'), $r('app.media.ic_mine_normal'), $r('app.media.ic_mine_actived')))
72    }
73    .backgroundColor('#F1F3F5')
74    .barMode(BarMode.Fixed)
75    .barWidth(this.currentBreakpoint === "lg" ? 96 : '100%')
76    .barHeight(this.currentBreakpoint === "lg" ? '60%' : 56)
77    // 设置TabBar放置在水平或垂直方向
78    .vertical(this.currentBreakpoint === "lg")
79  }
80}
81```
82
83另外在sm及lg断点下,TabBar中各个Item的图标和文字是按照垂直方向排布的,在md断点下,TabBar中各个Item的图标和文字是按照水平方向排布的。
84
85
86```ts
87@Component
88export default struct TabBarItem {
89  @StorageProp('currentBreakpoint') currentBreakpoint: string = 'md';
90
91  build() {
92    if (this.currentBreakpoint !== 'md' ) {
93      // sm及lg断点下,tabBarItem中的图标和文字垂直排布
94      Column() {
95       // ...
96      }.justifyContent(FlexAlign.Center).height('100%').width('100%')
97    } else {
98      // md断点下,tabBarItem中的图标和文字水平排布
99      Row() {
100       // ...
101      }.justifyContent(FlexAlign.Center).height('100%').width('100%')
102    }
103  }
104}
105```
106
107
108## 标题栏与搜索栏
109
110标题栏和搜索栏,在sm和md断点下分两行显示,在lg断点下单行显示,可以通过栅格实现。在sm和md断点下,标题栏和搜索栏占满12列,此时会自动换行显示。在lg断点下,标题栏占8列而搜索栏占4列,此时标题栏和搜索栏在同一行中显示。
111
112  |  | sm/md | lg |
113| -------- | -------- | -------- |
114| 效果图 | ![zh-cn_image_0000001379385785](figures/zh-cn_image_0000001379385785.png) | ![zh-cn_image_0000001379464977](figures/zh-cn_image_0000001379464977.jpg) |
115| 栅格布局图 | ![zh-cn_image_0000001379464981](figures/zh-cn_image_0000001379464981.png) | ![zh-cn_image_0000001328745102](figures/zh-cn_image_0000001328745102.png) |
116
117
118```ts
119@Component
120export default struct IndexHeader {
121
122  @Builder searchBar() {
123    Stack({alignContent: Alignment.End}) {
124      TextInput({ placeholder: $r('app.string.search') })
125        .placeholderColor('#FF000000')
126        .placeholderFont({ size: 16, weight: 400 })
127        .textAlign(TextAlign.Start)
128        .caretColor('#FF000000')
129        .width('100%')
130        .height(40)
131        .fontWeight(400)
132        .padding({ top: 9, bottom: 9 })
133        .fontSize(16)
134        .backgroundColor(Color.White)
135
136      Image($r('app.media.ic_public_search'))
137        .width(16)
138        .height(16)
139        .margin({ right: 20 })
140    }.height(56).width('100%')
141  }
142
143  @Builder titleBar() {
144    Text($r('app.string.tabBar1'))
145      .fontSize(24)
146      .fontWeight(500)
147      .fontColor('#18181A')
148      .textAlign(TextAlign.Start)
149      .height(56)
150      .width('100%')
151  }
152
153  build() {
154    // 借助栅格实现标题栏和搜索栏在不同断点下的不同布局效果。
155    GridRow() {
156      GridCol({ span: { xs: 12, lg: 8 } }) {
157        this.titleBar()
158      }
159      GridCol({ span: { xs: 12, lg: 4 } }) {
160        this.searchBar()
161      }
162    }
163    .width('100%')
164  }
165}
166```
167
168
169## 运营横幅
170
171不同断点下的运营横幅,sm断点下显示一张图片,md断点下显示两张图片,lg断点下显示三张图片。可以通过[Swiper组件的displayCount属性](../../reference/apis-arkui/arkui-ts/ts-container-swiper.md)实现目标效果。
172
173
174```ts
175@Component
176export default struct IndexSwiper {
177  @StorageProp('currentBreakpoint') currentBreakpoint: string = 'md';
178  @Builder swiperItem(imageSrc:Resource) {
179    Image(imageSrc)
180      .width('100%')
181      .aspectRatio(2.5)
182      .objectFit(ImageFit.Fill)
183  }
184
185  build() {
186    Swiper() {
187      this.swiperItem($r('app.media.ic_public_swiper1'))
188      this.swiperItem($r('app.media.ic_public_swiper2'))
189      this.swiperItem($r('app.media.ic_public_swiper3'))
190      // ...
191    }
192    .autoPlay(true)
193    .indicator(false)
194    .itemSpace(10)
195    // 配置不同断点下运行横幅中展示的图片数量
196    .displayCount(this.currentBreakpoint === 'sm' ? 1 : (this.currentBreakpoint === 'md' ? 2 : 3))
197    .width('100%')
198    .padding({ left: 12, right: 12, bottom: 16, top: 16 })
199  }
200}
201```
202
203
204## 快捷入口
205
206在不同的断点下,快捷入口的5个图标始终均匀排布,这是典型的均分能力使用场景。
207
208
209```ts
210import { entranceIcons } from '../model/HomeData';
211import { AllIcons } from '../model/HomeDataType';
212
213@Component
214export default struct IndexEntrance {
215  build() {
216    // 将justifyContent参数配置为FlexAlign.SpaceEvenly实现均分布局
217    Row() {
218      ForEach(entranceIcons, (icon: AllIcons) => {
219        // 各快捷入口的图标及名称
220        Column() {
221          // ...
222          }
223      })
224    }
225    .width('100%')
226    .height(64)
227    .justifyContent(FlexAlign.SpaceEvenly)
228    .padding({ left: 12, right: 12 })
229  }
230}
231```
232
233
234## 精品应用
235
236随着可用显示区域的增加,精品应用中显示的图标数量也不断增加,这是典型的延伸能力使用场景。精品游戏的实现与精品应用类似,不再展开分析。
237
238
239```ts
240import { AppItem, MyAppSource } from '../model/HomeDataType';
241
242@Component
243export default struct IndexApps {
244  private title?: Resource;
245  @StorageProp('currentBreakpoint') currentBreakpoint: string = 'md';
246  private apps: AppItem[] = [];
247  @Builder
248  appListHeader() {
249    Row() {
250      Text(this.title)
251        .width(100)
252        .fontSize(16)
253        .textAlign(TextAlign.Start)
254        .fontWeight(500)
255      Blank()
256      Text($r('app.string.more'))
257        .fontSize(14)
258        .textAlign(TextAlign.End)
259        .fontWeight(400)
260        .margin({ right: 2 })
261      Image($r('app.media.ic_public_arrow_right'))
262        .width(12)
263        .height(18)
264        .opacity(0.9)
265        .objectFit(ImageFit.Fill)
266    }
267    .margin({ bottom: 9, top: 9 })
268    .width('100%')
269    .alignItems(VerticalAlign.Bottom)
270  }
271
272  @Builder
273  appListItem(app:AppItem) {
274    Column() {
275      Image(app.image)
276        .width(this.currentBreakpoint === 'lg' ? 80 : 56)
277        .height(this.currentBreakpoint === 'lg' ? 80 : 56)
278        .margin({ bottom: 8 })
279      Text(app.title)
280        .width(this.currentBreakpoint === 'lg' ? 80 : 56)
281        .height(16)
282        .fontSize(12)
283        .textAlign(TextAlign.Center)
284        .fontColor('#18181A')
285        .margin({ bottom: 8 })
286      Text($r('app.string.install'))
287        .width(this.currentBreakpoint === 'lg' ? 80 : 56)
288        .height(28)
289        .fontColor('#0A59F7')
290        .textAlign(TextAlign.Center)
291        .borderRadius(this.currentBreakpoint === 'lg' ? 26 : 20)
292        .fontWeight(500)
293        .fontSize(12)
294        .padding({ top: 6, bottom: 6, left: 8, right: 8 })
295        .backgroundColor('rgba(0,0,0,0.05)')
296    }
297  }
298  build() {
299    Column() {
300      this.appListHeader()
301      // 借助List组件能力,实现延伸能力场景
302      List({ space: this.currentBreakpoint === 'lg' ? 44 : 20}) {
303        LazyForEach(new MyAppSource(this.apps), (app: AppItem)=> {
304          ListItem() {
305            // 每个应用的图标、名称及安装按钮
306            this.appListItem(app)
307          }
308        })
309      }
310      .width('100%')
311      .height(this.currentBreakpoint === 'lg' ? 140 : 120)
312      .listDirection(Axis.Horizontal)
313    }
314    .width('100%')
315    .height(this.currentBreakpoint === 'lg' ? 188 : 164)
316    .padding({ bottom: 8, left: 12, right: 12 })
317  }
318}
319```
320
321
322## 运行效果
323
324将上述各页面主要部分组合在一起后,即可完成整体页面开发。
325
326
327```ts
328import IndexSwiper from './IndexSwiper';
329import IndexEntrance from './IndexEntrance';
330import IndexApps from './IndexApps';
331import { appList, gameList } from '../model/HomeData';
332import IndexHeader from './IndexHeader';
333
334@Component
335struct IndexContent {
336  // ...
337  build() {
338    List() {
339      // 运营横幅
340      ListItem() {
341        IndexSwiper()
342      }
343      // 快捷入口
344      ListItem() {
345        IndexEntrance()
346      }
347      // 精品应用
348      ListItem() {
349        IndexApps({ title: $r('app.string.boutique_application'), apps: appList })
350      }
351      // 精品游戏
352      ListItem() {
353        IndexApps({ title: $r('app.string.boutique_game'), apps: gameList })
354      }
355    }
356    .width("100%")
357  }
358}
359
360@Entry
361@Component
362export default struct Home {
363  // ...
364  build() {
365    Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Start }) {
366      // 标题栏和搜索栏
367      IndexHeader()
368      // 运营横幅、快捷入口、精品应用、精品游戏等
369      IndexContent()
370    }
371    .height('100%')
372    .backgroundColor("#F1F3F5")
373  }
374}
375```
376
377本页面的实际运行效果如下图所示。
378
379  | sm | md | lg |
380| -------- | -------- | -------- |
381| ![zh-cn_image_0000001334345550](figures/zh-cn_image_0000001334345550.jpg) | ![zh-cn_image_0000001385105477](figures/zh-cn_image_0000001385105477.jpg) | ![zh-cn_image_0000001384985569](figures/zh-cn_image_0000001384985569.jpg) |
382
383## 相关实例
384
385针对应用市场应用开发,有以下相关实例可以参考:
386
387- 应用市场开发:[典型页面场景:应用市场首页(ArkTS)(API9)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-5.0.1-Release/code/SuperFeature/MultiDeviceAppDev/AppMarket)
388
389
390
391<!--no_check-->