DEV Community

Cover image for Popup Issues, Transparent Background Setting, any Type Problem, Combination Gestures for Swipe and Drag
kouwei qing
kouwei qing

Posted on

Popup Issues, Transparent Background Setting, any Type Problem, Combination Gestures for Swipe and Drag

[Daily HarmonyOS Next Knowledge]

Image description

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()
  }
}
Enter fullscreen mode Exit fullscreen mode

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) ?? '');
});
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. All variables should explicitly specify their specific types.
  2. For literals, use Record<> with as 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) 
    } 
  } 
}
Enter fullscreen mode Exit fullscreen mode

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;
          })
      )
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

Image description

HarmonyOS app icons follow these design principles:

  1. Conciseness and Elegance: Simple element icons with elegant line expressions convey design aesthetics.
  2. Rapid Meaning Conveyance: Icon graphics accurately communicate functions, services, and branding, with readability and recognizability.
  3. Emotional Expression: Summarize emotional expression through graphics and colors to convey brand visual identity.

Top comments (0)