1# 应用市场首页 2 3 4本小节将以应用市场首页为例,介绍如何使用自适应布局能力和响应式布局能力适配不同尺寸窗口。 5 6 7## 页面设计 8 9一个典型的应用市场首页的UX设计如下所示。 10 11 | sm | md | lg | 12| -------- | -------- | -------- | 13|  |  |  | 14 15观察应用市场首页的页面设计,不同断点下的页面设计有较多相似的地方。 16 17据此,我们可以将页面分拆为多个组成部分。 18 191. 底部/侧边导航栏 20 212. 标题栏与搜索栏 22 233. 运营横幅 24 254. 快捷入口 26 275. 精品应用 28 29 | sm | md | lg | 30| -------- | -------- | -------- | 31|  |  |  | 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| 效果图 |  |  | 115| 栅格布局图 |  |  | 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|  |  |  | 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-->