• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import  promptAction  from '@ohos.promptAction';
17import { Text, List, ListItem, Column, Button, Image, Row, Stack,ListItemGroup, Divider, Blank,
18  Tabs, TabContent, Flex, FlexOptions, Callback, px2vp, ItemAlign, BackgroundBrightnessOptions,
19  ButtonAttribute, ClickEvent, Component, BuilderParam, Padding, $r, SafeAreaEdge, BackgroundBlurStyleOptions,
20  BarState, NestedScrollOptions, NestedScrollMode, Color, JSON, Alignment, FlexDirection, BarPosition,
21  FlexAlign, BlurStyle, LazyForEach, ForEach, Builder, Margin, SafeAreaType, MenuItemOptions,
22  TabsController, TabsOptions ,StackOptions, ThemeColorMode, AdaptiveColor, ResourceStr, SizeOptions,
23  Reusable, TextOverflowOptions, ListOptions, LinearGradientOptions, Menu, MenuItem, Axis, BorderRadiuses,
24  WaterFlow, FlowItem, ImageFit, TextAlign, Scroll, ScrollAlign, Scroller, TextOverflow, ShadowStyle, TabsAnimationEvent,
25  OnTabsAnimationStartCallback, Rating, RatingOptions, GestureGroup, GestureEvent, GestureMode, TapGesture, $$,
26  SheetSize, SheetType, ScrollSizeMode, SheetOptions, CustomBuilder, Entry
27} from '@ohos.arkui.component'
28import { State, StateDecoratedVariable, MutableState, stateOf, AppStorage,
29  observableProxy,ObjectLink, Observed, Consume, Link, Provide, Watch} from '@ohos.arkui.stateManagement'
30import display from '@ohos.display';
31import { SceneModuleInfo } from '../../model/functionalScenes/SceneModuleInfo';
32import { WaterFlowDataSource } from '../../model/functionalScenes/WaterFlowDataSource';
33import { TAB_DATA, TabDataModel } from '../../model/functionalScenes/TabsData';
34import { SCENE_MODULE_LIST } from '../../model/functionalScenes/SceneModuleDatas'
35import { CollapseMenuSection } from '../collapsemenu/CollapseMenuSection';
36import { UIContext } from '@ohos.arkui.UIContext'
37
38/**
39 * 主页瀑布流列表
40 */
41@Entry
42@Component
43export struct FunctionalScenes {
44  listData: SceneModuleInfo[] = SCENE_MODULE_LIST;
45  dataSource: WaterFlowDataSource<SceneModuleInfo> = new WaterFlowDataSource<SceneModuleInfo>(this.listData);
46  @State tabsIndex: number = 0;
47  tabsController: TabsController = new TabsController();
48  private scrollController: Scroller = new Scroller();
49  items:number[] = [1,2,3,4,5,6,7,8,9,10] as number[];
50  @Builder
51  tabBuilder(index: number, name: string | undefined) {
52    Stack() {
53      Column() {
54      }
55      .width(this.tabsIndex === index ? 97 : 71)
56      .backgroundColor(this.tabsIndex === index ? '#0A59F7' : '#000000')
57      .opacity(this.tabsIndex === index ? 1 : 0.05)
58      .height(38)
59      .borderRadius(21)
60      Text(name)
61        .fontSize(14)
62        .fontColor(this.tabsIndex === index ? Color.White : Color.Black)
63        .opacity(this.tabsIndex === index ? 1 : 0.8)
64        .height('100%')
65        .id('section')
66    }
67    .margin(index !== 0 && index !== TAB_DATA.length ? { left: 9 } as Margin : {
68      left: 0,
69      right: 0
70    } as Margin)
71    .align(Alignment.Center)
72    .onClick((e: ClickEvent) => {
73      this.tabsIndex = index;
74      this.tabsController.changeIndex(index);
75    })
76  }
77
78  @Builder
79  tabsMenu() {
80    Menu() {
81      ForEach(TAB_DATA, (item: TabDataModel) => {
82        MenuItem({ content: item.navData } as MenuItemOptions)
83          .onClick((e: ClickEvent) => {
84            this.tabsIndex = item.id;
85            this.tabsController.changeIndex(item.id);
86          })
87          .id('menu_item')
88      })
89    }
90  }
91
92  /**
93   * 主页通过瀑布流和LazyForeach加载
94   * WaterFlow+LazyForEach详细用法可参考性能范例:
95   * waterflow_optimization.md/
96   */
97  build() {
98    Column() {
99      Row() {
100        Text($r('app.string.custom_return'))
101      }
102      .margin(3)
103      .width('100%')
104      .onClick((e: ClickEvent) => {
105        this.getUIContext().getRouter().back();
106      })
107      Row() {
108        Stack() {
109          List({ scroller: this.scrollController } as ListOptions) {
110            ForEach(TAB_DATA, (tabItem: TabDataModel) => {
111              ListItem() {
112                this.tabBuilder(tabItem.id, tabItem.navData);
113              }
114            })
115          }
116          .id('MainList')
117          .margin({ top: 3 } as Margin)
118          .height(38)
119          .listDirection(Axis.Horizontal)
120          .padding({ right: 46 } as Padding)
121          .scrollBar(BarState.Off)
122          Row() {
123            Row() {
124              Image($r('app.media.ic_public_more'))
125                .width(20)
126                .id('mainPageTabsImage')
127            }
128            .bindMenu(this.tabsMenu)
129            .justifyContent(FlexAlign.Center)
130            .width(43)
131            .height(43)
132            .borderRadius(100)
133            .backgroundColor('rgba(216, 216, 216, 0)')
134            .id('menu_button')
135          }
136          .linearGradient({
137            angle: 90,
138            colors: [['rgba(241, 241, 241, 0)', 0], ['#F1F3F5', 0.2], ['#F1F3F5', 1]]
139          } as LinearGradientOptions)
140          .justifyContent(FlexAlign.End)
141          .width(60)
142          .height(43)
143        }
144        .alignContent(Alignment.TopEnd)
145      }
146      .padding({
147        left: 13,
148        right: 13
149      } as Padding)
150      .margin({ top: 8 } as Margin)
151      Tabs({ controller: this.tabsController } as TabsOptions) {
152        ForEach(TAB_DATA, (tabItem: TabDataModel) => {
153          TabContent() {
154            if (tabItem.navData === '全部') {
155              List() {
156                LazyForEach(this.dataSource, (waterFlowItem: SceneModuleInfo) => {
157                  ListItem(){
158                    methodPoints({ listData: waterFlowItem })
159                  }
160                }, (waterFlowItem: SceneModuleInfo) => JSON.stringify(waterFlowItem))
161              }
162              .nestedScroll({
163                scrollForward: NestedScrollMode.PARENT_FIRST,
164                scrollBackward: NestedScrollMode.SELF_FIRST
165              } as NestedScrollOptions)
166              .width('100%')
167              .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
168              .padding({ bottom: $r('app.integer.functional_scenes_water_flow_padding_bottom') } as Padding)
169            } else {
170              Column() {
171                Text(tabItem.navData).fontSize(20)
172              }
173              .height('100%')
174              .width('100%')
175              .justifyContent(FlexAlign.Center)
176              .align(Alignment.Center)
177            }
178          }
179          .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
180          .align(Alignment.TopStart)
181          .alignSelf(ItemAlign.Start)
182        })
183      }
184      .margin({ top: 8 } as Margin)
185      .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
186      .padding({
187        left: 13,
188        right: 13
189      } as Padding)
190      .barWidth(0)
191      .barHeight(0)
192      .onAnimationStart((index: number, targetIndex: number, extraInfo: TabsAnimationEvent) => {
193        this.tabsIndex = targetIndex;
194        this.scrollController.scrollToIndex(targetIndex, true, ScrollAlign.START);
195      } as OnTabsAnimationStartCallback)
196    }
197    .height('100%')
198    .backgroundColor('#F1F1F1')
199    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
200  }
201}
202
203/**
204 * 瀑布流列表项组件布局
205 *
206 * @param listData 组件列表信息
207 */
208// TODO:知识点:
209// 1.@Reusable标识自定义组件具备可复用的能力,它可以被添加到任意的自定义组件上。
210// 2.复用自定义组件时避免一切可能改变自定义组件的组件树结构和可能使可复用组件中产生重新布局的操作以将组件复用的性能提升到最高。
211
212@Reusable
213@Component
214struct methodPoints {
215  @State listData: SceneModuleInfo =
216    new SceneModuleInfo($r('app.media.functional_scenes_address_exchange'), '地址交换动画',
217      'addressexchange/AddressExchangeView', '动效', 1);
218  @State helperUrl: string = 'about://blank';
219  @State screenW: number = px2vp(display.getDefaultDisplaySync().width);
220  @State isNeedClear: boolean = false;
221  private deviceSize: number = 600; // 依据Navigation的mode属性说明,如使用Auto,窗口宽度>=600vp时,采用Split模式显示;窗口宽度<600vp时,采用Stack模式显示。
222  // 当前屏幕折叠态(仅折叠屏设备下有效)
223  curFoldStatus: display.FoldStatus = display.FoldStatus.FOLD_STATUS_UNKNOWN;
224  // 从AppStorage中获取设别类别,判断是否为折叠屏
225  isFoldable: boolean | undefined = AppStorage.get('isFoldable');
226  @State @Watch('onShowReadMeChange') isShowReadMe: boolean = false;
227
228  aboutToAppear(): void {
229    if (display.isFoldable()) {
230      this.regDisplayListener();
231    } else {
232      if (this.screenW >= this.deviceSize) {
233        this.isNeedClear = true;
234      } else {
235        this.isNeedClear = false;
236      }
237    }
238  }
239
240  /**
241   * 组件的生命周期回调,在可复用组件从复用缓存中加入到组件树之前调用
242   * @param params:组件更新时所需参数
243   */
244  aboutToReuse(params: Record<string, SceneModuleInfo>): void {
245    const listData = params['listData'];
246    if (listData) {
247      this.listData = listData as SceneModuleInfo;
248    }
249  }
250
251  /**
252   * 注册屏幕状态监听 (仅限折叠屏)
253   * @returns {void}
254   */
255  regDisplayListener(): void {
256    this.changeNeedClear(display.getFoldStatus());
257    display.on('foldStatusChange', (curFoldStatus: display.FoldStatus) => {
258      // 同一个状态重复触发不做处理
259      if (this.curFoldStatus === curFoldStatus) {
260        return;
261      }
262      // 缓存当前折叠状态
263      this.curFoldStatus = curFoldStatus;
264      this.changeNeedClear(this.curFoldStatus);
265    })
266  }
267
268  changeNeedClear(status: number): void {
269    if (status === display.FoldStatus.FOLD_STATUS_FOLDED) {
270      this.isNeedClear = false;
271    } else {
272      this.isNeedClear = true;
273    }
274  }
275
276  changeHelpUrl(): void {
277    this.helperUrl = this.listData.helperUrl;
278  }
279
280  onShowReadMeChange(text: string): void {
281    if (!this.isShowReadMe) {
282
283    }
284  }
285
286  // 帮助功能:半模态弹窗显示对应案例README
287  @Builder
288  buildReadMeSheet(): void {
289    Column() {
290      Row() {
291        Row() {
292          Text(this.listData.name)
293            .textOverflow({ overflow: TextOverflow.Clip } as TextOverflowOptions)
294            .fontColor(Color.White)
295            .fontWeight(700)
296            .fontSize($r('app.integer.nav_destination_title_text_size'))
297        }
298        .width($r('app.integer.readme_sheet_text_size'))
299
300        Column() {
301          Stack() {
302            Column() {
303            }
304            .width($r('app.integer.readme_sheet_size'))
305            .height($r('app.integer.readme_sheet_size'))
306            .borderRadius($r('app.integer.nav_destination_title_image_border_radius'))
307            .backgroundColor(Color.White)
308            .opacity(0.05)
309            Image($r('app.media.ic_public_cancel'))
310              .fillColor(Color.White)
311              .width($r('app.integer.readme_sheet_cancel_image_width'))
312          }
313        }
314        .onClick((e: ClickEvent) => {
315          this.isShowReadMe = false;
316        })
317        .justifyContent(FlexAlign.Center)
318        .width($r('app.integer.readme_sheet_size'))
319        .height($r('app.integer.readme_sheet_size'))
320        .borderRadius($r('app.integer.nav_destination_title_image_border_radius'))
321      }
322      .padding({ left: $r('app.integer.readme_sheet_padding'), right: $r('app.integer.readme_sheet_padding') } as Padding)
323      .margin({ top: $r('app.integer.readme_sheet_margin')} as Margin)
324      .justifyContent(FlexAlign.SpaceBetween)
325      .width('100%')
326    }
327    .width('100%')
328    .height('100%')
329  }
330
331  build() {
332    Column() {
333      Image($r('app.media.background_pic_3'))
334        .borderRadius({
335          topLeft: 8,
336          topRight: 8,
337          bottomLeft: 0,
338          bottomRight: 0
339        } as BorderRadiuses)
340        .objectFit(ImageFit.Contain)
341        .width('100%')
342
343      Text(this.listData?.serialNumber?.toString() + '. ' + this.listData.name)
344        .padding({
345          left: 10,
346          right: 10
347        } as Padding)
348        .width('100%')
349        .fontColor(Color.Black)
350        .textAlign(TextAlign.Start)
351        .maxLines(2)
352        .fontSize(14)
353        .margin({
354          top: 10,
355          bottom: 10
356        } as Margin)
357        .textOverflow({ overflow: TextOverflow.Ellipsis } as TextOverflowOptions)
358
359      Row() {
360        Button($r('app.string.functional_scenes_readme'))
361          .fontSize(12)
362          .fontColor(Color.White)
363        .height(25)
364        .width(100)
365        .margin({ left: 6, right: 10 } as Margin)
366        .gesture(
367          GestureGroup(
368            GestureMode.Exclusive,
369            TapGesture({ fingers: 1, count: 1 })
370              .onAction(() => {
371                this.getUIContext().getRouter().pushUrl({url: 'pages/collapsemenu/Concent'})
372              })
373          )
374        )
375
376        Text($r('app.string.functional_scenes_difficulty'))
377          .fontColor(Color.Black)
378          .opacity(0.6)
379          .textAlign(TextAlign.Start)
380          .maxLines(1)
381          .height(18)
382          .fontSize(12)
383          .width(25)
384
385        Rating({
386          rating: this.listData.ratingNumber,
387          indicator: true
388        } as RatingOptions)
389          .stars(5)
390          .width(70)
391      }
392      .margin({ bottom: 10 } as Margin)
393      .width('100%')
394      .justifyContent(FlexAlign.Start)
395    }
396    .shadow(ShadowStyle.OUTER_DEFAULT_XS)
397    .backgroundColor(Color.White)
398    .width('100%')
399    .borderRadius(8)
400    .margin({
401      top: 4,
402      bottom: 4
403    } as Margin)
404    .onClick((e: ClickEvent) => {
405    })
406  }
407}
408
409