This content originally appeared on DEV Community and was authored by kouwei qing
[Daily HarmonyOS Next Knowledge]
1. Problems with HarmonyOS bindPopup?
Added a Popup for long-pressing each ListItem.
Problem 1: When using a @state variable at the outer level, long-pressing triggers multiple Popups. How to listen and display the Popup for the specific item pressed?
Problem 2: After clicking a Popup to delete the current item, how to listen for Popup clicks and make the Popup disappear after deletion?
Refer to the following demo:
import { router } from '@kit.ArkUI'
import { BusinessError } from '@kit.BasicServicesKit'
@Entry
@Component
export struct Index {
@State list: stockListData = new stockListData([])
aboutToAppear(): void {
const list: ItemInfo[] = []
for (let i = 0; i < 50; ++i) {
let stockName = "item" + i
let bgState = 0
list.push(new ItemInfo(stockName, bgState))
}
this.list.modifyAllData(list)
}
build() {
Column() {
Text("Start").height('50')
.width('100%').onClick(() => {
})
List({ space: 10 }) {
LazyForEach(this.list, (info: ItemInfo, index: number) => {
ListItem() {
ItemView({
info: info, itemFun: (name) => {
let itemIndex = 0
for (let im of this.list.getAllData()) {
if (im.stockName === name) {
break
}
itemIndex++
}
console.info("animate--ItemView--aboutToReuse---delete Name=" + name)
this.list.getAllData().splice(itemIndex, 1)
this.list.notifyDataDelete(itemIndex)
}
})
.onClick(() => {
})
}.height('80')
}, (info: ItemInfo, index: number) => info.stockName + "type" + index)
}
.height('100%')
.width('100%')
}
.height('100%')
.width('100%')
}
}
@Reusable
@Component
struct ItemView {
@Prop info: ItemInfo
private lastName: string = ""
aboutToAppear(): void {
this.lastName = this.info.stockName
}
aboutToReuse(params: Record<string, Object>): void {
console.info("animate--ItemView--aboutToReuse---name=" + this.info.stockName + ",lastName=" + this.lastName)
this.lastName = this.info.stockName
}
@State customPopup: boolean = false
itemFun: (name: string) => void = (name) => {
}
@Builder
popItemsView() {
Text("qipao pop").onClick(() => {
this.customPopup = !this.customPopup
this.itemFun(this.info.stockName)
})
}
build() {
Column() {
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Column() {
Text(this.info.stockName)
.maxFontSize('16')
.minFontSize('8')
.maxLines(1)
.margin({ bottom: 2 })
}
.width('36.27%')
.alignItems(HorizontalAlign.Start)
.padding({ left: '16', right: 8 })
}
.backgroundColor("#00ffea")
.height('100%')
}
.bindPopup(this.customPopup, {
builder: this.popItemsView, // Popup content
placement: Placement.Top, // Popup position
offset: { x: 0, y: 30 },
radius: 12,
popupColor: Color.Pink, // Popup background color
onStateChange: (e) => {
console.info('tag', JSON.stringify(e.isVisible))
if (!e.isVisible) {
this.customPopup = false
// this.bgColor = this.info.topStatus == 1 ? $r('app.color.dialog_background') : $r('app.color.start_window_background')
} else {
this.customPopup = true
// this.bgColor = $r('app.color.dialog_button')
}
}
})
.gesture(
LongPressGesture({ repeat: false })// Setting repeat to true triggers continuous long-press events at intervals of duration (default 500ms)
.onAction((event?: GestureEvent) => {
console.log("tag", "LongPressGesture start event--customPopup=" + this.customPopup)
if (event) {
this.customPopup = !this.customPopup
}
// if (event && event.repeat) {
// }
})// Triggers when the long-press action ends
.onActionEnd((event?: GestureEvent) => {
if (event) {
}
})
)
}
}
@Observed
class ItemInfo {
@Track
stockName: string = "--"
@Track
bgState: number = 0
constructor(name: string, bg: number) {
this.stockName = name
this.bgState = bg
}
}
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = [];
public totalCount(): number {
return 0;
}
public getData(index: number): ItemInfo | undefined {
return undefined;
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
this.listeners.push(listener);
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const position = this.listeners.indexOf(listener);
if (position >= 0) {
this.listeners.splice(position, 1);
}
}
notifyDataReload(): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataReloaded();
})
}
notifyDataAdd(index: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataAdd(index);
})
}
notifyDataChange(index: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataChange(index);
})
}
notifyDataDelete(index: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataDelete(index);
})
}
notifyDataMove(from: number, to: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataMove(from, to);
})
}
}
export class stockListData extends BasicDataSource {
private listData: ItemInfo[] = [];
constructor(dataArray: ItemInfo[]) {
super()
this.listData = dataArray;
}
public totalCount(): number {
return this.listData.length;
}
public getAllData(): ItemInfo[] {
return this.listData;
}
public getData(index: number): ItemInfo {
return this.listData[index];
}
public modifyAllData(data: ItemInfo[]): void {
this.listData = data
this.notifyDataReload()
}
}
2. How to set a transparent background for a HarmonyOS @Entry page?
For the router routing mode, refer to the following demo:
// Page1.ets
import window from '@ohos.window';
@Entry
@Component
struct Page2 {
@State message: string = 'page Page2';
onPageHide() {
console.log("pageHide")
}
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button("pageB").onClick(() => {
let windowStege: window.WindowStage = AppStorage.get("windowStage") as window.WindowStage;
windowStege.createSubWindow("hello", (err, win) => {
win.setUIContent('pages/Page2');
win.showWindow();
})
})
}
.width('100%')
}
.height('100%')
.backgroundColor(Color.Pink)
}
}
// Page2.ets
import window from '@ohos.window';
@Entry
@Component
struct Index {
@State message: string = 'page Index';
aboutToAppear() {
window.findWindow("hello").setWindowBackgroundColor("#00000000")
}
onBackPress() {
window.findWindow("hello").destroyWindow().then((res) => {
console.log("destroyWindow success")
}).catch(() => {
console.log("destroyWindow fail")
})
return true
}
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.backgroundColor(Color.Red)
}
.alignItems(VerticalAlign.Top)
.height('100%')
.backgroundColor("#80ffffff")
}
}
// Note: Add the key code in EntryAbility
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Page231206095213071', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
AppStorage.setAndLink("windowStage", windowStage);// Key code
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
AppStorage.setAndLink(“windowStage”, windowStage);// This key code configures windowStage.loadContent(‘pages/page1’). The demo shows Page1 jumping to Page2, where Page2 has a transparent effect.
3. In HarmonyOS ArkTS ArkUI, how to define generics when any, null, etc., cannot be used, and parameters may be arrays, objects, or multi-level structures?
ArkTS does not support any
, undefined
, or unknown
types. Explicitly specify specific types.
Modification solutions:
- All variables should explicitly specify their specific types.
- For literals, use
Record<>
withas
to specify types.
How to define generics for component parameters:
Refer to the demo:
let strArr: string[] = ["java", "python", "C++"]
let intArr: number[] = [1, 2, 3]
@Entry
@Component struct Index {
build() {
Row() {
Column() {
this.printArray(strArr)
Text('Divider')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.fontColor(Color.Green)
this.printArray(intArr)
}
.width('100%')
}
.height('100%')
}
@Builder printArray<T>(arr: T[]) {
Column() {
ForEach(arr, (item: T) => {
Text(String(item))
.fontSize(50)
.fontWeight(FontWeight.Bold)
}, (item: string) => item)
}
}
}
4. How to write combination gestures for a control to recognize swipe and drag in HarmonyOS, and how to determine the swipe direction?
A control listens for gestures, directly operating on swipe and moving according to the drag distance on drag.
Refer to the following demo:
// xxx.ets
@Entry
@Component
struct Index {
@State offsetX: number = 0;
@State offsetY: number = 0;
@State count: number = 0;
@State positionX: number = 0;
@State positionY: number = 0;
@State borderStyles: BorderStyle = BorderStyle.Solid
build() {
Column() {
Text('sequence gesture\n' + 'LongPress onAction:' + this.count + '\nPanGesture offset:\nX: ' + this.offsetX + '\n' + 'Y: ' + this.offsetY)
.fontSize(28)
}
// Binding the translate property enables component position movement
.translate({ x: this.offsetX, y: this.offsetY, z: 0 })
.height(250)
.width(300)
// The following combination gestures are recognized in sequence; drag gestures will not trigger if long-press events do not fire normally
.gesture(
// Declare the combination gesture type as Sequence
GestureGroup(GestureMode.Sequence,
// The first trigger in the combination gesture is a long-press gesture, which can respond multiple times
LongPressGesture({ repeat: true })
// When the long-press gesture is recognized, increment the count displayed on the Text component
.onAction((event: GestureEvent|undefined) => {
if(event){
if (event.repeat) {
this.count++;
}
}
console.info('LongPress onAction');
})
.onActionEnd(() => {
console.info('LongPress end');
}),
// When dragging after a long-press, the PanGesture is triggered
PanGesture()
.onActionStart(() => {
this.borderStyles = BorderStyle.Dashed;
console.info('pan start');
})
// When the gesture is triggered, modify the component's displacement based on the drag distance from the callback
.onActionUpdate((event: GestureEvent|undefined) => {
if(event){
this.offsetX = this.positionX + event.offsetX;
this.offsetY = this.positionY + event.offsetY;
}
console.info('pan update');
})
.onActionEnd(() => {
this.positionX = this.offsetX;
this.positionY = this.offsetY;
this.borderStyles = BorderStyle.Solid;
})
)
)
}
}
5. Are there specifications or documentation for HarmonyOS desktop icons?
For desktop icon and launch page icon resolution specifications, refer to: https://developer.huawei.com/consumer/cn/doc/design-guides-V1/app-icon-0000001188065088-V1
HarmonyOS app icon design aims to return to basics, accurately conveying functions, services, and branding through modern semantic expression. Visually, it balances aesthetics and recognizability; formally, it is inclusive and harmonious yet distinct.
HarmonyOS app icons follow these design principles:
- Conciseness and Elegance: Simple element icons with elegant line expressions convey design aesthetics.
- Rapid Meaning Conveyance: Icon graphics accurately communicate functions, services, and branding, with readability and recognizability.
- Emotional Expression: Summarize emotional expression through graphics and colors to convey brand visual identity.
This content originally appeared on DEV Community and was authored by kouwei qing