[Daily HarmonyOS Next Knowledge] getContext Issue, Web Cache Clearance, Popup Click Event Passthrough, Gap Removal, Swipe Menu Settings
1. HarmonyOS getContext() not accessible?
When using bindPopup and bindSheet on two different pages to pop up the same dialog, clicking a button in the dialog to jump to an H5 page causes bindPopup to crash with the error: Error message: Cannot read property resourceManager of undefined
. The source code shows const resourceManager = context.resourceManager;
, indicating getContext() fails to retrieve the context. bindSheet, however, opens the H5 page normally.
Issue: getContext() failure leads to resourceManager being undefined.
Solutions:
- Use
this.context
within the ability. - Use
getContext()
on the UI side.
2. How to clear Web cache in HarmonyOS?
How to clear resources (such as images) cached by the Web component after loading a webpage.
Use the removeCache()
method.
Reference: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-webview-V5#removecache
removeCache(clearRom: boolean): void
- This method clears resource cache files for all webviews in the same application.
- Webview cache can be viewed in the
data/storage/el2/base/cache/web/Cache
directory.
3. Handling click event passthrough for HarmonyOS popups
Implementing a popup feature across multiple pages requires handling click event passthrough for popups. The goal is to obtain the click position (pixel coordinates within the popup) and transparency to determine if the event should pass through to the underlying scene.
Current challenge: The component screenshot API is asynchronous, while event interception requires a synchronous API, making the overall chain unfeasible.
Reference code:
@Entry
@Component
struct HitTestPage {
@State message: string = 'Hello World';
build() {
Stack() {
Column()
.onTouch((event: TouchEvent)=> {
if (event.type == TouchType.Down) {
console.info("Clicked: Bottom interface")
}
})
.height('100%')
.width('100%')
.backgroundColor(Color.Green)
Column() {
Text()
.backgroundColor(Color.Black)
.onTouch((event: TouchEvent)=> {
event.stopPropagation()
if (event.type == TouchType.Down) {
console.info("Clicked: Text")
}
})
.height(100)
.width(100)
}
.onTouch((event: TouchEvent)=> {
if (event.type == TouchType.Down) {
console.info("Clicked: Upper interface")
}
})
.hitTestBehavior(HitTestMode.Transparent)
.backgroundColor(Color.Transparent)
.height('100%')
.width('100%')
}
.height('100%')
.width('100%')
}
}
//Index.ets
import { PageTwo } from './pageTwo';
export class NavParam {
dialogHeightChangeBack?: (dialogHeight: number) => void
constructor( dialogHeightChangeBack?: (dialogHeight: number) => void) {
this.dialogHeightChangeBack = dialogHeightChangeBack
}
}
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
@Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack()
@StorageLink("windowHeight") windowHeight: number = 0
@State contentHeight: number = 100;
private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
@Builder
PageMap(name: string, param: NavParam) {
if (name === 'pageTwo') {
PageTwo({dialogHeightChangeBlock: param.dialogHeightChangeBack});
}
}
aboutToAppear() {
console.info("testTag: Current windowHeight" + this,this.windowHeight);
this.contentHeight = px2vp(this.windowHeight);
}
build() {
Navigation(this.pageInfos) {
Column() {
Row().height(50)
Button("pageOne")
.fontSize(30)
.type(ButtonType.Capsule)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.pageInfos.pushPath({ name: 'pageTwo', param: new NavParam(
(dialogHeight: number) => {
if (dialogHeight == 0) {
animateTo({ duration: 250 }, () => {
this.contentHeight = px2vp(this.windowHeight);
})
} else {
this.contentHeight = px2vp(this.windowHeight) - dialogHeight;
}
})
});
})
List({ space: 20, initialIndex: 0 }) {
ForEach(this.arr, (item: number) => {
ListItem() {
Text('' + item)
.width('100%').height(100).fontSize(16)
.textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF)
}
}, (item: string) => item)
}
.listDirection(Axis.Vertical)
.scrollBar(BarState.Off)
.friction(0.6)
.divider({ strokeWidth: 2, color: 0xFFFFFF, startMargin: 20, endMargin: 20 })
.edgeEffect(EdgeEffect.Spring)
.width('90%')
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height(this.contentHeight)
.hitTestBehavior(HitTestMode.Transparent)
.onTouch((event: TouchEvent) => {
if (event.type == TouchType.Down) {
console.info('Event passthrough: Column clicked')
}
})
}
.navDestination(this.PageMap)
.onTouchIntercept((event: TouchEvent) => {
if (event.type == TouchType.Down) {
console.info('Event passthrough: Navigation clicked')
}
return HitTestMode.Transparent
})
}
}
//PageTwo.ets
@Component
export struct PageTwo {
@State isShow: boolean = true;
gravity: Alignment = Alignment.Bottom
transitionEdge: TransitionEdge = TransitionEdge.BOTTOM
@Consume('pageInfos') pageInfos: NavPathStack;
@State gestureHeight: number = 500;
dialogHeightChangeBlock?: (dialogHeight: number) => void;
private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
scrollOffsetY: number = 0;
private scrollerForList: Scroller = new Scroller()
@State canHandleScroll: boolean = true;
aboutToAppear() {
this.callBackHeight(this.gestureHeight);
}
callBackHeight(height: number) {
if (this.dialogHeightChangeBlock) {
this.dialogHeightChangeBlock!(height);
}
}
build() {
NavDestination() {
Stack({ alignContent: this.gravity }) {
Stack({ alignContent: this.gravity }) {
if (this.isShow) {
Stack({ alignContent: Alignment.Top }) {
Column() {
List({ space: 20, initialIndex: 0, scroller: this.scrollerForList }) {
ForEach(this.arr, (item: number) => {
ListItem() {
Text('' + item)
.width('100%')
.height(100)
.fontSize(16)
.textAlign(TextAlign.Center)
.borderRadius(10)
.backgroundColor(0xFFFFFF)
}
}, (item: string) => item)
}
.enableScrollInteraction(this.canHandleScroll)
.listDirection(Axis.Vertical)
.scrollBar(BarState.Off)
.friction(0.6)
.divider({ strokeWidth: 2, color: 0xFFFFFF, startMargin: 20, endMargin: 20 })
.edgeEffect(EdgeEffect.Spring)
.onScroll((scrollOffset: number, scrollState: ScrollState) => {
this.scrollOffsetY += scrollOffset;
console.info("list: y offset " + this.scrollOffsetY);
if (this.scrollOffsetY <= 0) {
this.scrollerForList.scrollTo({xOffset:0,yOffset:0,animation:false});
this.scrollOffsetY = 0;
this.canHandleScroll = false;
}
})
.height('100%')
.width('100%')
}
}
.borderRadius(6)
.transition(TransitionEffect.move(this.transitionEdge).animation({ duration: 250, curve: Curve.Smooth }))
.position({ x: 0, y: 0 })
.height("100%")
.width("100%")
.clip(true)
.backgroundColor(Color.White)
}
}
.width("100%")
.height(this.gestureHeight)
.parallelGesture(
PanGesture({ direction: PanDirection.Vertical, fingers: 1 })
.onActionUpdate((event: GestureEvent) => {
if (!this.canHandleScroll) {
console.info("testTag: y offset " + event.offsetY);
let temHeight = 500;
temHeight -= event.offsetY;
if (temHeight >= 500) {
this.gestureHeight = 500;
} else {
this.gestureHeight = temHeight;
}
this.callBackHeight(this.gestureHeight);
}
})
.onActionEnd((event: GestureEvent) => {
if (!this.canHandleScroll) {
console.info("testTag: Animation end y offset " + event.offsetY);
let temHeight = 500;
temHeight -= event.offsetY;
if (temHeight < 250) {
this.closeDialogAction()
} else if (temHeight >= 250 && temHeight < 500) {
let duration = (500 - temHeight) / 500.0 * 250.0;
animateTo({ duration: duration }, () => {
this.gestureHeight = 500;
this.callBackHeight(this.gestureHeight);
})
}
this.canHandleScroll = true;
}
})
)
}
.onClick(() => {
// this.closeDialogAction()
})
.onTouchIntercept((event: TouchEvent) => {
if (event.type == TouchType.Down) {
console.info('Event passthrough: Stack clicked')
}
return HitTestMode.Transparent
})
.width('100%')
.height('100%')
.backgroundColor('#33000000')
}
.onBackPressed(() => {
this.closeDialogAction()
return true
})
.hitTestBehavior(HitTestMode.Transparent)
.onTouch((event: TouchEvent) => {
if (event.type == TouchType.Down) {
console.info('Event passthrough: Dialog clicked')
}
})
.hideTitleBar(true)
.mode(NavDestinationMode.DIALOG)
}
closeDialogAction() {
this.isShow = false;
this.callBackHeight(0);
animateTo({
duration: 100, delay: 250, onFinish: () => {
this.pageInfos.pop()
}
}, () => {
})
}
}
//EntryAbility.ets
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
async function enterImmersion(windowClass: window.Window) {
AppStorage.setOrCreate<number>('windowHeight', windowClass.getWindowProperties().windowRect.height)
AppStorage.setOrCreate<number>('windowWidth', windowClass.getWindowProperties().windowRect.width)
windowClass.on('windowSizeChange', (size)=> {
AppStorage.setOrCreate<number>('windowHeight', size.height)
AppStorage.setOrCreate<number>('windowWidth', size.width)
})
await windowClass.setWindowLayoutFullScreen(true)
await windowClass.setWindowSystemBarEnable(["status", "navigation"])
await windowClass.setWindowSystemBarProperties({
navigationBarColor: "#00000000",
statusBarColor: "#00000000",
})
}
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy(): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
async onWindowStageCreate(windowStage: window.WindowStage) {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
let windowClass:window.Window = await windowStage.getMainWindow()
await enterImmersion(windowClass)
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
});
}
onWindowStageDestroy(): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground(): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}
4. How to remove gaps in HarmonyOS?
How to remove the gap between the virtual keyboard and a custom input dialog.
Reason for the gap: Custom popups are designed for simple prompts and should not replace full pages. Popups automatically avoid the soft keyboard by lifting the popup by the keyboard height, creating a 16vp safety margin between the popup and the keyboard.
Solution: Manually set offset: { y: 16 }
when the soft keyboard pops up to offset the safety margin.
Reference demo:
//CustomDialog.ets
build{
Column(){
TextArea({
text: "",
placeholder: 'The text area can hold an unlimited amount of text. input your word...',
controller: this.textController
})
.height(200)
.width("100%")
}
.offset({
y:16
})
}
// Specification selection page controller
customDialogController: CustomDialogController = new CustomDialogController({
builder: SpecificationsCustomDialog({
}),
alignment: DialogAlignment.Bottom,
customStyle: true,
autoCancel: false,
offset: {
dx: 0,
dy: -16
}
});
5. Dynamic setting of HarmonyOS ListItem swipe menu
A list with items that have swipe menus, but no swipe menus in edit mode. Implement dynamic swipe menu setting via a toggle.
Reference demo:
@Entry
@Component
struct ListItemExample2 {
@State arr: number[] = [0, 1, 2, 3, 4]
@State enterEndDeleteAreaString: string = "not enterEndDeleteArea"
@State exitEndDeleteAreaString: string = "not exitEndDeleteArea"
@State isShow:boolean = true;
@Builder itemEnd() {
Row() {
Button("Delete").margin("4vp")
Button("Set").margin("4vp")
}.padding("4vp").justifyContent(FlexAlign.SpaceEvenly)
}
build() {
Column() {
Button("Click").onClick(()=>{
this.isShow=!this.isShow
Top comments (0)