DEV Community

HarmonyOS
HarmonyOS

Posted on

Swipe-to-Delete with ArkUI on HarmonyOS Next: Intuitive UX for Wearable Devices

Read the original article:Swipe-to-Delete with ArkUI on HarmonyOS Next: Intuitive UX for Wearable Devices

Introduction

Developing for wearable technology on HarmonyOS Next is an exhilarating journey, yet it comes with its unique set of challenges. On compact screens like those of smartwatches, user experience (UX) becomes the most critical factor. Every pixel and every interaction holds immense importance, making intuitive and efficient design paramount. Leveraging the flexibility offered by ArkUI, we're committed to overcoming these challenges and delivering the best possible user experience.

Today, I'll share how we implemented a fluid and user-friendly “swipe-to-delete” feature using ArkUI for wearable devices, specifically illustrating it through a comment list scenario. This interaction significantly simplifies managing listed items on small screens.

Why Swipe-to-Delete on Wearables?

Given the limited screen real estate of smartwatches, performing actions like deleting an item across multiple menu steps can be quite cumbersome for users. Traditional “select and delete” approaches can strain finger dexterity in confined spaces. This is precisely where the swipe-to-delete gesture comes to our rescue. When a user swipes a list item to the left, a hidden action button, like a trash can icon, is revealed. This makes the operation both intuitive and manageable with a single hand. It allows users to complete tasks faster, with less effort, and with fewer errors.

We implemented this feature within an ArkList component, which displays a list of user comments. Each comment is rendered as a CommentCard, enabling users to easily swipe a desired comment to initiate the deletion process.

Technical Deep Dive: Implementing Swipe-to-Delete with ArkUI

First, we have a straightforward model defining the structure of our comment data:

export class CommentModel {
  id: number;
  personPp: Resource;
  personName: string;
  comment: string;

  constructor(id: number, personPp: Resource, personName: string, comment: string) {
    this.id = id;
    this.personPp = personPp;
    this.personName = personName;
    this.comment = comment;
  }
}
Enter fullscreen mode Exit fullscreen mode

This is where the magic happens. The CommentCard component displays the comment content and manages the swipe gesture:

import { CommentModel } from "./CommentModel";

@Component
export default struct CommentCard {
  @Prop commentItem: CommentModel;
  @State private offsetX: number = 0; // Horizontal offset of the card
  @State private isDeleting: boolean = false; // Controls the deletion animation
  private deleteButtonWidth: number = 80; // Width of the delete button
  @State private showDeleteButton: boolean = false; // Visibility of the delete button

  build() {
    Stack() {
      // #1: Delete Button Layer (Background)
      Row() {
        if (this.showDeleteButton) { // Render only when the button is visible
          Button({ type: ButtonType.Circle }) {
            Image($r('app.media.trash')) // Trash can icon
              .width(24).height(24)
              .fillColor(Color.White)
          }
          .width(50).height(50)
          .backgroundColor(Color.Red)
          .margin({ right: 15 })
          .onClick(() => {
            this.isDeleting = true; // Start the deletion animation
            this.showDeleteButton = false; // Hide the button again
            // In a real application, a service call to delete the comment would be made here.
          })
        }
      }
      .width('100%').height('100%')
      .justifyContent(FlexAlign.End) // Align to the right
      .backgroundColor(Color.Transparent)
      .borderRadius(10)

      // #2: Comment Content Card Layer (Swipable Part)
      Row() {
        Image(this.commentItem.personPp)
          .width(30).height(30)
          .borderRadius(20)
          .objectFit(ImageFit.Cover)

        Column() {
          Text(this.commentItem.personName)
            .fontColor(Color.Black)
            .fontWeight(FontWeight.Bold)
            .textAlign(TextAlign.Start)

          // Comment text with Marquee feature
          Text(this.commentItem.comment)
            .textAlign(TextAlign.Start)
            .width('80%')
            .textOverflow({ overflow: TextOverflow.MARQUEE }) // Scrolling text if it overflows
            .marqueeOptions({
              start: true,
              fromStart: true,
              step: 4,
              loop: -1,
              delay: 0,
              fadeout: false,
              marqueeStartPolicy: MarqueeStartPolicy.DEFAULT
            })
        }
        .alignItems(HorizontalAlign.Start)
        .margin({ left: 3 })
        .justifyContent(FlexAlign.Center)
      }
      .width('100%').height('100%')
      .padding({ left: 5, right: 5 })
      .backgroundColor('#f0bcbcbc') // Light gray background
      .borderRadius(10)
      .alignItems(VerticalAlign.Center)
      .offset({ x: this.offsetX }) // Positions the card based on swipe amount
      .gesture(
        PanGesture({ direction: PanDirection.Horizontal }) // Horizontal pan gesture recognizer
          .onActionStart((event: GestureEvent) => { /* Optional start actions */ })
          .onActionUpdate((event: GestureEvent) => {
            // Allow only leftward swiping (offsetX must be negative)
            if (event.offsetX < 0) {
              this.offsetX = event.offsetX;
            } else {
              this.offsetX = 0; // Prevent rightward swiping
            }
          })
          .onActionEnd((event: GestureEvent) => {
            // When swipe ends: If swiped enough, show the button
            if (this.offsetX < -this.deleteButtonWidth / 2) {
              this.offsetX = -this.deleteButtonWidth; // Ensure button is fully visible
              this.showDeleteButton = true;
            } else {
              this.offsetX = 0; // Return to original position
              this.showDeleteButton = false;
            }
          })
      )
      .transition({ type: TransitionType.Delete }) // Animation when element is deleted
      .opacity(this.isDeleting ? 0 : 1) // Opacity animation during deletion
    }
    .width('90%').height('20%') // Card height
    .margin({ bottom: 5 }) // Bottom margin
  }
}
Enter fullscreen mode Exit fullscreen mode

In our main page, we use the CommentCard within an ArcList optimized for circular wearable screens:

import { CommentModel } from './CommentModel';
import CommentCard from './CommentCard'; // Import without curly braces for default export
import { ArcList, ArcListItem, LengthMetrics } from '@kit.ArkUI'; // Necessary ArkUI components

@Entry
@Component
struct Index {
  @State private comments: CommentModel[] = []; // List of comments

  // Populate with sample comment data when the page loads
  onPageShow() {
    this.comments = [
      new CommentModel(1, $r('app.media.profile_picture_1'), 'Bob Johnson', 'Great post, well done!'),
      new CommentModel(2, $r('app.media.profile_picture_2'), 'Alice Smith', 'Very informative, thank you.'),
      new CommentModel(3, $r('app.media.profile_picture_3'), 'Chloe Davis', 'UX is everything in wearables!'),
      new CommentModel(4, $r('app.media.profile_picture_1'), 'Bob Johnson', 'Another great comment here.'),
      new CommentModel(5, $r('app.media.profile_picture_2'), 'Alice Smith', 'Learning a lot from this.'),
      new CommentModel(6, $r('app.media.profile_picture_3'), 'Chloe Davis', 'This feature will be so useful.'),
    ];
  }

  build() {
    Column() {
      // Render comment cards within ArcList
      ArcList({ initialIndex: 0 }) {
        ForEach(this.comments, (item: CommentModel, index: number) => {
          ArcListItem() {
            CommentCard({
              commentItem: item
            })
          }.align(Alignment.Center)
        }, (item: CommentModel, index: number) => {
          return index + '__' + JSON.stringify(item); // Generate unique key
        })
      }
      .space(LengthMetrics.px(10)) // Space between items
      .focusable(true)
      .focusOnTouch(true)
      .defaultFocus(true)
    }
    .width('100%').height('100%')
    .justifyContent(FlexAlign.Start)
    .alignItems(HorizontalAlign.Center)
  }
}
Enter fullscreen mode Exit fullscreen mode

Code Anatomy: A Detailed Look

1) offsetX and showDeleteButton States

  • offsetX: Tracks how much the comment card has been horizontally swiped.
  • showDeleteButton: Controls whether the delete button is visible.

2) Swipe Detection with PanGesture

  • PanGesture: Allows listening for horizontal swipe gestures.
  • onActionUpdate: Only allows leftward swiping.
  • onActionEnd: Shows or hides the delete button based on swipe distance.

3) Delete Button Interaction

  • When clicked, the button triggers a fade-out animation and could call a service to delete the comment.

4) Animation with transition and opacity

  • transition: Smooth animation when component is removed.
  • opacity: Fade-out effect on delete.

1. — showDeleteButton: Controls whether the delete button is visible.

2. Swipe Detection with PanGesture:
— PanGesture: Allows listening for horizontal swipe gestures.
— onActionUpdate: Only allows leftward swiping.
— onActionEnd: Shows or hides the delete button based on swipe distance.

3. Delete Button Interaction:
— When clicked, the button triggers a fade-out animation and could call a service to delete the comment.

4. Animation with transition and opacity:
— transition: Smooth animation when component is removed.
— opacity: Fade-out effect on delete.

Marquee Text for Wearable User Experience

Displaying long text on narrow screens can be challenging. Using textOverflow({ overflow: TextOverflow.MARQUEE }) automatically scrolls the text if it doesn't fit. This improves readability and enhances user experience on small screens.

Output

Conclusion
HarmonyOS Next and ArkUI provide powerful tools for creating rich and intuitive user experiences on wearable devices. Simple yet effective interactions like “swipe-to-delete” help users complete tasks faster and more naturally on compact screens. Such details significantly boost an application's usability and overall user satisfaction.

This example serves not just for comment deletion, but also as a starting point for triggering various actions on wearables using similar swipe interactions. When developing with HarmonyOS Next, consider leveraging such creative solutions to overcome UX challenges.

Written by Muhammet Ali Ilgaz

Top comments (0)