1/* 2 * Copyright (c) 2023-2024 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 { Theme } from '@ohos.arkui.theme'; 17import { LengthMetrics } from '@ohos.arkui.node'; 18import { common, EnvironmentCallback } from '@kit.AbilityKit'; 19import { BusinessError } from '@kit.BasicServicesKit'; 20import { hilog } from '@kit.PerformanceAnalysisKit'; 21import measure from '@ohos.measure'; 22 23export enum IconType { 24 BADGE = 1, 25 NORMAL_ICON, 26 SYSTEM_ICON, 27 HEAD_SCULPTURE, 28 APP_ICON, 29 PREVIEW, 30 LONGITUDINAL, 31 VERTICAL 32} 33 34enum FontSizeScaleLevel { 35 LEVEL1 = 1.75, 36 LEVEL2 = 2, 37 LEVEL3 = 3.2 38} 39 40enum ItemHeight { 41 FIRST_HEIGHT = 48, 42 SECOND_HEIGHT = 56, 43 THIRD_HEIGHT = 64, 44 FOURTH_HEIGHT = 72, 45 FIFTH_HEIGHT = 96 46} 47 48export declare class OperateItem { 49 public icon?: OperateIcon; 50 public subIcon?: OperateIcon; 51 public button?: OperateButton; 52 public switch?: OperateCheck; 53 public checkbox?: OperateCheck; 54 public radio?: OperateCheck; 55 public image?: ResourceStr; 56 public symbolStyle?: SymbolGlyphModifier; 57 public text?: ResourceStr; 58 public arrow?: OperateIcon; 59} 60 61export declare class ContentItem { 62 public iconStyle?: IconType; 63 public icon?: ResourceStr; 64 public symbolStyle?: SymbolGlyphModifier; 65 public primaryText?: ResourceStr; 66 public secondaryText?: ResourceStr; 67 public description?: ResourceStr; 68} 69 70export declare class OperateIcon { 71 public value: ResourceStr; 72 public symbolStyle?: SymbolGlyphModifier; 73 public action?: () => void; 74 public accessibilityText?: ResourceStr; 75 public accessibilityDescription?: ResourceStr; 76 public accessibilityLevel?: string; 77} 78 79export declare class OperateButton { 80 public text?: ResourceStr; 81 public accessibilityText?: ResourceStr; 82 public accessibilityDescription?: ResourceStr; 83 public accessibilityLevel?: string; 84} 85 86export declare class OperateCheck { 87 public isCheck?: boolean; 88 public onChange?: (value: boolean) => void; 89 public accessibilityText?: ResourceStr; 90 public accessibilityDescription?: ResourceStr; 91 public accessibilityLevel?: string; 92} 93 94const TEXT_MAX_LINE = 1; 95const ITEM_BORDER_SHOWN = 2; 96const TEXT_COLUMN_SPACE = 4; 97const TEXT_SAFE_MARGIN = 8; 98const LISTITEM_PADDING = 6; 99const SWITCH_PADDING = 4; 100const STACK_PADDING = 4; 101const BADGE_SIZE = 8; 102const SMALL_ICON_SIZE = 16; 103const SYSTEM_ICON_SIZE = 24; 104const TEXT_ARROW_HEIGHT = 32; 105const SAFE_LIST_PADDING = 32; 106const HEADSCULPTURE_SIZE = 40; 107const BUTTON_SIZE = 28; 108const APP_ICON_SIZE = 64; 109const PREVIEW_SIZE = 96; 110const LONGITUDINAL_SIZE = 96; 111const VERTICAL_SIZE = 96; 112const NORMAL_ITEM_ROW_SPACE = 16; 113const SPECIAL_ITEM_ROW_SPACE = 0; 114const SPECIAL_ICON_SIZE = 0; 115const DEFAULT_ROW_SPACE = 0; 116const SPECICAL_ROW_SPACE = 4; 117const OPERATEITEM_ICONLIKE_SIZE = 24; 118const OPERATEITEM_SELECTIONBOX_PADDING_SIZE = 2; 119const OPERATEITEM_ARROW_WIDTH = 12 120const OPERATEITEM_ICON_CLICKABLE_SIZE = 40; 121const OPERATEITEM_IMAGE_SIZE = 48; 122const RIGHT_CONTENT_NULL_RIGHTWIDTH = '0vp'; 123const LEFT_PART_WIDTH = 'calc(66% - 16vp)'; 124const RIGHT_PART_WIDTH = '34%'; 125const RIGHT_ONLY_ARROW_WIDTH = '24vp'; 126const RIGHT_ONLY_IMAGE_WIDTH = '54vp'; 127const RIGHT_ONLY_ICON_WIDTH = '40vp'; 128const RIGHT_ICON_SUB_ICON_WIDTH = '80vp'; 129const RIGHT_ONLY_RADIO_WIDTH = '30vp'; 130const RIGHT_ONLY_CHECKBOX_WIDTH = '30vp'; 131const RIGHT_ONLY_SWITCH_WIDTH = '44vp'; 132const ACCESSIBILITY_LEVEL_AUTO = 'auto'; 133const ACCESSIBILITY_LEVEL_YES = 'yes'; 134const ACCESSIBILITY_LEVEL_NO = 'no'; 135const RESOURCE_TYPE_SYMBOL: number = 40000; 136 137const ICON_SIZE_MAP: Map<number, number> = new Map([ 138 [IconType.BADGE, BADGE_SIZE], 139 [IconType.NORMAL_ICON, SMALL_ICON_SIZE], 140 [IconType.SYSTEM_ICON, SYSTEM_ICON_SIZE], 141 [IconType.HEAD_SCULPTURE, HEADSCULPTURE_SIZE], 142 [IconType.APP_ICON, APP_ICON_SIZE], 143 [IconType.PREVIEW, PREVIEW_SIZE], 144 [IconType.LONGITUDINAL, LONGITUDINAL_SIZE], 145 [IconType.VERTICAL, VERTICAL_SIZE] 146]) 147// Does it support events such as focus, hover, press, etc. for the sub components of list 148const IS_SUPPORT_SUBCOMPONENT_EVENT: boolean = 149 LengthMetrics.resource($r('sys.float.composeListItem_focus_dynamic_effect')).value !== 1; 150const RECOVER_ITEM_SCALE: number = 1; 151const CLEAR_SHADOW: ShadowStyle = -1; 152const OPERATE_ITEM_RADIUS: number = 50; 153const DEFUALT_RADIO_CHECKBOX_BORDER_COLOR: ResourceColor = $r('sys.color.ohos_id_color_switch_outline_off'); 154const TEXT_SUPPORT_MARQUEE: number = 1; 155const IS_MARQUEE_OR_ELLIPSIS: number = LengthMetrics.resource($r('sys.float.composeListItem_right_textOverflow')).value; 156const UNUSUAL: number = -1; 157const FOCUSED_BG_COLOR: ResourceColor = $r('sys.color.composeListItem_container_focus_color'); 158const NORMAL_BG_COLOR: ResourceColor = $r('sys.color.composeListItem_container_normal_color'); 159const FOCUSED_ITEM_SCALE: number = LengthMetrics.resource($r('sys.float.composeListItem_focus_magnification')).value; 160const FOCUSED_SHADOW: ShadowStyle = LengthMetrics.resource($r('sys.float.composeListItem_focus_shadow_attribute')) 161 .value as ShadowStyle; 162const NORMAL_SHADOW: ShadowStyle = LengthMetrics.resource($r('sys.float.composeListItem_normal_shadow_attribute')) 163 .value as ShadowStyle; 164const ITEM_PADDING: Resource = $r('sys.float.composeListItem_padding'); 165const OPERATEITEM_ARROW_MARGIN_WIDTH: number = LengthMetrics.resource( 166 $r('sys.float.composeListItem_arrow_margin')).value; 167const APPICON_ITEMLENGTH: number = LengthMetrics.resource( 168 $r('sys.float.composeListItem_AppIcon_ItemLength')).value; 169 170class Util { 171 public static isSymbolResource(resourceStr: ResourceStr | undefined | null): boolean { 172 if (!Util.isResourceType(resourceStr)) { 173 return false; 174 } 175 let resource: Resource = resourceStr as Resource; 176 return resource.type === RESOURCE_TYPE_SYMBOL; 177 } 178 179 public static isResourceType(resource: ResourceStr | Resource | undefined | null): boolean { 180 if (!resource) { 181 return false; 182 } 183 if (typeof resource === 'string' || typeof resource === 'undefined') { 184 return false; 185 } 186 return true; 187 } 188} 189 190@Component 191struct ContentItemStruct { 192 @Prop @Watch('onPropChange') iconStyle: IconType | null = null; 193 @Prop @Watch('onPropChange') icon: ResourceStr | null = null; 194 @Prop @Watch('onPropChange') symbolStyle: SymbolGlyphModifier | null = null; 195 @Prop @Watch('onPropChange') primaryText: ResourceStr | null = null; 196 @Prop @Watch('onPropChange') secondaryText: ResourceStr | null = null; 197 @Prop @Watch('onPropChange') description: ResourceStr | null = null; 198 @State itemRowSpace: number = NORMAL_ITEM_ROW_SPACE; 199 @Prop leftWidth: string = LEFT_PART_WIDTH; 200 @State @Watch('onPropChange') primaryTextColor: ResourceColor = $r('sys.color.ohos_id_color_text_primary'); 201 @State @Watch('onPropChange') secondaryTextColor: ResourceColor = $r('sys.color.ohos_id_color_text_secondary'); 202 @State @Watch('onPropChange') descriptionColor: ResourceColor = $r('sys.color.ohos_id_color_text_secondary'); 203 @Prop fontSizeScale: number; 204 @Prop parentDirection: FlexDirection; 205 @Prop itemDirection: FlexDirection; 206 @Prop @Watch('onPropChange') isFocus: boolean = false; 207 @State primaryTextSize: string | number | Resource = $r('sys.float.ohos_id_text_size_body1'); 208 @State primaryTextColors: ResourceColor = $r('sys.color.font_primary'); 209 @Prop itemHeight: number | null = null; 210 @State iconColor: ResourceColor | null = null; 211 @State secondaryTextColors: ResourceColor = $r('sys.color.font_secondary'); 212 @State secondaryThirdTextSize: string | number | Resource = 213 $r('sys.float.composeListItem_left_secondary_tertiary_text_size'); 214 @State descriptionColors: ResourceColor = $r('sys.color.font_tertiary'); 215 @Link isWrapText: Boolean; 216 @State @Watch('onWrapChange') isWrapFirstText: Boolean = false; 217 @State @Watch('onWrapChange') isWrapSecondText: Boolean = false; 218 @State @Watch('onWrapChange') isWrapThirdText: Boolean = false; 219 220 onWillApplyTheme(theme: Theme): void { 221 this.primaryTextColor = theme.colors.fontPrimary; 222 this.secondaryTextColor = theme.colors.fontSecondary; 223 this.descriptionColor = theme.colors.fontTertiary; 224 } 225 226 onPropChange(): void { 227 if (this.icon == null && this.symbolStyle == null && this.iconStyle == null) { 228 this.itemRowSpace = SPECIAL_ITEM_ROW_SPACE; 229 } else { 230 this.itemRowSpace = NORMAL_ITEM_ROW_SPACE; 231 } 232 if (!IS_SUPPORT_SUBCOMPONENT_EVENT && this.isFocus) { 233 this.primaryTextColors = $r('sys.color.composeListItem_left_text_focus_color'); 234 this.secondaryTextColors = $r('sys.color.composeListItem_left_secondary_text_focus_color'); 235 this.descriptionColors = $r('sys.color.composeListItem_left_secondary_text_focus_color'); 236 } else { 237 this.primaryTextColors = this.primaryTextColor; 238 this.secondaryTextColors = this.secondaryTextColor; 239 this.descriptionColors = this.descriptionColor; 240 } 241 } 242 243 onWrapChange(): void { 244 this.isWrapText = this.isWrapFirstText || this.isWrapSecondText || this.isWrapThirdText; 245 } 246 247 getContentItemIconFillColor(): ResourceColor { 248 switch (this.iconStyle) { 249 case IconType.BADGE: 250 return $r('sys.color.composeListItem_badge_color'); 251 case IconType.SYSTEM_ICON: 252 return $r('sys.color.composeListItem_icon_normal_color'); 253 default: 254 return $r('sys.color.ohos_id_color_secondary'); 255 } 256 } 257 258 judgeIsWrap(text: ResourceStr | null, sizeResource: Length, newHeight: number): boolean { 259 let singleRowHeight = this.getSingleRowTextHeight(text, sizeResource); 260 return newHeight > singleRowHeight; 261 } 262 263 getSingleRowTextHeight(text: ResourceStr | null, sizeResource: Length): number { 264 if (text && sizeResource) { 265 let singleRowHeight = px2vp(measure.measureTextSize({ 266 textContent: text, 267 fontSize: sizeResource, 268 maxLines: TEXT_MAX_LINE 269 }).height as number); 270 return singleRowHeight; 271 } 272 return 0; 273 } 274 275 aboutToAppear(): void { 276 this.onPropChange(); 277 } 278 279 @Builder 280 createIcon() { 281 if (this.iconStyle != null && ICON_SIZE_MAP.has(this.iconStyle)) { 282 if (this.symbolStyle != null) { 283 SymbolGlyph() 284 .fontColor([this.getContentItemIconFillColor()]) 285 .attributeModifier(this.symbolStyle) 286 .fontSize(`${ICON_SIZE_MAP.get(this.iconStyle)}vp`) 287 .effectStrategy(SymbolEffectStrategy.NONE) 288 .symbolEffect(new SymbolEffect(), false) 289 .borderRadius($r('sys.float.composeListItem_Image_Radius')) 290 .focusable(false) 291 .draggable(false) 292 .flexShrink(0) 293 } else if (this.icon != null) { 294 if (Util.isSymbolResource(this.icon)) { 295 SymbolGlyph(this.icon as Resource) 296 .fontSize(`${ICON_SIZE_MAP.get(this.iconStyle)}vp`) 297 .fontColor([this.getContentItemIconFillColor()]) 298 .borderRadius($r('sys.float.composeListItem_Image_Radius')) 299 .focusable(false) 300 .draggable(false) 301 .flexShrink(0) 302 } else { 303 if (this.iconStyle <= IconType.PREVIEW) { 304 Image(this.icon) 305 .objectFit(ImageFit.Contain) 306 .width(ICON_SIZE_MAP.get(this.iconStyle)) 307 .height(ICON_SIZE_MAP.get(this.iconStyle)) 308 .borderRadius($r('sys.float.composeListItem_Image_Radius')) 309 .focusable(false) 310 .draggable(false) 311 .fillColor(this.getContentItemIconFillColor()) 312 .flexShrink(0) 313 } else { 314 Image(this.icon) 315 .objectFit(ImageFit.Contain) 316 .constraintSize({ 317 minWidth: SPECIAL_ICON_SIZE, 318 maxWidth: ICON_SIZE_MAP.get(this.iconStyle), 319 minHeight: SPECIAL_ICON_SIZE, 320 maxHeight: ICON_SIZE_MAP.get(this.iconStyle) 321 }) 322 .borderRadius($r('sys.float.composeListItem_Image_Radius')) 323 .focusable(false) 324 .draggable(false) 325 .fillColor(this.getContentItemIconFillColor()) 326 .flexShrink(0) 327 } 328 } 329 } 330 } 331 } 332 333 @Builder 334 createText() { 335 Column({ space: TEXT_COLUMN_SPACE }) { 336 Text(this.primaryText) 337 .fontSize(this.primaryTextSize) 338 .fontColor(this.primaryTextColors) 339 .textOverflow({ 340 overflow: IS_MARQUEE_OR_ELLIPSIS === TEXT_SUPPORT_MARQUEE ? TextOverflow.None : 341 TextOverflow.Ellipsis 342 }) 343 .fontWeight(FontWeight.Medium) 344 .focusable(true) 345 .draggable(false) 346 .onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => { 347 if (!IS_SUPPORT_SUBCOMPONENT_EVENT) { 348 this.isWrapFirstText = this.judgeIsWrap(this.primaryText, this.primaryTextSize, 349 newValue.height as number); 350 } 351 }) 352 if (this.secondaryText != null) { 353 Text(this.secondaryText) 354 .fontSize(this.secondaryThirdTextSize) 355 .fontColor(this.secondaryTextColors) 356 .textOverflow({ 357 overflow: IS_MARQUEE_OR_ELLIPSIS === TEXT_SUPPORT_MARQUEE ? TextOverflow.None : 358 TextOverflow.Ellipsis 359 }) 360 .draggable(false) 361 .onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => { 362 if (!IS_SUPPORT_SUBCOMPONENT_EVENT) { 363 this.isWrapSecondText = this.judgeIsWrap(this.secondaryText, this.secondaryThirdTextSize, 364 newValue.height as number); 365 } 366 }) 367 } 368 if (this.description != null) { 369 Text(this.description) 370 .fontSize(this.secondaryThirdTextSize) 371 .fontColor(this.descriptionColors) 372 .textOverflow({ 373 overflow: IS_MARQUEE_OR_ELLIPSIS === TEXT_SUPPORT_MARQUEE ? TextOverflow.None : 374 TextOverflow.Ellipsis 375 }) 376 .draggable(false) 377 .onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => { 378 if (!IS_SUPPORT_SUBCOMPONENT_EVENT) { 379 this.isWrapThirdText = this.judgeIsWrap(this.description, this.secondaryThirdTextSize, 380 newValue.height as number); 381 } 382 }) 383 } 384 } 385 .flexShrink(1) 386 .margin(this.fontSizeScale >= FontSizeScaleLevel.LEVEL1 ? undefined : { 387 top: TEXT_SAFE_MARGIN, 388 bottom: TEXT_SAFE_MARGIN 389 }) 390 .alignItems(HorizontalAlign.Start) 391 } 392 393 isColumnDirection(): boolean { 394 return this.itemDirection === FlexDirection.Column; 395 } 396 397 isParentColumnDirection(): boolean { 398 return this.parentDirection === FlexDirection.Column; 399 } 400 401 getItemSpace() { 402 if (this.isColumnDirection()) { 403 return LengthMetrics.resource($r('sys.float.padding_level1')); 404 } 405 return LengthMetrics.vp(this.itemRowSpace); 406 } 407 408 build() { 409 Flex({ 410 space: { main: this.getItemSpace() }, 411 direction: this.itemDirection, 412 justifyContent: FlexAlign.Start, 413 alignItems: this.isColumnDirection() ? ItemAlign.Start : ItemAlign.Center, 414 }) { 415 this.createIcon(); 416 this.createText(); 417 } 418 .height(this.itemDirection === FlexDirection.Column ? 'auto' : undefined) 419 .margin({ 420 end: this.isParentColumnDirection() ? 421 LengthMetrics.vp(0) : 422 LengthMetrics.vp(16) 423 }) 424 .padding({ start: LengthMetrics.vp(LISTITEM_PADDING) }) 425 .flexShrink(this.isParentColumnDirection() ? 0 : 1) 426 } 427} 428 429class CreateIconParam { 430 public icon?: OperateIcon; 431} 432 433class OperateItemStructController { 434 public changeRadioState = () => { 435 }; 436 public changeCheckboxState = () => { 437 }; 438 public changeSwitchState = () => { 439 }; 440} 441 442@Component 443struct OperateItemStruct { 444 @Prop @Watch('onPropChange') arrow: OperateIcon | null = null; 445 @Prop @Watch('onPropChange') icon: OperateIcon | null = null; 446 @Prop @Watch('onPropChange') subIcon: OperateIcon | null = null; 447 @Prop @Watch('onPropChange') button: OperateButton | null = null; 448 @Prop @Watch('onPropChange') switch: OperateCheck | null = null; 449 @Prop @Watch('onPropChange') checkBox: OperateCheck | null = null; 450 @Prop @Watch('onPropChange') radio: OperateCheck | null = null; 451 @Prop @Watch('onPropChange') image: ResourceStr | null = null; 452 @Prop @Watch('onPropChange') symbolStyle: SymbolGlyphModifier | null = null; 453 @Prop @Watch('onPropChange') text: ResourceStr | null = null; 454 @State switchState: boolean = false; 455 @State radioState: boolean = false; 456 @State checkBoxState: boolean = false; 457 @Prop rightWidth: string = RIGHT_PART_WIDTH; 458 @State @Watch('onFocusChange') secondaryTextColor: ResourceColor = $r('sys.color.ohos_id_color_text_secondary'); 459 @State hoveringColor: ResourceColor = '#0d000000'; 460 @State activedColor: ResourceColor = '#1a0a59f7'; 461 @Link parentCanFocus: boolean; 462 @Link parentCanTouch: boolean; 463 @Link parentIsHover: boolean; 464 @Link parentCanHover: boolean; 465 @Link parentIsActive: boolean; 466 @Link parentFrontColor: ResourceColor; 467 @Link parentDirection: FlexDirection; 468 @State rowSpace: number = DEFAULT_ROW_SPACE; 469 @Link @Watch('onFocusChange') isFocus: boolean; 470 @State secondaryTextSize: Length = $r('sys.float.ohos_id_text_size_body2'); 471 @State secondaryTextColors: ResourceColor = $r('sys.color.font_secondary'); 472 @State iconColor: ResourceColor = $r('sys.color.composeListItem_right_icon_normal_color'); 473 private controller: OperateItemStructController = new OperateItemStructController(); 474 475 onWillApplyTheme(theme: Theme): void { 476 this.secondaryTextColor = theme.colors.fontSecondary; 477 this.hoveringColor = theme.colors.interactiveHover; 478 this.activedColor = theme.colors.interactiveActive; 479 } 480 481 onFocusChange() { 482 if (!IS_SUPPORT_SUBCOMPONENT_EVENT && this.isFocus) { 483 this.secondaryTextColors = $r('sys.color.composeListItem_right_text_focus_color'); 484 } else { 485 this.secondaryTextColors = this.secondaryTextColor; 486 } 487 this.iconColor = this.isFocus ? $r('sys.color.composeListItem_right_icon_focus_color') : 488 $r('sys.color.composeListItem_right_icon_normal_color'); 489 } 490 491 onPropChange(): void { 492 if (this.switch != null) { 493 this.switchState = this.switch.isCheck as boolean; 494 } 495 if (this.radio != null) { 496 this.radioState = this.radio.isCheck as boolean; 497 } 498 if (this.checkBox != null) { 499 this.checkBoxState = this.checkBox.isCheck as boolean; 500 } 501 502 if ((this.button == null && this.image == null && this.symbolStyle == null && this.text != null) && 503 ((this.icon != null) || (this.icon == null && this.arrow != null))) { 504 this.rowSpace = SPECICAL_ROW_SPACE; 505 } else { 506 this.rowSpace = DEFAULT_ROW_SPACE; 507 } 508 } 509 510 aboutToAppear(): void { 511 this.onPropChange(); 512 this.onFocusChange(); 513 if (this.controller) { 514 this.controller.changeRadioState = this.changeRadioState; 515 this.controller.changeCheckboxState = this.changeCheckboxState; 516 this.controller.changeSwitchState = this.changeSwitchState; 517 } 518 } 519 520 changeRadioState = () => { 521 this.radioState = !this.radioState; 522 }; 523 changeCheckboxState = () => { 524 this.checkBoxState = !this.checkBoxState; 525 }; 526 changeSwitchState = () => { 527 this.switchState = !this.switchState; 528 }; 529 530 @Builder 531 createButton() { 532 Button() { 533 Row() { 534 Text(this.button?.text as ResourceStr) 535 .focusable(true) 536 } 537 .padding({ 538 left: TEXT_SAFE_MARGIN, 539 right: TEXT_SAFE_MARGIN 540 }) 541 } 542 .padding({ top: 0, bottom: 0 }) 543 .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) }) 544 .hitTestBehavior(IS_SUPPORT_SUBCOMPONENT_EVENT ? HitTestMode.Block : HitTestMode.None) 545 .fontSize($r('sys.float.ohos_id_text_size_button3')) 546 .fontColor($r('sys.color.ohos_id_color_text_primary_activated_transparent')) 547 .constraintSize({ 548 minHeight: BUTTON_SIZE 549 }) 550 .backgroundColor($r('sys.color.ohos_id_color_button_normal')) 551 .labelStyle({ 552 maxLines: TEXT_MAX_LINE 553 }) 554 .onFocus(() => { 555 this.parentCanFocus = false; 556 }) 557 .onHover((isHover: boolean) => { 558 this.parentCanHover = false; 559 if (isHover && this.parentFrontColor === this.hoveringColor && IS_SUPPORT_SUBCOMPONENT_EVENT) { 560 this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString(); 561 } 562 if (!isHover) { 563 this.parentCanHover = true; 564 if (this.parentIsHover) { 565 this.parentFrontColor = this.parentIsHover ? this.hoveringColor : 566 (this.parentIsActive ? this.activedColor : Color.Transparent.toString()); 567 } 568 } 569 }) 570 .accessibilityLevel(this.button?.accessibilityLevel ?? ACCESSIBILITY_LEVEL_AUTO) 571 .accessibilityText(getAccessibilityText(this.button?.accessibilityText ?? '')) 572 .accessibilityDescription(getAccessibilityText(this.button?.accessibilityDescription ?? '')) 573 } 574 575 @Builder 576 createIcon(param: CreateIconParam) { 577 Button({ type: ButtonType.Normal }) { 578 if (param.icon?.symbolStyle) { 579 SymbolGlyph() 580 .fontColor([this.iconColor]) 581 .attributeModifier(param.icon?.symbolStyle) 582 .fontSize(`${OPERATEITEM_ICONLIKE_SIZE}vp`) 583 .effectStrategy(SymbolEffectStrategy.NONE) 584 .symbolEffect(new SymbolEffect(), false) 585 .focusable(true) 586 .draggable(false) 587 } else { 588 if (Util.isSymbolResource(param.icon?.value)) { 589 SymbolGlyph(param.icon?.value as Resource) 590 .fontSize(`${OPERATEITEM_ICONLIKE_SIZE}vp`) 591 .fontColor([this.iconColor]) 592 .focusable(true) 593 .draggable(false) 594 } else { 595 Image(param.icon?.value) 596 .height(OPERATEITEM_ICONLIKE_SIZE) 597 .width(OPERATEITEM_ICONLIKE_SIZE) 598 .focusable(true) 599 .fillColor(this.iconColor) 600 .draggable(false) 601 } 602 } 603 } 604 .shadow(CLEAR_SHADOW) 605 .hitTestBehavior(IS_SUPPORT_SUBCOMPONENT_EVENT ? HitTestMode.Block : HitTestMode.None) 606 .backgroundColor(Color.Transparent) 607 .height(OPERATEITEM_ICON_CLICKABLE_SIZE) 608 .width(OPERATEITEM_ICON_CLICKABLE_SIZE) 609 .borderRadius($r('sys.float.ohos_id_corner_radius_clicked')) 610 .onFocus(() => { 611 this.parentCanFocus = false; 612 }) 613 .onHover((isHover: boolean) => { 614 this.parentCanHover = false; 615 if (isHover && this.parentFrontColor === this.hoveringColor && IS_SUPPORT_SUBCOMPONENT_EVENT) { 616 this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString(); 617 } 618 if (!isHover) { 619 this.parentCanHover = true; 620 if (this.parentIsHover) { 621 this.parentFrontColor = this.parentIsHover ? this.hoveringColor : 622 (this.parentIsActive ? this.activedColor : Color.Transparent.toString()); 623 } 624 } 625 }) 626 .onClick(param.icon?.action) 627 .accessibilityLevel(getAccessibilityLevelOnAction(param.icon?.accessibilityLevel, param.icon?.action)) 628 .accessibilityText(getAccessibilityText(param.icon?.accessibilityText ?? '')) 629 .accessibilityDescription(getAccessibilityText(param.icon?.accessibilityDescription ?? '')) 630 .flexShrink(0) 631 } 632 633 @Builder 634 createImage() { 635 if (Util.isSymbolResource(this.image)) { 636 SymbolGlyph(this.image as Resource) 637 .fontSize(`${OPERATEITEM_IMAGE_SIZE}vp`) 638 .draggable(false) 639 .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) }) 640 } else { 641 Image(this.image) 642 .height(OPERATEITEM_IMAGE_SIZE) 643 .width(OPERATEITEM_IMAGE_SIZE) 644 .draggable(false) 645 .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) }) 646 } 647 } 648 649 @Builder 650 createSymbol() { 651 SymbolGlyph() 652 .attributeModifier(this.symbolStyle) 653 .fontSize(`${OPERATEITEM_IMAGE_SIZE}vp`) 654 .effectStrategy(SymbolEffectStrategy.NONE) 655 .symbolEffect(new SymbolEffect(), false) 656 .draggable(false) 657 .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) }) 658 } 659 660 @Builder 661 createText() { 662 Text(this.text) 663 .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) }) 664 .fontSize(this.secondaryTextSize) 665 .fontColor(this.secondaryTextColors) 666 .textOverflow({ 667 overflow: IS_MARQUEE_OR_ELLIPSIS === TEXT_SUPPORT_MARQUEE ? TextOverflow.MARQUEE : 668 TextOverflow.None 669 }) 670 .marqueeOptions({ 671 start: this.isFocus || this.parentIsHover, 672 fadeout: true, 673 marqueeStartPolicy: MarqueeStartPolicy.DEFAULT 674 }) 675 .maxLines(LengthMetrics.resource($r('sys.float.composeListItem_maxLines_right')).value) 676 .draggable(false) 677 .flexShrink(1) 678 } 679 680 @Builder 681 createArrow() { 682 Button({ type: ButtonType.Normal }) { 683 if (this.arrow?.symbolStyle) { 684 SymbolGlyph() 685 .fontColor([IS_SUPPORT_SUBCOMPONENT_EVENT ? $r('sys.color.ohos_id_color_fourth') : this.iconColor]) 686 .attributeModifier(this.arrow?.symbolStyle) 687 .fontSize(`${OPERATEITEM_ICONLIKE_SIZE}vp`) 688 .effectStrategy(SymbolEffectStrategy.NONE) 689 .symbolEffect(new SymbolEffect(), false) 690 .focusable(true) 691 .draggable(false) 692 } else { 693 if (Util.isSymbolResource(this.arrow?.value)) { 694 SymbolGlyph(this.arrow?.value as Resource) 695 .fontSize(`${OPERATEITEM_ICONLIKE_SIZE}vp`) 696 .fontColor([IS_SUPPORT_SUBCOMPONENT_EVENT ? $r('sys.color.ohos_id_color_fourth') : this.iconColor]) 697 .focusable(true) 698 .draggable(false) 699 } else { 700 Image(this.arrow?.value) 701 .height(OPERATEITEM_ICONLIKE_SIZE) 702 .width(OPERATEITEM_ARROW_WIDTH) 703 .focusable(true) 704 .fillColor(IS_SUPPORT_SUBCOMPONENT_EVENT ? $r('sys.color.ohos_id_color_fourth') : this.iconColor) 705 .draggable(false) 706 .matchTextDirection(true) 707 } 708 } 709 } 710 .shadow(CLEAR_SHADOW) 711 .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) }) 712 .hitTestBehavior(IS_SUPPORT_SUBCOMPONENT_EVENT ? 713 (this.arrow?.action !== undefined ? HitTestMode.Block : HitTestMode.Transparent) : HitTestMode.None) 714 .backgroundColor(Color.Transparent) 715 .height(OPERATEITEM_ICONLIKE_SIZE) 716 .width(OPERATEITEM_ARROW_WIDTH) 717 .onFocus(() => { 718 this.parentCanFocus = false; 719 }) 720 .stateEffect(this.arrow?.action !== undefined) 721 .hoverEffect(this.arrow?.action !== undefined ? HoverEffect.Auto : HoverEffect.None) 722 .onHover((isHover: boolean) => { 723 if (this.arrow?.action === undefined) { 724 return; 725 } 726 if (isHover && IS_SUPPORT_SUBCOMPONENT_EVENT) { 727 this.parentCanHover = false; 728 this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString(); 729 } else { 730 this.parentCanHover = true; 731 if (this.parentIsHover) { 732 this.parentFrontColor = this.parentIsHover ? this.hoveringColor : 733 (this.parentIsActive ? this.activedColor : Color.Transparent.toString()); 734 } 735 } 736 }) 737 .onClick(this.arrow?.action) 738 .accessibilityLevel(getAccessibilityLevelOnAction(this.arrow?.accessibilityLevel, this.arrow?.action)) 739 .accessibilityText(getAccessibilityText(this.arrow?.accessibilityText ?? '')) 740 .accessibilityDescription(getAccessibilityText(this.arrow?.accessibilityDescription ?? '')) 741 } 742 743 @Builder 744 createRadio() { 745 Radio({ value: '', group: '' }) 746 .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) }) 747 .checked(this.radioState) 748 .radioStyle({ 749 uncheckedBorderColor: DEFUALT_RADIO_CHECKBOX_BORDER_COLOR 750 }) 751 .backgroundColor(Color.Transparent) 752 .borderRadius(OPERATE_ITEM_RADIUS) 753 .onChange((isCheck: boolean) => { 754 if (!IS_SUPPORT_SUBCOMPONENT_EVENT) { 755 this.radioState = isCheck; 756 } 757 if (this.radio?.onChange) { 758 this.radio?.onChange(isCheck); 759 } 760 }) 761 .height(OPERATEITEM_ICONLIKE_SIZE) 762 .width(OPERATEITEM_ICONLIKE_SIZE) 763 .padding(OPERATEITEM_SELECTIONBOX_PADDING_SIZE) 764 .onFocus(() => { 765 this.parentCanFocus = false; 766 }) 767 .hitTestBehavior(IS_SUPPORT_SUBCOMPONENT_EVENT ? HitTestMode.Block : HitTestMode.None) 768 .flexShrink(0) 769 .onHover((isHover: boolean) => { 770 this.parentCanHover = false; 771 if (isHover && this.parentFrontColor === this.hoveringColor && IS_SUPPORT_SUBCOMPONENT_EVENT) { 772 this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString(); 773 } 774 if (!isHover) { 775 this.parentCanHover = true; 776 if (this.parentIsHover) { 777 this.parentFrontColor = this.parentIsHover ? this.hoveringColor : 778 (this.parentIsActive ? this.activedColor : Color.Transparent.toString()); 779 } 780 } 781 }) 782 .accessibilityLevel(getAccessibilityLevelOnChange(this.radio?.accessibilityLevel, this.radio?.onChange)) 783 .accessibilityText(getAccessibilityText(this.radio?.accessibilityText ?? '')) 784 .accessibilityDescription(getAccessibilityText(this.radio?.accessibilityDescription ?? '')) 785 } 786 787 @Builder 788 createCheckBox() { 789 Checkbox() 790 .borderRadius(IS_SUPPORT_SUBCOMPONENT_EVENT ? UNUSUAL : OPERATE_ITEM_RADIUS) 791 .unselectedColor(DEFUALT_RADIO_CHECKBOX_BORDER_COLOR) 792 .backgroundColor(Color.Transparent) 793 .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) }) 794 .select(this.checkBoxState) 795 .onChange((isCheck: boolean) => { 796 if (!IS_SUPPORT_SUBCOMPONENT_EVENT) { 797 this.checkBoxState = isCheck; 798 } 799 if (this.checkBox?.onChange) { 800 this.checkBox?.onChange(isCheck); 801 } 802 }) 803 .height(OPERATEITEM_ICONLIKE_SIZE) 804 .width(OPERATEITEM_ICONLIKE_SIZE) 805 .padding(OPERATEITEM_SELECTIONBOX_PADDING_SIZE) 806 .onFocus(() => { 807 this.parentCanFocus = false; 808 }) 809 .hitTestBehavior(IS_SUPPORT_SUBCOMPONENT_EVENT ? HitTestMode.Block : HitTestMode.None) 810 .flexShrink(0) 811 .onHover((isHover: boolean) => { 812 this.parentCanHover = false; 813 if (isHover && this.parentFrontColor === this.hoveringColor && IS_SUPPORT_SUBCOMPONENT_EVENT) { 814 this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString(); 815 } 816 if (!isHover) { 817 this.parentCanHover = true; 818 if (this.parentIsHover) { 819 this.parentFrontColor = this.parentIsHover ? this.hoveringColor : 820 (this.parentIsActive ? this.activedColor : Color.Transparent.toString()); 821 } 822 } 823 }) 824 .accessibilityLevel(getAccessibilityLevelOnChange(this.checkBox?.accessibilityLevel, this.checkBox?.onChange)) 825 .accessibilityText(getAccessibilityText(this.checkBox?.accessibilityText ?? '')) 826 .accessibilityDescription(getAccessibilityText(this.checkBox?.accessibilityDescription ?? '')) 827 } 828 829 @Builder 830 createSwitch() { 831 Row() { 832 Toggle({ type: ToggleType.Switch, isOn: this.switchState }) 833 .borderRadius(IS_SUPPORT_SUBCOMPONENT_EVENT ? UNUSUAL : OPERATE_ITEM_RADIUS) 834 .backgroundColor(Color.Transparent) 835 .onChange((isCheck: boolean) => { 836 this.switchState = isCheck; 837 if (this.switch?.onChange) { 838 this.switch?.onChange(isCheck); 839 } 840 }) 841 .onClick(() => { 842 this.switchState = !this.switchState; 843 }) 844 .hitTestBehavior(IS_SUPPORT_SUBCOMPONENT_EVENT ? HitTestMode.Block : HitTestMode.None) 845 .accessibilityLevel(getAccessibilityLevelOnChange(this.switch?.accessibilityLevel, this.switch?.onChange)) 846 .accessibilityText(getAccessibilityText(this.switch?.accessibilityText ?? '')) 847 .accessibilityDescription(getAccessibilityText(this.switch?.accessibilityDescription ?? '')) 848 } 849 .margin({ end: LengthMetrics.vp(SWITCH_PADDING) }) 850 .height(OPERATEITEM_ICON_CLICKABLE_SIZE) 851 .width(OPERATEITEM_ICON_CLICKABLE_SIZE) 852 .justifyContent(FlexAlign.Center) 853 .onFocus(() => { 854 this.parentCanFocus = false; 855 }) 856 .onHover((isHover: boolean) => { 857 this.parentCanHover = false; 858 if (isHover && this.parentFrontColor === this.hoveringColor && IS_SUPPORT_SUBCOMPONENT_EVENT) { 859 this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString(); 860 } 861 if (!isHover) { 862 this.parentCanHover = true; 863 if (this.parentIsHover) { 864 this.parentFrontColor = this.parentIsHover ? this.hoveringColor : 865 (this.parentIsActive ? this.activedColor : Color.Transparent.toString()); 866 } 867 } 868 }) 869 } 870 871 @Builder 872 createTextArrow() { 873 Button({ type: ButtonType.Normal }) { 874 if (this.parentDirection === FlexDirection.Column) { 875 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { 876 Text(this.text) 877 .fontSize($r('sys.float.ohos_id_text_size_body2')) 878 .fontColor(this.secondaryTextColor) 879 .focusable(true) 880 .draggable(false) 881 .constraintSize({ 882 maxWidth: `calc(100% - ${OPERATEITEM_ARROW_WIDTH}vp)` 883 }) 884 if (this.arrow?.symbolStyle) { 885 SymbolGlyph() 886 .fontColor([$r('sys.color.ohos_id_color_fourth')]) 887 .attributeModifier(this.arrow?.symbolStyle) 888 .fontSize(`${OPERATEITEM_ICONLIKE_SIZE}vp`) 889 .effectStrategy(SymbolEffectStrategy.NONE) 890 .symbolEffect(new SymbolEffect(), false) 891 .focusable(false) 892 .draggable(false) 893 } else { 894 if (Util.isSymbolResource(this.arrow?.value)) { 895 SymbolGlyph(this.arrow?.value as Resource) 896 .fontSize(`${OPERATEITEM_ICONLIKE_SIZE}vp`) 897 .fontColor([$r('sys.color.ohos_id_color_fourth')]) 898 .focusable(false) 899 .draggable(false) 900 } else { 901 Image(this.arrow?.value) 902 .height(OPERATEITEM_ICONLIKE_SIZE) 903 .width(OPERATEITEM_ARROW_WIDTH) 904 .fillColor($r('sys.color.ohos_id_color_fourth')) 905 .focusable(false) 906 .draggable(false) 907 .matchTextDirection(true) 908 } 909 } 910 } 911 .padding({ 912 start: LengthMetrics.vp(TEXT_SAFE_MARGIN), 913 end: LengthMetrics.vp(LISTITEM_PADDING) 914 }) 915 } else { 916 Row({ space: SPECICAL_ROW_SPACE }) { 917 Text(this.text) 918 .fontSize(this.secondaryTextSize) 919 .fontColor(this.secondaryTextColors) 920 .textOverflow({ 921 overflow: IS_MARQUEE_OR_ELLIPSIS === TEXT_SUPPORT_MARQUEE ? TextOverflow.MARQUEE : 922 TextOverflow.None 923 }) 924 .marqueeOptions({ 925 start: this.isFocus || this.parentIsHover, 926 fadeout: true, 927 marqueeStartPolicy: MarqueeStartPolicy.DEFAULT 928 }) 929 .maxLines(LengthMetrics.resource($r('sys.float.composeListItem_maxLines_right')).value) 930 .focusable(true) 931 .draggable(false) 932 .constraintSize({ 933 maxWidth: `calc(100% - ${OPERATEITEM_ARROW_WIDTH + OPERATEITEM_ARROW_MARGIN_WIDTH}vp)` 934 }) 935 .margin({ right: OPERATEITEM_ARROW_MARGIN_WIDTH }) 936 if (this.arrow?.symbolStyle) { 937 SymbolGlyph() 938 .fontColor([IS_SUPPORT_SUBCOMPONENT_EVENT ? $r('sys.color.icon_fourth') : this.iconColor]) 939 .attributeModifier(this.arrow?.symbolStyle) 940 .fontSize(`${OPERATEITEM_ICONLIKE_SIZE}vp`) 941 .effectStrategy(SymbolEffectStrategy.NONE) 942 .symbolEffect(new SymbolEffect(), false) 943 .focusable(false) 944 .draggable(false) 945 } else { 946 if (Util.isSymbolResource(this.arrow?.value)) { 947 SymbolGlyph(this.arrow?.value as Resource) 948 .fontSize(`${OPERATEITEM_ICONLIKE_SIZE}vp`) 949 .fontColor([IS_SUPPORT_SUBCOMPONENT_EVENT ? $r('sys.color.icon_fourth') : this.iconColor]) 950 .focusable(false) 951 .draggable(false) 952 } else { 953 Image(this.arrow?.value) 954 .height(OPERATEITEM_ICONLIKE_SIZE) 955 .width(OPERATEITEM_ARROW_WIDTH) 956 .fillColor(IS_SUPPORT_SUBCOMPONENT_EVENT ? $r('sys.color.icon_fourth') : this.iconColor) 957 .focusable(false) 958 .draggable(false) 959 .matchTextDirection(true) 960 } 961 } 962 } 963 .padding({ 964 start: LengthMetrics.vp(TEXT_SAFE_MARGIN), 965 end: LengthMetrics.vp(LISTITEM_PADDING) 966 }) 967 } 968 } 969 .shadow(CLEAR_SHADOW) 970 .hitTestBehavior(IS_SUPPORT_SUBCOMPONENT_EVENT ? 971 (this.arrow?.action !== undefined ? HitTestMode.Block : HitTestMode.Transparent) : HitTestMode.None) 972 .labelStyle({ 973 maxLines: TEXT_MAX_LINE 974 }) 975 .backgroundColor(Color.Transparent) 976 .constraintSize({ minHeight: TEXT_ARROW_HEIGHT }) 977 .borderRadius($r('sys.float.ohos_id_corner_radius_clicked')) 978 .onFocus(() => { 979 this.parentCanFocus = false; 980 }) 981 .padding({ 982 top: 0, 983 bottom: 0, 984 left: 0, 985 right: 0 986 }) 987 .stateEffect(this.arrow?.action !== undefined) 988 .hoverEffect(this.arrow?.action !== undefined ? HoverEffect.Auto : HoverEffect.None) 989 .onHover((isHover: boolean) => { 990 if (this.arrow?.action === undefined) { 991 return; 992 } 993 if (isHover && IS_SUPPORT_SUBCOMPONENT_EVENT) { 994 this.parentCanHover = false; 995 this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString(); 996 } else { 997 this.parentCanHover = true; 998 if (this.parentIsHover) { 999 this.parentFrontColor = this.parentIsHover ? this.hoveringColor : 1000 (this.parentIsActive ? this.activedColor : Color.Transparent.toString()); 1001 } 1002 } 1003 }) 1004 .onClick(this.arrow?.action) 1005 .accessibilityLevel(getAccessibilityLevelOnAction(this.arrow?.accessibilityLevel, this.arrow?.action)) 1006 .accessibilityText(`${this.text} ${getAccessibilityText(this.arrow?.accessibilityText ?? '')}`) 1007 .accessibilityDescription(getAccessibilityText(this.arrow?.accessibilityDescription ?? '')) 1008 } 1009 1010 getFlexOptions(): FlexOptions { 1011 let flexOptions: FlexOptions = { alignItems: ItemAlign.Center }; 1012 if (this.parentDirection === FlexDirection.Column) { 1013 flexOptions.justifyContent = FlexAlign.SpaceBetween; 1014 } else { 1015 flexOptions.space = { main: LengthMetrics.vp(this.rowSpace) }; 1016 flexOptions.justifyContent = FlexAlign.End; 1017 } 1018 return flexOptions; 1019 } 1020 1021 build() { 1022 Flex(this.getFlexOptions()) { 1023 if (this.button != null) { 1024 this.createButton(); 1025 } else if (this.symbolStyle != null) { 1026 this.createSymbol(); 1027 } else if (this.image != null) { 1028 this.createImage(); 1029 } else if (this.icon != null && this.text != null) { 1030 this.createText(); 1031 this.createIcon({ icon: this.icon }) 1032 } else if (this.arrow != null && (this.text == null || this.text == '')) { 1033 this.createArrow(); 1034 } else if (this.arrow != null && this.text != null) { 1035 this.createTextArrow(); 1036 } else if (this.text != null) { 1037 this.createText(); 1038 } else if (this.radio != null) { 1039 this.createRadio(); 1040 } else if (this.checkBox != null) { 1041 this.createCheckBox(); 1042 } else if (this.switch != null) { 1043 this.createSwitch(); 1044 } else if (this.icon != null) { 1045 this.createIcon({ icon: this.icon }); 1046 if (this.subIcon != null) { 1047 this.createIcon({ icon: this.subIcon }); 1048 } 1049 } 1050 } 1051 .width(this.parentDirection === FlexDirection.Column ? undefined : this.rightWidth) 1052 } 1053} 1054 1055/** 1056 * Obtain accessible text 1057 * 1058 * @param resource initial resource 1059 * @param selected select state 1060 * @returns string 1061 */ 1062function getAccessibilityText(resource: ResourceStr): string { 1063 try { 1064 let resourceString: string = ''; 1065 if (typeof resource === 'string') { 1066 resourceString = resource; 1067 } else { 1068 resourceString = getContext().resourceManager.getStringSync(resource); 1069 } 1070 return resourceString; 1071 } catch (error) { 1072 let code: number = (error as BusinessError).code; 1073 let message: string = (error as BusinessError).message; 1074 hilog.error(0x3900, 'Ace', `getAccessibilityText error, code: ${code}, message: ${message}`); 1075 return ''; 1076 } 1077} 1078 1079/** 1080 * Obtain accessible level 1081 * 1082 * @param resource 1083 * @param selected select state 1084 * @returns string 1085 */ 1086function getAccessibilityLevelOnChange(accessibilityLevel?: string, onChange?: (value: boolean) => void): string { 1087 if (accessibilityLevel) { 1088 return accessibilityLevel; 1089 } 1090 if (onChange) { 1091 return ACCESSIBILITY_LEVEL_YES; 1092 } 1093 return ACCESSIBILITY_LEVEL_NO; 1094} 1095 1096/** 1097 * Obtain accessible level 1098 * 1099 * @param resource 1100 * @param selected select state 1101 * @returns string 1102 */ 1103function getAccessibilityLevelOnAction(accessibilityLevel?: string, onAction?: () => void): string { 1104 if (accessibilityLevel) { 1105 return accessibilityLevel; 1106 } 1107 if (onAction) { 1108 return ACCESSIBILITY_LEVEL_YES; 1109 } 1110 return ACCESSIBILITY_LEVEL_NO; 1111} 1112 1113@Component 1114export struct ComposeListItem { 1115 @Prop @Watch('onPropChange') contentItem: ContentItem | null = null; 1116 @Prop @Watch('onPropChange') operateItem: OperateItem | null = null; 1117 @State frontColor: ResourceColor = NORMAL_BG_COLOR; 1118 @State borderSize: number = 0; 1119 @State canFocus: boolean = false; 1120 @State canTouch: boolean = true; 1121 @State canHover: boolean = true; 1122 @State isHover: boolean = false; 1123 @State itemHeight: number = ItemHeight.FIRST_HEIGHT; 1124 @State isActive: boolean = false; 1125 @State hoveringColor: ResourceColor = '#0d000000'; 1126 @State touchDownColor: ResourceColor = '#1a000000'; 1127 @State activedColor: ResourceColor = '#1a0a59f7'; 1128 @State focusOutlineColor: ResourceColor = $r('sys.color.ohos_id_color_focused_outline'); 1129 @State @Watch('onFontSizeScaleChange') fontSizeScale: number = 1; 1130 @State containerDirection: FlexDirection = FlexDirection.Row; 1131 @State contentItemDirection: FlexDirection = FlexDirection.Row; 1132 @State containerPadding?: Padding | LocalizedPadding | Length = undefined; 1133 @State textArrowLeftSafeOffset: number = 0; 1134 private isFollowingSystemFontScale = this.getUIContext().isFollowingSystemFontScale(); 1135 private maxFontScale = this.getUIContext().getMaxFontScale(); 1136 private callbackId: number | undefined = undefined; 1137 @State accessibilityTextBuilder: string = ''; 1138 @State isFocus: boolean = false; 1139 @State @Watch('onWrapChange') isWrapText: boolean = false; 1140 @State listScale: ScaleOptions = { x: 1, y: 1 }; 1141 private operateItemStructRef = new OperateItemStructController(); 1142 1143 onWillApplyTheme(theme: Theme): void { 1144 this.hoveringColor = theme.colors.interactiveHover; 1145 this.touchDownColor = theme.colors.interactivePressed; 1146 this.activedColor = theme.colors.interactiveActive; 1147 this.focusOutlineColor = theme.colors.interactiveFocus; 1148 } 1149 1150 onWrapChange(): void { 1151 this.containerPadding = this.getPadding(); 1152 } 1153 1154 onPropChange(): void { 1155 this.containerDirection = this.decideContainerDirection(); 1156 this.contentItemDirection = this.decideContentItemDirection(); 1157 if (this.contentItem === undefined) { 1158 if (this.operateItem?.image !== undefined || 1159 this.operateItem?.symbolStyle !== undefined || 1160 this.operateItem?.icon !== undefined || 1161 this.operateItem?.subIcon !== undefined) { 1162 this.itemHeight = OPERATEITEM_IMAGE_SIZE + SAFE_LIST_PADDING; 1163 } 1164 return; 1165 } 1166 if (this.contentItem?.secondaryText === undefined && this.contentItem?.description === undefined) { 1167 if (this.contentItem?.icon === undefined) { 1168 this.itemHeight = ItemHeight.FIRST_HEIGHT; 1169 } else { 1170 this.itemHeight = this.contentItem.iconStyle as number <= IconType.HEAD_SCULPTURE ? 1171 ItemHeight.SECOND_HEIGHT : 1172 (LengthMetrics.resource($r('sys.float.composeListItem_system_icon_line_height')).value); 1173 } 1174 } else if (this.contentItem.description === undefined) { 1175 let iconStyle = this.contentItem.iconStyle as number; 1176 if (this.contentItem.icon === undefined || 1177 (this.contentItem.icon !== undefined && iconStyle <= IconType.SYSTEM_ICON)) { 1178 this.itemHeight = ItemHeight.THIRD_HEIGHT; 1179 } else { 1180 this.itemHeight = iconStyle === IconType.HEAD_SCULPTURE ? ItemHeight.FOURTH_HEIGHT : APPICON_ITEMLENGTH; 1181 } 1182 } else { 1183 this.itemHeight = ItemHeight.FIFTH_HEIGHT; 1184 } 1185 if (ICON_SIZE_MAP.get(this.contentItem?.iconStyle as number) as number >= this.itemHeight) { 1186 this.itemHeight = ICON_SIZE_MAP.get(this.contentItem?.iconStyle as number) as number + SAFE_LIST_PADDING; 1187 } 1188 1189 if (this.operateItem?.arrow && this.operateItem?.text && this.operateItem?.arrow?.action) { 1190 this.accessibilityTextBuilder = ` 1191 ${getAccessibilityText(this.contentItem?.primaryText ?? '')} 1192 ${getAccessibilityText(this.contentItem?.secondaryText ?? '')} 1193 ${getAccessibilityText(this.contentItem?.description ?? '')} 1194 `; 1195 } else { 1196 this.accessibilityTextBuilder = ` 1197 ${getAccessibilityText(this.contentItem?.primaryText ?? '')} 1198 ${getAccessibilityText(this.contentItem?.secondaryText ?? '')} 1199 ${getAccessibilityText(this.contentItem?.description ?? '')} 1200 ${getAccessibilityText(this.operateItem?.text ?? '')} 1201 `; 1202 } 1203 } 1204 1205 aboutToAppear(): void { 1206 this.fontSizeScale = this.decideFontSizeScale(); 1207 this.onPropChange(); 1208 try { 1209 this.callbackId = getContext()?.getApplicationContext()?.on('environment', this.envCallback); 1210 } catch (paramError) { 1211 let code = (paramError as BusinessError).code; 1212 let message = (paramError as BusinessError).message; 1213 hilog.error(0x3900, 'Ace', 1214 `ComposeListItem Faild to get environment param error: ${code}, ${message}`); 1215 } 1216 if (!IS_SUPPORT_SUBCOMPONENT_EVENT) { 1217 this.onFontSizeScaleChange(); 1218 } 1219 } 1220 1221 private envCallback: EnvironmentCallback = { 1222 onConfigurationUpdated: (config) => { 1223 if (config === undefined || !this.isFollowingSystemFontScale) { 1224 this.fontSizeScale = 1; 1225 return; 1226 } 1227 try { 1228 this.fontSizeScale = Math.min( 1229 this.maxFontScale, config.fontSizeScale ?? 1); 1230 } catch (paramError) { 1231 let code = (paramError as BusinessError).code; 1232 let message = (paramError as BusinessError).message; 1233 hilog.error(0x3900, 'Ace', 1234 `ComposeListItem environmentCallback error: ${code}, ${message}`); 1235 } 1236 }, 1237 onMemoryLevel: (level) => { 1238 } 1239 } 1240 1241 aboutToDisappear(): void { 1242 if (this.callbackId) { 1243 this.getUIContext() 1244 ?.getHostContext() 1245 ?.getApplicationContext() 1246 ?.off('environment', this.callbackId); 1247 this.callbackId = void (0); 1248 } 1249 } 1250 1251 calculatedRightWidth(): string { 1252 if (this.operateItem?.text || this.operateItem?.button) { 1253 return RIGHT_PART_WIDTH; 1254 } 1255 if (this.operateItem?.switch) { 1256 return RIGHT_ONLY_SWITCH_WIDTH; 1257 } else if (this.operateItem?.checkbox) { 1258 return RIGHT_ONLY_CHECKBOX_WIDTH; 1259 } else if (this.operateItem?.radio) { 1260 return RIGHT_ONLY_RADIO_WIDTH; 1261 } else if (this.operateItem?.icon) { 1262 if (this.operateItem?.subIcon) { 1263 return RIGHT_ICON_SUB_ICON_WIDTH; 1264 } 1265 return RIGHT_ONLY_ICON_WIDTH; 1266 } else if (this.operateItem?.symbolStyle) { 1267 return RIGHT_ONLY_IMAGE_WIDTH; 1268 } else if (this.operateItem?.image) { 1269 return RIGHT_ONLY_IMAGE_WIDTH; 1270 } else if (this.operateItem?.arrow) { 1271 return RIGHT_ONLY_ARROW_WIDTH; 1272 } 1273 return RIGHT_CONTENT_NULL_RIGHTWIDTH; 1274 } 1275 1276 decideContentItemDirection(): FlexDirection { 1277 if (this.fontSizeScale >= FontSizeScaleLevel.LEVEL1 && 1278 this.contentItem?.iconStyle && this.contentItem?.iconStyle > IconType.HEAD_SCULPTURE) { 1279 return FlexDirection.Column; 1280 } 1281 return FlexDirection.Row; 1282 } 1283 1284 decideContainerDirection(): FlexDirection { 1285 if (this.fontSizeScale < FontSizeScaleLevel.LEVEL1 || !this.contentItem) { 1286 return FlexDirection.Row; 1287 } 1288 if (this.operateItem?.button) { 1289 return FlexDirection.Column; 1290 } else if (this.operateItem?.symbolStyle) { 1291 return FlexDirection.Row; 1292 } else if (this.operateItem?.image) { 1293 return FlexDirection.Row; 1294 } else if (this.operateItem?.icon && this.operateItem?.text) { 1295 return FlexDirection.Column; 1296 } else if (this.operateItem?.arrow) { 1297 if (!this.operateItem?.text) { 1298 return FlexDirection.Row; 1299 } 1300 this.textArrowLeftSafeOffset = TEXT_SAFE_MARGIN; 1301 return FlexDirection.Column; 1302 } else if (this.operateItem?.text) { 1303 return FlexDirection.Column; 1304 } else { 1305 return FlexDirection.Row; 1306 } 1307 } 1308 1309 onFontSizeScaleChange(): void { 1310 this.containerDirection = this.decideContainerDirection(); 1311 this.contentItemDirection = this.decideContentItemDirection(); 1312 if (this.fontSizeScale >= FontSizeScaleLevel.LEVEL3) { 1313 this.containerPadding = { 1314 top: $r('sys.float.padding_level12'), 1315 bottom: $r('sys.float.padding_level12'), 1316 }; 1317 } else if (this.fontSizeScale >= FontSizeScaleLevel.LEVEL2) { 1318 this.containerPadding = { 1319 top: $r('sys.float.padding_level10'), 1320 bottom: $r('sys.float.padding_level10'), 1321 }; 1322 } else if (this.fontSizeScale >= FontSizeScaleLevel.LEVEL1) { 1323 this.containerPadding = { 1324 top: $r('sys.float.padding_level8'), 1325 bottom: $r('sys.float.padding_level8'), 1326 }; 1327 } else { 1328 this.containerPadding = this.getPadding(); 1329 } 1330 } 1331 1332 isSingleLine(): boolean { 1333 return !this.contentItem?.secondaryText && !this.contentItem?.description; 1334 } 1335 1336 getOperateOffset(): LengthMetrics { 1337 if (this.containerDirection === FlexDirection.Row) { 1338 return LengthMetrics.vp(0); 1339 } 1340 let iconSize = ICON_SIZE_MAP.get(this.contentItem?.iconStyle as number); 1341 if (this.contentItem?.icon && iconSize && iconSize <= HEADSCULPTURE_SIZE) { 1342 return LengthMetrics.vp(iconSize + NORMAL_ITEM_ROW_SPACE + LISTITEM_PADDING - this.textArrowLeftSafeOffset); 1343 } 1344 return LengthMetrics.vp(LISTITEM_PADDING - this.textArrowLeftSafeOffset); 1345 } 1346 1347 getMainSpace(): LengthMetrics { 1348 if (this.containerDirection === FlexDirection.Column) { 1349 return LengthMetrics.resource(this.isSingleLine() ? $r('sys.float.padding_level1') : 1350 $r('sys.float.padding_level8')); 1351 } 1352 return LengthMetrics.vp(0); 1353 } 1354 1355 getFlexOptions(): FlexOptions { 1356 if (this.containerDirection === FlexDirection.Column) { 1357 return { 1358 space: { main: this.getMainSpace() }, 1359 justifyContent: FlexAlign.Center, 1360 alignItems: ItemAlign.Start, 1361 direction: this.containerDirection, 1362 }; 1363 } 1364 return { 1365 justifyContent: FlexAlign.SpaceBetween, 1366 alignItems: ItemAlign.Center, 1367 direction: this.containerDirection, 1368 }; 1369 } 1370 1371 decideFontSizeScale(): number { 1372 if (!this.isFollowingSystemFontScale) { 1373 return 1; 1374 } 1375 return Math.min( 1376 this.maxFontScale, 1377 (this.getUIContext().getHostContext() as common.UIAbilityContext)?.config.fontSizeScale ?? 1 1378 ) 1379 } 1380 1381 getPadding(): Padding | undefined { 1382 if (!IS_SUPPORT_SUBCOMPONENT_EVENT) { 1383 let paddingNum = LengthMetrics.resource(ITEM_PADDING).value; 1384 let compareSize = paddingNum > LISTITEM_PADDING; 1385 let horizontalPadding = compareSize ? paddingNum - LISTITEM_PADDING : 0; 1386 return { 1387 top: this.isWrapText ? paddingNum : 0, 1388 bottom: this.isWrapText ? paddingNum : 0, 1389 left: horizontalPadding, 1390 right: horizontalPadding 1391 }; 1392 } else { 1393 return undefined; 1394 } 1395 } 1396 1397 build() { 1398 Stack() { 1399 Flex(this.getFlexOptions()) { 1400 if (this.contentItem === null) { 1401 ContentItemStruct({ 1402 isWrapText: this.isWrapText 1403 }) 1404 } 1405 if (this.contentItem !== null) { 1406 ContentItemStruct({ 1407 icon: this.contentItem?.icon, 1408 symbolStyle: this.contentItem?.symbolStyle, 1409 iconStyle: this.contentItem?.iconStyle, 1410 primaryText: this.contentItem?.primaryText, 1411 secondaryText: this.contentItem?.secondaryText, 1412 description: this.contentItem?.description, 1413 fontSizeScale: this.fontSizeScale, 1414 parentDirection: this.containerDirection, 1415 itemDirection: this.contentItemDirection, 1416 isFocus: this.isFocus, 1417 itemHeight: this.itemHeight, 1418 isWrapText: this.isWrapText 1419 }); 1420 } 1421 if (this.operateItem !== null) { 1422 OperateItemStruct({ 1423 icon: this.operateItem?.icon, 1424 subIcon: this.operateItem?.subIcon, 1425 button: this.operateItem?.button, 1426 switch: this.operateItem?.switch, 1427 checkBox: this.operateItem?.checkbox, 1428 radio: this.operateItem?.radio, 1429 image: this.operateItem?.image, 1430 symbolStyle: this.operateItem?.symbolStyle, 1431 text: this.operateItem?.text, 1432 arrow: this.operateItem?.arrow, 1433 parentCanFocus: this.canFocus, 1434 parentCanTouch: this.canTouch, 1435 parentIsHover: this.isHover, 1436 parentFrontColor: this.frontColor, 1437 parentIsActive: this.isActive, 1438 parentCanHover: this.canHover, 1439 rightWidth: this.calculatedRightWidth(), 1440 parentDirection: this.containerDirection, 1441 isFocus: this.isFocus, 1442 controller: this.operateItemStructRef 1443 }) 1444 .flexShrink(0) 1445 .onFocus(() => { 1446 this.canFocus = false; 1447 }) 1448 .onBlur(() => { 1449 this.canFocus = true; 1450 }).padding({ start: this.getOperateOffset() }); 1451 } 1452 } 1453 .height(this.containerDirection === FlexDirection.Column ? 'auto' : undefined) 1454 .constraintSize({ 1455 minHeight: this.itemHeight 1456 }) 1457 .focusable(IS_SUPPORT_SUBCOMPONENT_EVENT) 1458 .borderRadius($r('sys.float.composeListItem_radius')) 1459 .backgroundColor(this.frontColor) 1460 .onFocus(() => { 1461 this.canFocus = true; 1462 }) 1463 .onBlur(() => { 1464 this.canFocus = false; 1465 }) 1466 .onHover((isHover: boolean) => { 1467 if (this.isFocus && !IS_SUPPORT_SUBCOMPONENT_EVENT) { 1468 this.isHover = false; 1469 return; 1470 } 1471 this.isHover = isHover; 1472 if (this.canHover) { 1473 this.frontColor = isHover ? this.hoveringColor : 1474 (this.isActive ? this.activedColor : Color.Transparent.toString()); 1475 } 1476 if (!IS_SUPPORT_SUBCOMPONENT_EVENT) { 1477 this.frontColor = isHover ? FOCUSED_BG_COLOR : NORMAL_BG_COLOR; 1478 isHover ? this.zoomIn() : this.zoomOut(); 1479 } 1480 }) 1481 .stateStyles({ 1482 focused: { 1483 .border({ 1484 radius: $r('sys.float.composeListItem_radius'), 1485 width: ITEM_BORDER_SHOWN, 1486 color: this.focusOutlineColor, 1487 style: BorderStyle.Solid 1488 }) 1489 }, 1490 normal: { 1491 .border({ 1492 radius: $r('sys.float.composeListItem_radius'), 1493 color: $r('sys.color.composeListItem_stroke_normal_color'), 1494 width: $r('sys.float.composeListItem_stroke_normal_thickness'), 1495 }) 1496 }, 1497 pressed: { 1498 .backgroundColor(this.touchDownColor) 1499 } 1500 }) 1501 .padding(this.containerPadding) 1502 } 1503 .width('100%') 1504 .accessibilityGroup(true) 1505 .accessibilityText(this.accessibilityTextBuilder) 1506 .onFocus(() => { 1507 this.isFocus = true; 1508 this.frontColor = FOCUSED_BG_COLOR; 1509 this.zoomIn(); 1510 }) 1511 .onBlur(() => { 1512 this.isFocus = false; 1513 this.frontColor = NORMAL_BG_COLOR; 1514 this.zoomOut(); 1515 }) 1516 .borderRadius(IS_SUPPORT_SUBCOMPONENT_EVENT ? undefined : $r('sys.float.composeListItem_radius')) 1517 .onClick(IS_SUPPORT_SUBCOMPONENT_EVENT ? undefined : () => { 1518 if (this.operateItem?.icon && this.operateItem.icon?.action) { 1519 this.operateItem.icon.action(); 1520 } 1521 if (this.operateItem?.subIcon && this.operateItem.subIcon?.action) { 1522 this.operateItem.subIcon.action(); 1523 } 1524 if (this.operateItem?.arrow && this.operateItem.arrow?.action) { 1525 this.operateItem.arrow.action(); 1526 } 1527 if (this.operateItem?.radio) { 1528 this.operateItemStructRef.changeRadioState(); 1529 } 1530 if (this.operateItem?.checkbox) { 1531 this.operateItemStructRef.changeCheckboxState(); 1532 } 1533 if (this.operateItem?.switch) { 1534 this.operateItemStructRef.changeSwitchState(); 1535 } 1536 }) 1537 .scale(this.listScale) 1538 .shadow(IS_SUPPORT_SUBCOMPONENT_EVENT ? undefined : (this.isFocus ? FOCUSED_SHADOW : NORMAL_SHADOW)) 1539 .margin({ 1540 left: !IS_SUPPORT_SUBCOMPONENT_EVENT ? STACK_PADDING : undefined, 1541 right: !IS_SUPPORT_SUBCOMPONENT_EVENT ? STACK_PADDING : undefined 1542 }) 1543 .padding({ 1544 left: IS_SUPPORT_SUBCOMPONENT_EVENT ? STACK_PADDING : 0, 1545 right: IS_SUPPORT_SUBCOMPONENT_EVENT ? STACK_PADDING : 0 1546 }) 1547 } 1548 1549 private zoomIn(): void { 1550 this.listScale = { 1551 x: IS_SUPPORT_SUBCOMPONENT_EVENT ? undefined : FOCUSED_ITEM_SCALE, 1552 y: IS_SUPPORT_SUBCOMPONENT_EVENT ? undefined : FOCUSED_ITEM_SCALE 1553 }; 1554 } 1555 1556 private zoomOut(): void { 1557 this.listScale = { 1558 x: IS_SUPPORT_SUBCOMPONENT_EVENT ? undefined : RECOVER_ITEM_SCALE, 1559 y: IS_SUPPORT_SUBCOMPONENT_EVENT ? undefined : RECOVER_ITEM_SCALE 1560 }; 1561 } 1562}