This document provides development guidance for two typical scenarios of immersive light sensing, including the search box title bar effect and enabling immersive light sensing for the content area title bar.
In information browsing applications (such as news, forums, and novel reading applications), when you swipe up the home page content area, the title bar display range shrinks accordingly, while Immersive Light Sensing enhances the interactive experience of the title bar.
Set the bottom Tabs to float and enable immersive light sensing for the Tabs component. Also use ExpandSafeArea to extend the displayed content to the status bar area, making the overall application experience more consistent.
@Entry
@ComponentV2
struct BestPractise {
@Local currentTab: number = 0
exploreStack: NavPathStack = new NavPathStack()
gameStack: NavPathStack = new NavPathStack()
@Builder
BottomTabBarItem(title: string, icon: Resource, index: number) {
Column() {
SymbolGlyph(icon)
.fontSize(22)
.fontColor(this.currentTab === index ? ['#007dff'] : ['#999999'])
Text(title)
.fontSize(10)
.fontColor(this.currentTab === index ? '#007dff' : '#999999')
.margin({ top: 2 })
}.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
}
@Builder
tabExploreContent() {
// Configure the corresponding page navigation through the system route table.
Navigation(this.exploreStack, { name: 'explore' })
.hideTitleBar(true)
}
@Builder
tabGameContent() {
// Configure the corresponding page navigation through the system route table.
Navigation(this.gameStack, { name: 'game' })
.hideTitleBar(true)
}
build() {
Tabs({ index: this.currentTab }) {
TabContent() {
this.tabExploreContent()
}.tabBar(this.BottomTabBarItem('Explore', $r('sys.symbol.compass'), 0))
TabContent() {
this.tabGameContent()
}.tabBar(this.BottomTabBarItem('Games', $r('sys.symbol.gamecontroller'), 1))
TabContent() {
}.tabBar(this.BottomTabBarItem('Apps', $r('sys.symbol.grid'), 2))
TabContent() {
}.tabBar(this.BottomTabBarItem('Atomic services', $r('sys.symbol.gearshape'), 3))
}
.barPosition(BarPosition.End)
.barMode(BarMode.Fixed)
.barOverlap(true)
.barFloatingStyle({ systemMaterial: new uiMaterial.ImmersiveMaterial({ style: uiMaterial.ImmersiveStyle.THIN }) })
.ignoreLayoutSafeArea([LayoutSafeAreaType.SYSTEM], [LayoutSafeAreaEdge.BOTTOM])
.height(LayoutPolicy.matchParent)
.onChange((index: number) => {
this.currentTab = index
})
}
} For the target page to navigate to, enable immersive light sensing for the title bar area of the corresponding tab page through NavigationTitleOptions. It is recommended to set the BarStyle of the Navigation component to STACK mode so that the content area is displayed below the title bar, achieving a see-through effect. The following code implements the following effects: immersive light sensing is enabled for the title bar and its child components. When swiping up, the search box is hidden while the category list is retained and highlighted. Immersive light sensing is enabled for the category list items to improve user interaction experience and content exposure.
@ComponentV2
struct ExploreHomePage {
@Local currentIndex: number = 0
@Local searchOpacity: number = 1
@Local classifyType: Array<string> = []
totalTitleHeight = 110
@Local scrollOffset: number = 0
@Local titleOffset: number = 0
@Builder
exploreTitleBar() {
Column() {
Row() {
Text('Explore')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#1A1A1A')
Blank()
Search({ placeholder: 'Explore Explore' })
.searchButton('Search')
.height(40)
.width(220)
.systemMaterial(new uiMaterial.ImmersiveMaterial({}))
}
.width('100%')
.opacity(this.searchOpacity)
.height(50)
ClassifyComponent()
}
.width('100%')
.padding({ left: 20, right: 20 })
.position({ x: 0, y: -this.titleOffset })
}
build() {
NavDestination() {
Scroll() {
// Content of the sliding area.
Column() {
Image($r('app.media.startIcon')).width('100%').height(180)
.borderRadius(12)
.backgroundColor('#e0e0e0')
.objectFit(ImageFit.Cover)
List() {
ForEach(listItems, (item: ListItemData) => {
ListItem() {
Row() {
SymbolGlyph(item.image).fontSize(36)
.fontColor(['#007dff'])
.margin({ right: 16 })
Text(item.name).fontSize(16)
.fontColor('#333333')
}.width('100%')
.padding({ left: 20, right: 20, top: 14, bottom: 14 })
}
}, (item: ListItemData) => item.id)
}
}
}
// Avoid the title bar display area.
.contentStartOffset(this.totalTitleHeight)
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.Spring)
.width('100%')
.height('100%')
.onDidScroll((xOffset: number, yOffset: number, state: ScrollState) => {
this.scrollOffset += yOffset
if (this.scrollOffset <= 50) {
this.titleOffset = this.scrollOffset
this.searchOpacity = 1 - this.titleOffset / 50
}
})
}.title(
{ builder: this.exploreTitleBar, height: this.totalTitleHeight },
{ barStyle: BarStyle.STACK,
// Set the title bar blur.
scrollEffectOptions: {
scrollEffectType: ScrollEffectType.GRADUAL_BLUR
}
}
).hideBackButton(true)
}
} Immersive light sensing is currently subject to certain constraints. For details, see Immersive Light Sensing Power Optimization. In scenarios where the content area scrolls and contains multiple levels of titles, when a content area title scrolls to the title bar area, you can embed the corresponding content into the title bar for display, so that the content area title is shown in the NavDestination title bar area, thereby improving the user interaction experience.
Extract the subtitles in the content area as independent components.
@ComponentV2
export struct ClassifyComponent {
@Local selectedIndexes: Array<number> = [0]
info: Array<ChipGroupItemOptions> = []
aboutToAppear(): void {
let classifyType: string[] = ['Strategy', 'Action', 'Competitive', 'Shooting', 'Card', 'Sports', 'Casual', 'Music']
for (let index: number = 0; index < classifyType.length; index++) {
this.info.push({
label: { text: classifyType[index] },
prefixSymbol: {
activated: new SymbolGlyphModifier($r('sys.symbol.star_fill')).fontSize(20).fontColor(['#d3d3d3']),
normal: new SymbolGlyphModifier($r('sys.symbol.star_fill')).fontSize(20).fontColor([Color.Black])
}
})
}
}
build() {
ChipGroup({
items: this.info,
itemStyle: {
backgroundColor: Color.Transparent,
selectedFontColor: '#d3d3d3'
},
backgroundSystemMaterial: new uiMaterial.ImmersiveMaterial({
colorInvert: true,
interactive: true
}),
selectedIndexes: this.selectedIndexes,
onChange: (activeIndexes: Array<number>) => {
this.selectedIndexes = activeIndexes
}
})
}
} Scroll the content area. When the content area title scrolls to the title bar area, switch it to display in the title bar.
@ComponentV2
struct GamePage {
@Local titleHeight: number = 60
@Local scrollOffset: number = 0
@Local showTitle: boolean = false
@Local titleOpacity: number = 1
@Local contentOffset: number = 0
totalOffset: number = 0
@Local textOffset: number = 0
titleEnd: number = 0
titleStart: number = 0
@Local listVisible: Visibility = Visibility.Visible
@Builder
gameTitleBar() {
Row() {
if (this.showTitle) {
ClassifyComponent()
} else {
Text('Game')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#1A1A1A')
.opacity(this.titleOpacity)
.id('text')
Blank()
}
Button() {
SymbolGlyph($r('sys.symbol.AI_search')).fontSize(20)
}.borderRadius(180)
.width(40)
.height(40)
.backgroundColor(Color.Transparent)
.systemMaterial(new uiMaterial.ImmersiveMaterial({
lightEffect: { color: Color.White },
materialColor: '#80d3d3d3'
}))
}
.width('100%')
.height('100%')
.padding({ left: 20, right: 20 })
.alignItems(VerticalAlign.Center)
}
build() {
NavDestination() {
Scroll() {
Column() {
Image($r('app.media.startIcon'))
.width('100%')
.height(180)
.borderRadius(12)
.backgroundColor('#fff3e0')
.objectFit(ImageFit.Cover)
ClassifyComponent()
.id('content')
.visibility(this.showTitle ? Visibility.Hidden : Visibility.Visible)
List() {
ForEach(listItems, (item: ListItemData) => {
ListItem() {
Row() {
SymbolGlyph(item.image)
.fontSize(36)
.fontColor(['#ff6d00'])
.margin({ right: 16 })
Text(item.name).fontSize(16).fontColor('#333333')
}.width('100%')
.padding({ left: 20, right: 20, top: 14, bottom: 14 })
}
}, (item: ListItemData) => item.id)
}.width('100%')
.margin({ top: 8 })
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
.divider({ strokeWidth: 5, color: '#e0e0e0', startMargin: 72, endMargin: 20 })
}.padding({ left: 16, right: 16, top: 8, bottom: 16 })
}
.contentStartOffset(this.titleHeight)
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.Spring)
.width('100%')
.height('100%')
.onDidScroll((xOffset: number, yOffset: number) => {
this.totalOffset += yOffset
let curOffset = this.contentOffset - this.totalOffset
if (curOffset > this.titleEnd) {
this.showTitle = false
this.listVisible = Visibility.Hidden
return
}
if (curOffset < this.titleEnd) {
this.showTitle = true
this.listVisible = Visibility.Visible
return
}
this.titleHeight = (curOffset - this.titleEnd) / (this.titleHeight - this.titleEnd)
})
}.onShown(() => {
let titleInfo = this.getUIContext().getComponentUtils().getRectangleById('text')
this.titleStart = this.getUIContext().px2vp(titleInfo.windowOffset.y)
this.titleEnd = this.getUIContext().px2vp(titleInfo.size.height)
this.contentOffset = this.getUIContext().px2vp(
this.getUIContext().getComponentUtils().getRectangleById('content').windowOffset.y
)
})
.hideBackButton(true)
.title({ builder: this.gameTitleBar, height: this.titleHeight }, {
barStyle: BarStyle.STACK,
scrollEffectOptions: {
scrollEffectType: ScrollEffectType.GRADUAL_BLUR
}
})
}
}