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