DEV Community

Cover image for Image Dragging, Animation Zoom, List Height, Back Button Listening, Divider Color
kouwei qing
kouwei qing

Posted on

Image Dragging, Animation Zoom, List Height, Back Button Listening, Divider Color

[Daily HarmonyOS Next Knowledge] Image Dragging, Animation Zoom, List Height, Back Button Listening, Divider Color

1. Can images in HarmonyOS Image components be dragged freely?

The Image component supports default dragging effects. When draggable is set to true, the component can be dragged. Refer to the document: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/image-V5

alt text
In HarmonyOS, the Image component natively supports long-press to trigger dragging events. Specifically, the dragging capability is achieved by setting the draggable attribute. By default, draggable is true for Text, TextInput, TextArea, Hyperlink, Image, and RichEditor components, meaning they can be long-pressed and dragged. You can customize Image-related components to disable dragging for specific cases.

2. Why can't animation zoom be achieved after switching to a model in HarmonyOS?

When ListItem content is an object, direct comparison with == for the current item is invalid. Use the index to determine the clicked item instead. Modify as follows: (For other cases, refer to: https://gitee.com/harmonyos-cases/cases/tree/master/CommonAppDevelopment/feature/listexchange)

import curves from '@ohos.curves';
@Entry
@Component
struct model {
  @State private arr: titleStrModel[] = []
  @State dragItem: titleStrModel = new titleStrModel()
  @State scaleItem: titleStrModel = new titleStrModel()
  @State neighborItem: titleStrModel = new titleStrModel()
  @State selectedIndex: number = -1
  @State neighborScale: number = -1
  private dragRefOffset: number = 0
  @State offsetX: number = 0
  @State offsetY: number = 0
  private ITEM_INTV: number = 120
  aboutToAppear(): void {
    const d = new titleStrModel()
    d.name = 'wewewe'
    const b = new titleStrModel()
    b.name = 'adadad'
    const h = new titleStrModel()
    h.name = 'dfdfdf'
    this.arr.push(d)
    this.arr.push(b)
    this.arr.push(h)
  }

  // Determine if the item should be scaled based on the current index
  scaleSelect(item: titleStrModel): void {
    this.selectedIndex = this.arr.indexOf(item)
    this.arr[this.selectedIndex].scale = 1.05
  }
  itemMove(index: number, newIndex: number): void {
    let tmp = this.arr.splice(index, 1)
    this.arr.splice(newIndex, 0, tmp[0])
  }
  build() {
    Stack() {
      List({ space: 20, initialIndex: 0 }) {
        ForEach(this.arr, (item: titleStrModel) => {
          ListItem() {
            Row() {
              Text('' + item.name)
                .width('100%')
                .height(100)
                .fontSize(16)
                .textAlign(TextAlign.Center)
                .borderRadius(10)
                .backgroundColor(0xFFFFFF)
              Image($r('app.media.app_icon'))
                .width(100)
                .height(100)
            }
            .shadow(this.scaleItem.name == item.name ? { radius: 70, color: Color.Blue, offsetX: 0, offsetY: 0 } :
              { radius: 0, color: Color.Blue, offsetX: 0, offsetY: 0 })
            .animation({ curve: Curve.Sharp, duration: 300 })
          }
          .margin({ left: 12, right: 12 })
          .scale({ x: item.scale, y: item.scale })
          .zIndex(this.dragItem.name == item.name ? 1 : 0)
          .translate(this.dragItem.name == item.name ? { y: this.offsetY } : { y: 0 })
          .gesture(

            // The following combined gesture is sequentially recognized; the drag gesture will not trigger if the long-press gesture fails
            GestureGroup(GestureMode.Sequence,
              LongPressGesture({ repeat: true })
                .onAction((event?: GestureEvent) => {
                  animateTo({ curve: Curve.Friction, duration: 300 }, () => {
                    this.scaleItem = item
                    this.scaleSelect(item)
                  })
                })
                .onActionEnd(() => {
                  animateTo({ curve: Curve.Friction, duration: 300 }, () => {
                    this.scaleItem = new titleStrModel()
                  })
                }),
              PanGesture({ fingers: 1, direction: null, distance: 0 })
                .onActionStart(() => {
                  this.dragItem = item
                  this.dragRefOffset = 0
                })
                .onActionUpdate((event: GestureEvent) => {
                  this.offsetY = event.offsetY - this.dragRefOffset
                  // console.log('Y:' + this.offsetY.toString())
                  this.neighborItem = new titleStrModel()
                  let index = this.arr.indexOf(item)
                  let curveValue = curves.initCurve(Curve.Sharp)
                  let value: number = 0
                  // Calculate the scale of adjacent items based on displacement
                  if (this.offsetY < 0) {
                    value = curveValue.interpolate(-this.offsetY / this.ITEM_INTV)
                    this.neighborItem = this.arr[index - 1]
                    this.neighborScale = 1 - value / 20;
                    console.log('neighborScale:' + this.neighborScale.toString())
                  } else if (this.offsetY > 0) {
                    value = curveValue.interpolate(this.offsetY / this.ITEM_INTV)
                    this.neighborItem = this.arr[index + 1]
                    this.neighborScale = 1 - value / 20;
                  }
                  // Swap排序 based on displacement
                  if (this.offsetY > this.ITEM_INTV / 2) {
                    animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
                      this.offsetY -= this.ITEM_INTV
                      this.dragRefOffset += this.ITEM_INTV
                      this.itemMove(index, index + 1)
                    })
                  } else if (this.offsetY < -this.ITEM_INTV / 2) {
                    animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
                      this.offsetY += this.ITEM_INTV
                      this.dragRefOffset -= this.ITEM_INTV
                      this.itemMove(index, index - 1)
                    })
                  }
                })
                .onActionEnd((event: GestureEvent) => {
                  animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
                    this.dragItem = new titleStrModel()
                    this.neighborItem = new titleStrModel()
                    // Restore the scale after the animation ends
                    this.arr[this.arr.indexOf(this.scaleItem)].scale = 1
                  })
                  animateTo({
                    curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150
                  }, () => {
                    this.scaleItem = new titleStrModel()
                  })
                })
            )
              .onCancel(() => {
                animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
                  this.dragItem = new titleStrModel()
                  this.neighborItem = new titleStrModel()
                })
                animateTo({
                  curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150
                }, () => {
                  this.scaleItem = new titleStrModel()
                })
              })
          )
        })
      }
    }.width('100%').height('100%').backgroundColor(0xDCDCDC).padding({ top: 5 })
  }
}
class titleStrModel {
  name: string = ''
  scale: number = 1 // Add the scale attribute, each item has a corresponding scale value
}
Enter fullscreen mode Exit fullscreen mode

3. Why doesn't setting List height to 100% display completely in HarmonyOS?

The issue is resolved by adding layoutWeight(1) to both the child Column and List. Here’s the explanation:

  1. Principle of layoutWeight: When the parent container's size is fixed, child elements with layoutWeight distribute the main-axis space proportionally based on their weights. For example, three elements with layoutWeight(1) each will divide the parent container's main-axis space in a 1:1:1 ratio, similar to .width('33%'), .width('34%'), .width('33%').

  2. Tabs behavior: Tabs by default occupy the full screen height, making explicit height calculations (e.g., TNAppUIData.getPageHeight()) ineffective as they override the computed height.

4. How to listen for physical back button presses in HarmonyOS?

When a CustomDialog is open, pressing the physical back button should not close the dialog. Use onBackPress, which triggers when the back button is clicked and only works for @Entry-decorated custom components. Return true to handle the back logic locally without routing; return false (or no return value) to use default routing. Refer to: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-custom-component-lifecycle-V5#onbackpress

onBackPress?(): void | boolean

Example:

// xxx.ets
@Entry
@Component
struct IndexComponent {
  @State textColor: Color = Color.Black;

  onPageShow() {
    this.textColor = Color.Blue;
    console.info('IndexComponent onPageShow');
  }

  onPageHide() {
    this.textColor = Color.Transparent;
    console.info('IndexComponent onPageHide');
  }

  onBackPress() {
    this.textColor = Color.Red;
    console.info('IndexComponent onBackPress');
    return true; // Handle back logic locally, prevent default routing
  }

  build() {
    Column() {
      Text('Hello World')
        .fontColor(this.textColor)
        .fontSize(30)
        .margin(30)
    }.width('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

5. How to match the color before startMargin in List divider with ListItem in HarmonyOS?

The built-in divider attribute of List cannot meet this requirement. Customize the divider as follows:

build() {
  Row() {
    List() {
      ListItem() {
        Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween }) {
          Text('test')
          Divider()
            .color(Color.Red)
            .strokeWidth(5)
            .margin({ left: 50 })
        }
      }.backgroundColor(Color.Blue)
      .height(100)
      ListItem() {
        Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween }) {
          Text('test')
          Divider()
            .color(Color.Red)
            .strokeWidth(5)
            .margin({ left: 50, bottom: 0 })
        }
      }.backgroundColor(Color.Orange)
      .height(100)
      ListItem() {
        Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween }) {
          Text('test')
          Divider()
            .color(Color.Red)
            .strokeWidth(5)
            .margin({ left: 50 })
        }
      }.backgroundColor(Color.Yellow)
      .height(100)
    }
    .width('100%').height('100%')
    .backgroundColor(Color.Gray)
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)