1/* 2 * Copyright (c) 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 16const DEFAULT_BAR_WIDTH: number = 96; 17const DEFAULT_BAR_HEIGHT: number = 48; 18const TEXT_WIDTH_HEIGHT_SIZE: number = 24; 19const TEXT_FONT_WEIGHT: number = 500; 20const TEXT_LIGHT_HEIGHT: number = 14; 21 22@Component 23export struct AtomicServiceTabs { 24 @BuilderParam tabContents?: [TabContentBuilder?, 25 TabContentBuilder?, 26 TabContentBuilder?, 27 TabContentBuilder?, 28 TabContentBuilder?]; 29 @Prop tabBarOptionsArray: [TabBarOptions, TabBarOptions, TabBarOptions?, TabBarOptions?, TabBarOptions?]; 30 @Prop @Watch('barPositionChangeBySingleMode') tabBarPosition?: TabBarPosition = TabBarPosition.BOTTOM; 31 @Prop barBackgroundColor?: ResourceColor = Color.Transparent; 32 @Prop index?: number | undefined = 0; 33 @Prop barOverlap?: boolean = true; 34 @Prop layoutMode?: LayoutMode = LayoutMode.VERTICAL; 35 controller?: TabsController = new TabsController(); 36 onChange?: Callback<number>; 37 onTabBarClick?: Callback<number>; 38 onContentWillChange?: OnContentWillChangeCallback; 39 @State private selectedIndex: number = 0; 40 @State private barModeStatus?: BarMode = BarMode.Fixed; 41 @State private tabBarHeight?: Length = undefined; 42 private isIconTextExist: boolean = false; 43 44 aboutToAppear(): void { 45 if (this.tabBarOptionsArray[0].icon && this.tabBarOptionsArray[0].text) { 46 this.isIconTextExist = true; 47 } 48 this.barPositionChangeBySingleMode(); 49 } 50 51 /** 52 *单图标或文本场景下监听位置变化影响tabbar高度布局样式 53 */ 54 barPositionChangeBySingleMode(): void { 55 if (this.isIconTextExist) { 56 return; 57 } 58 if (this.tabBarPosition === TabBarPosition.LEFT) { 59 this.tabBarHeight = (50 / this.tabBarOptionsArray.length + '%'); 60 this.barModeStatus = BarMode.Scrollable; 61 } else { 62 this.barModeStatus = BarMode.Fixed; 63 this.tabBarHeight = undefined; 64 } 65 } 66 67 @Builder 68 TabBuilder(item: TabBarOptions, index: number) { 69 Flex({ 70 alignItems: ItemAlign.Center, 71 justifyContent: FlexAlign.Center 72 }){ 73 if (item.icon) { 74 Image(item.icon as ResourceStr) 75 .width(TEXT_WIDTH_HEIGHT_SIZE) 76 .height(TEXT_WIDTH_HEIGHT_SIZE) 77 .objectFit(ImageFit.Contain) 78 .fillColor(this.selectedIndex === index ? item.selectedColor : item.unselectedColor) 79 .backgroundColor(Color.Transparent) 80 .flexShrink(0) 81 } else { 82 Text(item.text) 83 .textOverflow({ overflow: TextOverflow.Ellipsis }) 84 .maxLines(1) 85 .fontColor(this.selectedIndex === index ? item.selectedColor : item.unselectedColor) 86 .maxFontSize($r('sys.float.ohos_id_text_size_button3')) 87 .minFontSize(9) 88 .fontWeight(TEXT_FONT_WEIGHT) 89 .lineHeight(TEXT_LIGHT_HEIGHT) 90 .textAlign(TextAlign.Center) 91 .focusOnTouch(true) 92 .backgroundColor(Color.Transparent) 93 } 94 } 95 .height(this.tabBarHeight) 96 } 97 98 build() { 99 Tabs({ 100 barPosition: this.tabBarPosition === TabBarPosition.LEFT ? BarPosition.Start : BarPosition.End, 101 index: this.index, 102 controller: this.controller 103 }) { 104 ForEach(this.tabBarOptionsArray, (item: TabBarOptions, index: number) => { 105 if (item) { 106 TabContent() { 107 if (this.tabContents && this.tabContents[index]) { 108 this.tabContents[index]?.() 109 } 110 } 111 .tabBar( 112 this.isIconTextExist ? 113 BottomTabBarStyle.of(item.icon, item.text).layoutMode(this.layoutMode) 114 .labelStyle({ unselectedColor: item.unselectedColor, selectedColor: item.selectedColor }) 115 .iconStyle({ unselectedColor: item.unselectedColor, selectedColor: item.selectedColor }) : 116 this.TabBuilder(item, index) 117 ) 118 .width((!this.tabContents && this.tabBarPosition === TabBarPosition.LEFT) ? DEFAULT_BAR_WIDTH : '100%') 119 .height((!this.tabContents && this.tabBarPosition === TabBarPosition.BOTTOM) ? DEFAULT_BAR_HEIGHT : '100%') 120 } 121 }) 122 } 123 .safeAreaPadding({ 124 // barHeight设置具体高度时,tabBar的背景色不会延伸到底部安全区,需要新增该属性值使tabBar和安全区背景色一致 125 bottom: 0 126 }) 127 .animationDuration(0) // 切换页签时,tabBar有默认的动画效果,设置该属性取消动画效果 128 .barBackgroundColor(this.barBackgroundColor) 129 .divider(null) 130 .barMode(this.barModeStatus) 131 .vertical(this.tabBarPosition === TabBarPosition.LEFT ? true : false) 132 .scrollable(false) 133 .barOverlap(this.barOverlap) 134 .barBackgroundBlurStyle(BlurStyle.COMPONENT_THICK) 135 .onChange((index: number) => { 136 if (this.onChange) { 137 this.onChange(index); 138 } 139 this.selectedIndex = index; 140 }) 141 .onTabBarClick(this.onTabBarClick) 142 .onContentWillChange(this.onContentWillChange) 143 .barWidth(this.isIconTextExist ? undefined : 144 (this.tabBarPosition === TabBarPosition.LEFT) ? DEFAULT_BAR_WIDTH : '100%') 145 .barHeight(this.isIconTextExist ? undefined : 146 (this.tabBarPosition === TabBarPosition.BOTTOM) ? DEFAULT_BAR_HEIGHT : '100%') 147 .width((!this.tabContents && this.tabBarPosition === TabBarPosition.LEFT) ? DEFAULT_BAR_WIDTH : '100%') 148 .height((!this.tabContents && this.tabBarPosition === TabBarPosition.BOTTOM) ? DEFAULT_BAR_HEIGHT : '100%') 149 } 150} 151 152export class TabBarOptions { 153 public icon: ResourceStr | TabBarSymbol; 154 public text: ResourceStr; 155 public unselectedColor?: ResourceColor; 156 public selectedColor?: ResourceColor; 157 158 constructor(icon: ResourceStr | TabBarSymbol, text: ResourceStr, 159 unselectedColor?: ResourceColor, selectedColor?: ResourceColor) { 160 this.icon = icon; 161 this.text = text; 162 this.unselectedColor = unselectedColor; 163 this.selectedColor = selectedColor; 164 } 165} 166 167export enum TabBarPosition { 168 LEFT = 0, 169 BOTTOM = 1 170} 171 172export type TabContentBuilder = () => void; 173export type OnContentWillChangeCallback = (currentIndex: number, comingIndex: number) => boolean;