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 from '@ohos.app.ability.common'; 19 20export enum IconType { 21 BADGE = 1, 22 NORMAL_ICON, 23 SYSTEM_ICON, 24 HEAD_SCULPTURE, 25 APP_ICON, 26 PREVIEW, 27 LONGITUDINAL, 28 VERTICAL 29} 30 31enum FontSizeScaleLevel { 32 LEVEL1 = 1.75, 33 LEVEL2 = 2, 34 LEVEL3 = 3.2 35} 36 37enum ItemHeight { 38 FIRST_HEIGHT = 48, 39 SECOND_HEIGHT = 56, 40 THIRD_HEIGHT = 64, 41 FOURTH_HEIGHT = 72, 42 FIFTH_HEIGHT = 96 43} 44 45export declare class OperateItem { 46 public icon?: OperateIcon; 47 public subIcon?: OperateIcon; 48 public button?: OperateButton; 49 public switch?: OperateCheck; 50 public checkbox?: OperateCheck; 51 public radio?: OperateCheck; 52 public image?: ResourceStr; 53 public text?: ResourceStr; 54 public arrow?: OperateIcon; 55} 56 57export declare class ContentItem { 58 public iconStyle?: IconType; 59 public icon?: ResourceStr; 60 public primaryText?: ResourceStr; 61 public secondaryText?: ResourceStr; 62 public description?: ResourceStr; 63} 64 65export declare class OperateIcon { 66 public value: ResourceStr; 67 public action?: () => void; 68} 69 70export declare class OperateButton { 71 public text?: ResourceStr; 72} 73 74export declare class OperateCheck { 75 public isCheck?: boolean; 76 public onChange?: (value: boolean) => void; 77} 78 79const TEXT_MAX_LINE = 1; 80const ITEM_BORDER_SHOWN = 2; 81const TEXT_COLUMN_SPACE = 4; 82const TEXT_SAFE_MARGIN = 8; 83const LISTITEM_PADDING = 6; 84const SWITCH_PADDING = 4; 85const STACK_PADDING = 4; 86const BADGE_SIZE = 8; 87const SMALL_ICON_SIZE = 16; 88const SYSTEM_ICON_SIZE = 24; 89const TEXT_ARROW_HEIGHT = 32; 90const SAFE_LIST_PADDING = 32; 91const HEADSCULPTURE_SIZE = 40; 92const BUTTON_SIZE = 28; 93const APP_ICON_SIZE = 64; 94const PREVIEW_SIZE = 96; 95const LONGITUDINAL_SIZE = 96; 96const VERTICAL_SIZE = 96; 97const NORMAL_ITEM_ROW_SPACE = 16; 98const SPECIAL_ITEM_ROW_SPACE = 0; 99const SPECIAL_ICON_SIZE = 0; 100const DEFAULT_ROW_SPACE = 0; 101const SPECICAL_ROW_SPACE = 4; 102const OPERATEITEM_ICONLIKE_SIZE = 24; 103const OPERATEITEM_SELECTIONBOX_PADDING_SIZE = 2; 104const OPERATEITEM_ARROW_WIDTH = 12 105const OPERATEITEM_ICON_CLICKABLE_SIZE = 40; 106const OPERATEITEM_IMAGE_SIZE = 48; 107const RIGHT_CONTENT_NULL_RIGHTWIDTH = '0vp'; 108const LEFT_PART_WIDTH = 'calc(66% - 16vp)'; 109const RIGHT_PART_WIDTH = '34%'; 110const RIGHT_ONLY_ARROW_WIDTH = '24vp'; 111const RIGHT_ONLY_IMAGE_WIDTH = '54vp'; 112const RIGHT_ONLY_ICON_WIDTH = '40vp'; 113const RIGHT_ICON_SUB_ICON_WIDTH = '80vp'; 114const RIGHT_ONLY_RADIO_WIDTH = '30vp'; 115const RIGHT_ONLY_CHECKBOX_WIDTH = '30vp'; 116const RIGHT_ONLY_SWITCH_WIDTH = '44vp'; 117 118const ICON_SIZE_MAP: Map<number, number> = new Map([ 119 [IconType.BADGE, BADGE_SIZE], 120 [IconType.NORMAL_ICON, SMALL_ICON_SIZE], 121 [IconType.SYSTEM_ICON, SYSTEM_ICON_SIZE], 122 [IconType.HEAD_SCULPTURE, HEADSCULPTURE_SIZE], 123 [IconType.APP_ICON, APP_ICON_SIZE], 124 [IconType.PREVIEW, PREVIEW_SIZE], 125 [IconType.LONGITUDINAL, LONGITUDINAL_SIZE], 126 [IconType.VERTICAL, VERTICAL_SIZE] 127]) 128 129@Component 130struct ContentItemStruct { 131 @Prop @Watch('onPropChange') iconStyle: IconType | null = null; 132 @Prop @Watch('onPropChange') icon: ResourceStr | null = null; 133 @Prop @Watch('onPropChange') primaryText: ResourceStr | null = null; 134 @Prop @Watch('onPropChange') secondaryText: ResourceStr | null = null; 135 @Prop @Watch('onPropChange') description: ResourceStr | null = null; 136 @State itemRowSpace: number = NORMAL_ITEM_ROW_SPACE; 137 @Prop leftWidth: string = LEFT_PART_WIDTH; 138 @State primaryTextColor: ResourceColor = $r('sys.color.ohos_id_color_text_primary'); 139 @State secondaryTextColor: ResourceColor = $r('sys.color.ohos_id_color_text_secondary'); 140 @State descriptionColor: ResourceColor = $r('sys.color.ohos_id_color_text_secondary'); 141 @Prop fontSizeScale: number; 142 @Prop parentDirection: FlexDirection; 143 @Prop itemDirection: FlexDirection; 144 145 onWillApplyTheme(theme: Theme): void { 146 this.primaryTextColor = theme.colors.fontPrimary; 147 this.secondaryTextColor = theme.colors.fontSecondary; 148 this.descriptionColor = theme.colors.fontTertiary; 149 } 150 151 onPropChange() { 152 if (this.icon == null && this.iconStyle == null) { 153 this.itemRowSpace = SPECIAL_ITEM_ROW_SPACE; 154 } else { 155 this.itemRowSpace = NORMAL_ITEM_ROW_SPACE; 156 } 157 } 158 159 aboutToAppear() { 160 this.onPropChange(); 161 } 162 163 @Builder 164 createIcon() { 165 if (this.icon != null && this.iconStyle != null && ICON_SIZE_MAP.has(this.iconStyle)) { 166 if (this.iconStyle <= IconType.PREVIEW) { 167 Image(this.icon) 168 .objectFit(ImageFit.Contain) 169 .width(ICON_SIZE_MAP.get(this.iconStyle)) 170 .height(ICON_SIZE_MAP.get(this.iconStyle)) 171 .borderRadius($r('sys.float.ohos_id_corner_radius_default_m')) 172 .focusable(false) 173 .draggable(false) 174 .fillColor($r('sys.color.ohos_id_color_secondary')) 175 .flexShrink(0) 176 } else { 177 Image(this.icon) 178 .objectFit(ImageFit.Contain) 179 .constraintSize({ 180 minWidth: SPECIAL_ICON_SIZE, 181 maxWidth: ICON_SIZE_MAP.get(this.iconStyle), 182 minHeight: SPECIAL_ICON_SIZE, 183 maxHeight: ICON_SIZE_MAP.get(this.iconStyle) 184 }) 185 .borderRadius($r('sys.float.ohos_id_corner_radius_default_m')) 186 .focusable(false) 187 .draggable(false) 188 .fillColor($r('sys.color.ohos_id_color_secondary')) 189 .flexShrink(0) 190 } 191 } 192 } 193 194 @Builder 195 createText() { 196 Column({ space: TEXT_COLUMN_SPACE }) { 197 Text(this.primaryText) 198 .fontSize($r('sys.float.ohos_id_text_size_body1')) 199 .fontColor(this.primaryTextColor) 200 .textOverflow({ overflow: TextOverflow.Ellipsis }) 201 .fontWeight(FontWeight.Medium) 202 .focusable(true) 203 .draggable(false) 204 if (this.secondaryText != null) { 205 Text(this.secondaryText) 206 .fontSize($r('sys.float.ohos_id_text_size_body2')) 207 .fontColor(this.secondaryTextColor) 208 .textOverflow({ overflow: TextOverflow.Ellipsis }) 209 .draggable(false) 210 } 211 if (this.description != null) { 212 Text(this.description) 213 .fontSize($r('sys.float.ohos_id_text_size_body2')) 214 .fontColor(this.descriptionColor) 215 .textOverflow({ overflow: TextOverflow.Ellipsis }) 216 .draggable(false) 217 } 218 } 219 .flexShrink(1) 220 .margin(this.fontSizeScale >= FontSizeScaleLevel.LEVEL1 ? undefined : { 221 top: TEXT_SAFE_MARGIN, 222 bottom: TEXT_SAFE_MARGIN 223 }) 224 .alignItems(HorizontalAlign.Start) 225 } 226 227 isColumnDirection() { 228 return this.itemDirection === FlexDirection.Column; 229 } 230 231 isParentColumnDirection() { 232 return this.parentDirection === FlexDirection.Column; 233 } 234 235 getItemSpace() { 236 if (this.isColumnDirection()) { 237 return LengthMetrics.resource($r('sys.float.padding_level1')); 238 } 239 return LengthMetrics.vp(this.itemRowSpace); 240 } 241 242 build() { 243 Flex({ 244 space: { main: this.getItemSpace() }, 245 direction: this.itemDirection, 246 justifyContent: FlexAlign.Start, 247 alignItems: this.isColumnDirection() ? ItemAlign.Start : ItemAlign.Center, 248 }) { 249 this.createIcon() 250 this.createText() 251 } 252 .margin({ 253 end: this.isParentColumnDirection() ? 254 LengthMetrics.vp(0) : 255 LengthMetrics.vp(16) 256 }) 257 .padding({ start: LengthMetrics.vp(LISTITEM_PADDING) }) 258 .flexShrink(this.isParentColumnDirection() ? 0 : 1) 259 } 260} 261 262class CreateIconParam { 263 public icon?: OperateIcon; 264} 265 266@Component 267struct OperateItemStruct { 268 @Prop @Watch('onPropChange') arrow: OperateIcon | null = null; 269 @Prop @Watch('onPropChange') icon: OperateIcon | null = null; 270 @Prop @Watch('onPropChange') subIcon: OperateIcon | null = null; 271 @Prop @Watch('onPropChange') button: OperateButton | null = null; 272 @Prop @Watch('onPropChange') switch: OperateCheck | null = null; 273 @Prop @Watch('onPropChange') checkBox: OperateCheck | null = null; 274 @Prop @Watch('onPropChange') radio: OperateCheck | null = null; 275 @Prop @Watch('onPropChange') image: ResourceStr | null = null; 276 @Prop @Watch('onPropChange') text: ResourceStr | null = null; 277 @State switchState: boolean = false 278 @State radioState: boolean = false 279 @State checkBoxState: boolean = false 280 @Prop rightWidth: string = RIGHT_PART_WIDTH; 281 @State secondaryTextColor: ResourceColor = $r('sys.color.ohos_id_color_text_secondary'); 282 @State hoveringColor: ResourceColor = '#0d000000'; 283 @State activedColor: ResourceColor = '#1a0a59f7'; 284 @Link parentCanFocus: boolean 285 @Link parentCanTouch: boolean 286 @Link parentIsHover: boolean 287 @Link parentCanHover: boolean 288 @Link parentIsActive: boolean 289 @Link parentFrontColor: ResourceColor; 290 @Link parentDirection: FlexDirection; 291 @State rowSpace: number = DEFAULT_ROW_SPACE; 292 293 onWillApplyTheme(theme: Theme): void { 294 this.secondaryTextColor = theme.colors.fontSecondary; 295 this.hoveringColor = theme.colors.interactiveHover; 296 this.activedColor = theme.colors.interactiveActive; 297 } 298 299 onPropChange() { 300 if (this.switch != null) { 301 this.switchState = this.switch.isCheck as boolean; 302 } 303 if (this.radio != null) { 304 this.radioState = this.radio.isCheck as boolean; 305 } 306 if (this.checkBox != null) { 307 this.checkBoxState = this.checkBox.isCheck as boolean; 308 } 309 310 if ((this.button == null && this.image == null && this.icon != null && this.text != null) || 311 (this.button == null && this.image == null && this.icon == null && this.arrow != null && this.text != null)) { 312 this.rowSpace = SPECICAL_ROW_SPACE; 313 } else { 314 this.rowSpace = DEFAULT_ROW_SPACE; 315 } 316 } 317 318 aboutToAppear() { 319 this.onPropChange(); 320 } 321 322 @Builder 323 createButton() { 324 Button() { 325 Row() { 326 Text(this.button?.text as ResourceStr) 327 .focusable(true) 328 } 329 .padding({ 330 left: TEXT_SAFE_MARGIN, 331 right: TEXT_SAFE_MARGIN 332 }) 333 } 334 .padding({ top: 0, bottom: 0 }) 335 .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) }) 336 .hitTestBehavior(HitTestMode.Block) 337 .fontSize($r('sys.float.ohos_id_text_size_button3')) 338 .fontColor($r('sys.color.ohos_id_color_text_primary_activated_transparent')) 339 .constraintSize({ 340 minHeight: BUTTON_SIZE 341 }) 342 .backgroundColor($r('sys.color.ohos_id_color_button_normal')) 343 .labelStyle({ 344 maxLines: TEXT_MAX_LINE 345 }) 346 .onFocus(() => { 347 this.parentCanFocus = false; 348 }) 349 .onHover((isHover: boolean) => { 350 this.parentCanHover = false 351 if (isHover && this.parentFrontColor === this.hoveringColor) { 352 this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString(); 353 } 354 if (!isHover) { 355 this.parentCanHover = true 356 if (this.parentIsHover) { 357 this.parentFrontColor = this.parentIsHover ? this.hoveringColor : 358 (this.parentIsActive ? this.activedColor : Color.Transparent.toString()); 359 } 360 } 361 }) 362 } 363 364 @Builder 365 createIcon(param: CreateIconParam) { 366 Button({ type: ButtonType.Normal }) { 367 Image(param.icon?.value) 368 .height(OPERATEITEM_ICONLIKE_SIZE) 369 .width(OPERATEITEM_ICONLIKE_SIZE) 370 .focusable(true) 371 .fillColor($r('sys.color.ohos_id_color_primary')) 372 .draggable(false) 373 } 374 .hitTestBehavior(HitTestMode.Block) 375 .backgroundColor(Color.Transparent) 376 .height(OPERATEITEM_ICON_CLICKABLE_SIZE) 377 .width(OPERATEITEM_ICON_CLICKABLE_SIZE) 378 .borderRadius($r('sys.float.ohos_id_corner_radius_clicked')) 379 .onFocus(() => { 380 this.parentCanFocus = false; 381 }) 382 .onHover((isHover: boolean) => { 383 this.parentCanHover = false 384 if (isHover && this.parentFrontColor === this.hoveringColor) { 385 this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString(); 386 } 387 if (!isHover) { 388 this.parentCanHover = true 389 if (this.parentIsHover) { 390 this.parentFrontColor = this.parentIsHover ? this.hoveringColor : 391 (this.parentIsActive ? this.activedColor : Color.Transparent.toString()); 392 } 393 } 394 }) 395 .onClick(param.icon?.action) 396 .flexShrink(0) 397 } 398 399 @Builder 400 createImage() { 401 Image(this.image) 402 .height(OPERATEITEM_IMAGE_SIZE) 403 .width(OPERATEITEM_IMAGE_SIZE) 404 .draggable(false) 405 .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) }) 406 } 407 408 @Builder 409 createText() { 410 Text(this.text) 411 .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) }) 412 .fontSize($r('sys.float.ohos_id_text_size_body2')) 413 .fontColor(this.secondaryTextColor) 414 .draggable(false) 415 .flexShrink(1) 416 } 417 418 @Builder 419 createArrow() { 420 Button({ type: ButtonType.Normal }) { 421 Image(this.arrow?.value) 422 .height(OPERATEITEM_ICONLIKE_SIZE) 423 .width(OPERATEITEM_ARROW_WIDTH) 424 .focusable(true) 425 .fillColor($r('sys.color.ohos_id_color_fourth')) 426 .draggable(false) 427 .matchTextDirection(true) 428 } 429 .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) }) 430 .hitTestBehavior(this.arrow?.action !== undefined ? HitTestMode.Block : HitTestMode.Transparent) 431 .backgroundColor(Color.Transparent) 432 .height(OPERATEITEM_ICONLIKE_SIZE) 433 .width(OPERATEITEM_ARROW_WIDTH) 434 .onFocus(() => { 435 this.parentCanFocus = false; 436 }) 437 .stateEffect(this.arrow?.action !== undefined) 438 .hoverEffect(this.arrow?.action !== undefined ? HoverEffect.Auto : HoverEffect.None) 439 .onHover((isHover: boolean) => { 440 if (this.arrow?.action === undefined) { 441 return; 442 } 443 this.parentCanHover = false 444 if (isHover) { 445 this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString(); 446 } 447 if (!isHover) { 448 this.parentCanHover = true 449 if (this.parentIsHover) { 450 this.parentFrontColor = this.parentIsHover ? this.hoveringColor : 451 (this.parentIsActive ? this.activedColor : Color.Transparent.toString()); 452 } 453 } 454 }) 455 .onClick(this.arrow?.action) 456 } 457 458 @Builder 459 createRadio() { 460 Radio({ value: '', group: '' }) 461 .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) }) 462 .checked(this.radioState) 463 .onChange(this.radio?.onChange) 464 .height(OPERATEITEM_ICONLIKE_SIZE) 465 .width(OPERATEITEM_ICONLIKE_SIZE) 466 .padding(OPERATEITEM_SELECTIONBOX_PADDING_SIZE) 467 .onFocus(() => { 468 this.parentCanFocus = false; 469 }) 470 .hitTestBehavior(HitTestMode.Block) 471 .flexShrink(0) 472 .onHover((isHover: boolean) => { 473 this.parentCanHover = false 474 if (isHover && this.parentFrontColor === this.hoveringColor) { 475 this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString(); 476 } 477 if (!isHover) { 478 this.parentCanHover = true 479 if (this.parentIsHover) { 480 this.parentFrontColor = this.parentIsHover ? this.hoveringColor : 481 (this.parentIsActive ? this.activedColor : Color.Transparent.toString()); 482 } 483 } 484 }) 485 } 486 487 @Builder 488 createCheckBox() { 489 Checkbox() 490 .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) }) 491 .select(this.checkBoxState) 492 .onChange(this.checkBox?.onChange) 493 .height(OPERATEITEM_ICONLIKE_SIZE) 494 .width(OPERATEITEM_ICONLIKE_SIZE) 495 .padding(OPERATEITEM_SELECTIONBOX_PADDING_SIZE) 496 .onFocus(() => { 497 this.parentCanFocus = false; 498 }) 499 .hitTestBehavior(HitTestMode.Block) 500 .onHover((isHover: boolean) => { 501 this.parentCanHover = false 502 if (isHover && this.parentFrontColor === this.hoveringColor) { 503 this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString(); 504 } 505 if (!isHover) { 506 this.parentCanHover = true 507 if (this.parentIsHover) { 508 this.parentFrontColor = this.parentIsHover ? this.hoveringColor : 509 (this.parentIsActive ? this.activedColor : Color.Transparent.toString()); 510 } 511 } 512 }) 513 } 514 515 @Builder 516 createSwitch() { 517 Row() { 518 Toggle({ type: ToggleType.Switch, isOn: this.switchState }) 519 .onChange(this.switch?.onChange) 520 .onClick(() => { 521 this.switchState = !this.switchState 522 }) 523 .hitTestBehavior(HitTestMode.Block) 524 } 525 .margin({ end: LengthMetrics.vp(SWITCH_PADDING) }) 526 .height(OPERATEITEM_ICON_CLICKABLE_SIZE) 527 .width(OPERATEITEM_ICON_CLICKABLE_SIZE) 528 .justifyContent(FlexAlign.Center) 529 .onFocus(() => { 530 this.parentCanFocus = false; 531 }) 532 .onHover((isHover: boolean) => { 533 this.parentCanHover = false 534 if (isHover && this.parentFrontColor === this.hoveringColor) { 535 this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString(); 536 } 537 if (!isHover) { 538 this.parentCanHover = true 539 if (this.parentIsHover) { 540 this.parentFrontColor = this.parentIsHover ? this.hoveringColor : 541 (this.parentIsActive ? this.activedColor : Color.Transparent.toString()); 542 } 543 } 544 }) 545 } 546 547 @Builder 548 createTextArrow() { 549 Button({ type: ButtonType.Normal }) { 550 if (this.parentDirection === FlexDirection.Column) { 551 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { 552 Text(this.text) 553 .fontSize($r('sys.float.ohos_id_text_size_body2')) 554 .fontColor(this.secondaryTextColor) 555 .focusable(true) 556 .draggable(false) 557 .constraintSize({ 558 maxWidth: `calc(100% - ${OPERATEITEM_ARROW_WIDTH}vp)` 559 }) 560 Image(this.arrow?.value) 561 .height(OPERATEITEM_ICONLIKE_SIZE) 562 .width(OPERATEITEM_ARROW_WIDTH) 563 .fillColor($r('sys.color.ohos_id_color_fourth')) 564 .focusable(false) 565 .draggable(false) 566 .matchTextDirection(true) 567 } 568 .padding({ 569 start: LengthMetrics.vp(TEXT_SAFE_MARGIN), 570 end: LengthMetrics.vp(LISTITEM_PADDING) 571 }) 572 } else { 573 Row({ space: SPECICAL_ROW_SPACE }) { 574 Text(this.text) 575 .fontSize($r('sys.float.ohos_id_text_size_body2')) 576 .fontColor(this.secondaryTextColor) 577 .focusable(true) 578 .draggable(false) 579 .constraintSize({ 580 maxWidth: `calc(100% - ${OPERATEITEM_ARROW_WIDTH}vp)` 581 }) 582 Image(this.arrow?.value) 583 .height(OPERATEITEM_ICONLIKE_SIZE) 584 .width(OPERATEITEM_ARROW_WIDTH) 585 .fillColor($r('sys.color.ohos_id_color_fourth')) 586 .focusable(false) 587 .draggable(false) 588 .matchTextDirection(true) 589 } 590 .padding({ 591 start: LengthMetrics.vp(TEXT_SAFE_MARGIN), 592 end: LengthMetrics.vp(LISTITEM_PADDING) 593 }) 594 } 595 } 596 .hitTestBehavior(this.arrow?.action !== undefined ? HitTestMode.Block : HitTestMode.Transparent) 597 .labelStyle({ 598 maxLines: TEXT_MAX_LINE 599 }) 600 .backgroundColor(Color.Transparent) 601 .constraintSize({ minHeight: TEXT_ARROW_HEIGHT }) 602 .borderRadius($r('sys.float.ohos_id_corner_radius_clicked')) 603 .onFocus(() => { 604 this.parentCanFocus = false; 605 }) 606 .padding({ 607 top: 0, 608 bottom: 0, 609 left: 0, 610 right: 0 611 }) 612 .stateEffect(this.arrow?.action !== undefined) 613 .hoverEffect(this.arrow?.action !== undefined ? HoverEffect.Auto : HoverEffect.None) 614 .onHover((isHover: boolean) => { 615 if (this.arrow?.action === undefined) { 616 return; 617 } 618 this.parentCanHover = false 619 if (isHover) { 620 this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString(); 621 } 622 if (!isHover) { 623 this.parentCanHover = true 624 if (this.parentIsHover) { 625 this.parentFrontColor = this.parentIsHover ? this.hoveringColor : 626 (this.parentIsActive ? this.activedColor : Color.Transparent.toString()); 627 } 628 } 629 }) 630 .onClick(this.arrow?.action) 631 } 632 633 getFlexOptions(): FlexOptions { 634 let flexOptions: FlexOptions = { alignItems: ItemAlign.Center }; 635 if (this.parentDirection === FlexDirection.Column) { 636 flexOptions.justifyContent = FlexAlign.SpaceBetween; 637 } else { 638 flexOptions.space = { main: LengthMetrics.vp(this.rowSpace) }; 639 flexOptions.justifyContent = FlexAlign.End; 640 } 641 return flexOptions; 642 } 643 644 build() { 645 Flex(this.getFlexOptions()) { 646 if (this.button != null) { 647 this.createButton(); 648 } else if (this.image != null) { 649 this.createImage() 650 } else if (this.icon != null && this.text != null) { 651 this.createText() 652 this.createIcon({ icon: this.icon }) 653 } else if (this.arrow != null && this.text == null) { 654 this.createArrow() 655 } else if (this.arrow != null && this.text != null) { 656 this.createTextArrow(); 657 } else if (this.text != null) { 658 this.createText() 659 } else if (this.radio != null) { 660 this.createRadio() 661 } else if (this.checkBox != null) { 662 this.createCheckBox() 663 } else if (this.switch != null) { 664 this.createSwitch() 665 } else if (this.icon != null) { 666 this.createIcon({ icon: this.icon }) 667 if (this.subIcon != null) { 668 this.createIcon({ icon: this.subIcon }); 669 } 670 } 671 } 672 .width(this.parentDirection === FlexDirection.Column ? undefined : this.rightWidth) 673 } 674} 675 676@Component 677export struct ComposeListItem { 678 @Prop @Watch('onPropChange') contentItem: ContentItem | null = null; 679 @Prop @Watch('onPropChange') operateItem: OperateItem | null = null; 680 @State frontColor: ResourceColor = Color.Transparent.toString(); 681 @State borderSize: number = 0; 682 @State canFocus: boolean = false 683 @State canTouch: boolean = true 684 @State canHover: boolean = true 685 @State isHover: boolean = true 686 @State itemHeight: number = ItemHeight.FIRST_HEIGHT; 687 @State isActive: boolean = false 688 @State hoveringColor: ResourceColor = '#0d000000'; 689 @State touchDownColor: ResourceColor = '#1a000000'; 690 @State activedColor: ResourceColor = '#1a0a59f7'; 691 @State focusOutlineColor: ResourceColor = $r('sys.color.ohos_id_color_focused_outline'); 692 @State @Watch('onFontSizeScaleChange') fontSizeScale: number = 1; 693 @State containerDirection: FlexDirection = FlexDirection.Row; 694 @State contentItemDirection: FlexDirection = FlexDirection.Row; 695 @State containerPadding?: Padding = undefined; 696 @State textArrowLeftSafeOffset: number = 0; 697 private isFollowingSystemFontScale = this.getUIContext().isFollowingSystemFontScale(); 698 private maxFontScale = this.getUIContext().getMaxFontScale(); 699 700 onWillApplyTheme(theme: Theme): void { 701 this.hoveringColor = theme.colors.interactiveHover; 702 this.touchDownColor = theme.colors.interactivePressed; 703 this.activedColor = theme.colors.interactiveActive; 704 this.focusOutlineColor = theme.colors.interactiveFocus; 705 } 706 707 onPropChange() { 708 if (this.contentItem === undefined) { 709 if (this.operateItem?.image !== undefined || 710 this.operateItem?.icon !== undefined || 711 this.operateItem?.subIcon !== undefined) { 712 this.itemHeight = OPERATEITEM_IMAGE_SIZE + SAFE_LIST_PADDING 713 } 714 return 715 } 716 717 if (this.contentItem?.secondaryText === undefined && this.contentItem?.description === undefined) { 718 if (this.contentItem?.icon === undefined) { 719 this.itemHeight = ItemHeight.FIRST_HEIGHT 720 } else { 721 this.itemHeight = this.contentItem.iconStyle as number <= IconType.HEAD_SCULPTURE ? 722 ItemHeight.SECOND_HEIGHT : ItemHeight.THIRD_HEIGHT; 723 } 724 } else if (this.contentItem.description === undefined) { 725 if (this.contentItem.icon === undefined || 726 (this.contentItem.icon !== undefined && this.contentItem.iconStyle as number <= IconType.SYSTEM_ICON)) { 727 this.itemHeight = ItemHeight.THIRD_HEIGHT 728 } else { 729 this.itemHeight = ItemHeight.FOURTH_HEIGHT 730 } 731 } else { 732 this.itemHeight = ItemHeight.FIFTH_HEIGHT 733 } 734 735 if (ICON_SIZE_MAP.get(this.contentItem?.iconStyle as number) as number >= this.itemHeight) { 736 this.itemHeight = ICON_SIZE_MAP.get(this.contentItem?.iconStyle as number) as number + SAFE_LIST_PADDING; 737 } 738 739 } 740 741 aboutToAppear() { 742 this.onPropChange(); 743 } 744 745 calculatedRightWidth(): string { 746 if (this.operateItem === null || JSON.stringify(this.operateItem) === '{}') { 747 return RIGHT_CONTENT_NULL_RIGHTWIDTH; 748 } else if (this.operateItem?.switch != null && this.operateItem?.text == null) { 749 return RIGHT_ONLY_SWITCH_WIDTH; 750 } else if (this.operateItem?.checkbox != null && this.operateItem?.text == null) { 751 return RIGHT_ONLY_CHECKBOX_WIDTH; 752 } else if (this.operateItem?.radio != null && this.operateItem?.text == null) { 753 return RIGHT_ONLY_RADIO_WIDTH; 754 } else if (this.operateItem?.icon != null && this.operateItem?.text == null) { 755 if (this.operateItem?.subIcon != null) { 756 return RIGHT_ICON_SUB_ICON_WIDTH; 757 } 758 return RIGHT_ONLY_ICON_WIDTH; 759 } else if (this.operateItem?.image != null && this.operateItem?.text == null) { 760 return RIGHT_ONLY_IMAGE_WIDTH; 761 } else if (this.operateItem?.arrow != null && this.operateItem?.text == null) { 762 return RIGHT_ONLY_ARROW_WIDTH; 763 } else { 764 return RIGHT_PART_WIDTH; 765 } 766 } 767 768 decideContentItemDirection(): FlexDirection { 769 if (this.fontSizeScale >= FontSizeScaleLevel.LEVEL1 && 770 this.contentItem?.iconStyle && this.contentItem?.iconStyle > IconType.HEAD_SCULPTURE) { 771 return FlexDirection.Column; 772 } 773 return FlexDirection.Row; 774 } 775 776 decideContainerDirection(): FlexDirection { 777 if (this.fontSizeScale < FontSizeScaleLevel.LEVEL1 || !this.contentItem) { 778 return FlexDirection.Row; 779 } 780 if (this.operateItem?.button) { 781 return FlexDirection.Column; 782 } else if (this.operateItem?.image) { 783 return FlexDirection.Row; 784 } else if (this.operateItem?.icon && this.operateItem?.text) { 785 return FlexDirection.Column; 786 } else if (this.operateItem?.arrow) { 787 if (!this.operateItem?.text) { 788 return FlexDirection.Row; 789 } 790 this.textArrowLeftSafeOffset = TEXT_SAFE_MARGIN; 791 return FlexDirection.Column; 792 } else if (this.operateItem?.text) { 793 return FlexDirection.Column; 794 } else { 795 return FlexDirection.Row; 796 } 797 } 798 799 onFontSizeScaleChange() { 800 this.containerDirection = this.decideContainerDirection(); 801 this.contentItemDirection = this.decideContentItemDirection(); 802 if (this.fontSizeScale >= FontSizeScaleLevel.LEVEL3) { 803 this.containerPadding = { 804 top: $r('sys.float.padding_level12'), 805 bottom: $r('sys.float.padding_level12'), 806 }; 807 } else if (this.fontSizeScale >= FontSizeScaleLevel.LEVEL2) { 808 this.containerPadding = { 809 top: $r('sys.float.padding_level10'), 810 bottom: $r('sys.float.padding_level10'), 811 }; 812 } else if (this.fontSizeScale >= FontSizeScaleLevel.LEVEL1) { 813 this.containerPadding = { 814 top: $r('sys.float.padding_level8'), 815 bottom: $r('sys.float.padding_level8'), 816 }; 817 } else { 818 this.containerPadding = undefined; 819 } 820 } 821 822 isSingleLine(): boolean { 823 return !this.contentItem?.secondaryText && !this.contentItem?.description; 824 } 825 826 getOperateOffset(): LengthMetrics { 827 if (this.containerDirection === FlexDirection.Row) { 828 return LengthMetrics.vp(0); 829 } 830 let iconSize = ICON_SIZE_MAP.get(this.contentItem?.iconStyle as number); 831 if (this.contentItem?.icon && iconSize && iconSize <= HEADSCULPTURE_SIZE) { 832 return LengthMetrics.vp(iconSize + NORMAL_ITEM_ROW_SPACE + LISTITEM_PADDING - this.textArrowLeftSafeOffset); 833 } 834 return LengthMetrics.vp(LISTITEM_PADDING - this.textArrowLeftSafeOffset); 835 } 836 837 getMainSpace(): LengthMetrics { 838 if (this.containerDirection === FlexDirection.Column) { 839 return LengthMetrics.resource(this.isSingleLine() ? $r('sys.float.padding_level1') : $r('sys.float.padding_level8')); 840 } 841 return LengthMetrics.vp(0); 842 } 843 844 getFlexOptions(): FlexOptions { 845 if (this.containerDirection === FlexDirection.Column) { 846 return { 847 space: { main: this.getMainSpace() }, 848 justifyContent: FlexAlign.Center, 849 alignItems: ItemAlign.Start, 850 direction: this.containerDirection, 851 }; 852 } 853 return { 854 justifyContent: FlexAlign.SpaceBetween, 855 alignItems: ItemAlign.Center, 856 direction: this.containerDirection, 857 }; 858 } 859 860 decideFontSizeScale(): number { 861 if (!this.isFollowingSystemFontScale) { 862 return 1; 863 } 864 return Math.min( 865 this.maxFontScale, 866 (this.getUIContext().getHostContext() as common.UIAbilityContext)?.config.fontSizeScale ?? 1 867 ) 868 } 869 870 onMeasureSize(selfLayoutInfo: GeometryInfo, children: Measurable[], constraint: ConstraintSizeOptions): SizeResult { 871 this.fontSizeScale = this.decideFontSizeScale(); 872 let sizeResult: SizeResult = { height: 0, width: 0 }; 873 children.forEach((child) => { 874 let childMeasureResult: MeasureResult = child.measure(constraint); 875 sizeResult.width = childMeasureResult.width; 876 sizeResult.height = childMeasureResult.height; 877 }); 878 return sizeResult; 879 } 880 881 build() { 882 Stack() { 883 Flex(this.getFlexOptions()) { 884 if (this.contentItem === null) { 885 ContentItemStruct({}) 886 } 887 if (this.contentItem !== null) { 888 ContentItemStruct({ 889 icon: this.contentItem?.icon, 890 iconStyle: this.contentItem?.iconStyle, 891 primaryText: this.contentItem?.primaryText, 892 secondaryText: this.contentItem?.secondaryText, 893 description: this.contentItem?.description, 894 fontSizeScale: this.fontSizeScale, 895 parentDirection: this.containerDirection, 896 itemDirection: this.contentItemDirection, 897 }) 898 } 899 if (this.operateItem !== null) { 900 OperateItemStruct({ 901 icon: this.operateItem?.icon, 902 subIcon: this.operateItem?.subIcon, 903 button: this.operateItem?.button, 904 switch: this.operateItem?.switch, 905 checkBox: this.operateItem?.checkbox, 906 radio: this.operateItem?.radio, 907 image: this.operateItem?.image, 908 text: this.operateItem?.text, 909 arrow: this.operateItem?.arrow, 910 parentCanFocus: this.canFocus, 911 parentCanTouch: this.canTouch, 912 parentIsHover: this.isHover, 913 parentFrontColor: this.frontColor, 914 parentIsActive: this.isActive, 915 parentCanHover: this.canHover, 916 rightWidth: this.calculatedRightWidth(), 917 parentDirection: this.containerDirection, 918 }) 919 .flexShrink(0) 920 .onFocus(() => { 921 this.canFocus = false 922 }) 923 .onBlur(() => { 924 this.canFocus = true 925 }).padding({start: this.getOperateOffset()}); 926 } 927 } 928 .constraintSize({ 929 minHeight: this.itemHeight 930 }) 931 .focusable(true) 932 .borderRadius($r('sys.float.ohos_id_corner_radius_default_m')) 933 .backgroundColor(this.frontColor) 934 .onFocus(() => { 935 this.canFocus = true 936 }) 937 .onBlur(() => { 938 this.canFocus = false 939 }) 940 .onHover((isHover: boolean) => { 941 this.isHover = isHover 942 if (this.canHover) { 943 this.frontColor = isHover ? this.hoveringColor : 944 (this.isActive ? this.activedColor : Color.Transparent.toString()); 945 } 946 }) 947 .stateStyles({ 948 focused: { 949 .border({ 950 radius: $r('sys.float.ohos_id_corner_radius_default_m'), 951 width: ITEM_BORDER_SHOWN, 952 color: this.focusOutlineColor, 953 style: BorderStyle.Solid 954 }) 955 }, 956 normal: { 957 .border({ 958 radius: $r('sys.float.ohos_id_corner_radius_default_m'), 959 width: ITEM_BORDER_SHOWN, 960 color: Color.Transparent 961 }) 962 }, 963 pressed: { 964 .backgroundColor(this.touchDownColor) 965 } 966 }).padding(this.containerPadding) 967 } 968 .width('100%') 969 .padding({ 970 left: STACK_PADDING, 971 right: STACK_PADDING 972 }) 973 } 974}