DEV Community

HarmonyOS
HarmonyOS

Posted on

Understanding ArkTS ArcList: The Go-To Component for Watch UIs

Read the original article:Understanding ArkTS ArcList: The Go-To Component for Watch UIs

1_EYTem96x460nzLrbIpl2FA.png

ArcList first Look

📘 Introduction

Smartwatch apps are rapidly evolving, demanding fluid, visually intuitive UIs adapted for small, circular screens. HarmonyOS offers developers an elegant way to build such interfaces through the ArkTS framework and its ArcUI components. One of the most efficient components designed specifically for this purpose is ArcList. It stands out as a key element in wearable UI design, simplifying list rendering in circular layouts while improving user interaction.

In this article, we’ll explore what ArcList is, how it works, and why it’s essential for wearable UI development in ArkTS.

📋 What is ArcList?

ArcList is a circular list container provided by ArkTS’s ArkUI framework. It is optimized for wearable devices, especially those with round displays. Unlike traditional vertical or horizontal lists, ArcList arranges its child items along an arc, providing a natural and intuitive scroll experience on smartwatches.

⌚ Why Use ArcList for Wearables?

Smartwatches require unique interaction patterns due to limited screen space and curved displays. ArcList offers several benefits:

  • Optimized Layout: Arranges items along a visible arc.
  • Focus & Highlight: Automatically centers and highlights the focused item.
  • Smooth Interaction: Supports rotational input and smooth scroll gestures.
  • Flexible Item Management: Works with both static and dynamic content.

💡 Basic Syntax

import {
  ArcList,
  ArcListItem,
  ComponentContent,
  ArcListAttribute,
  ArcListItemAttribute,
  LengthMetrics
} from '@kit.ArkUI'

@Builder
function buildText() {
  Column() {
    Text('Header')
      .fontSize('60px')
      .fontWeight(FontWeight.Bold)
      .fontColor(Color.White)
  }.margin(0)
}

@Entry
@Component
struct Index {
  private watchSize: string = '466px' // Default watch size: 466*466
  private listSize: string = '414px' // Item width
  context: UIContext = this.getUIContext()
  header: ComponentContent<Object> = new ComponentContent(this.context, wrapBuilder(buildText));

  @Builder
  ListCardItem() {
    ArcListItem() {
      Button({ type: ButtonType.Capsule }) {
        Column({ space: 2 }) {
          Text('Title')
            .fontSize('30px')
            .fontColor(Color.White)
          Text('Subtitle')
            .fontSize('20px')
            .fontColor(Color.White)
        }
        .width('100%')
        .padding('8px')
      }
      .width(this.listSize)
      .height('100px')
      .focusable(true)
      .focusOnTouch(true)
      .backgroundColor('#0B798B')
      .onClick(() => {
      })
    }.align(Alignment.Center)
  }

  build() {
    ArcList({ initialIndex: 0, header: this.header }) {
      this.ListCardItem()
      this.ListCardItem()
      this.ListCardItem()
      this.ListCardItem()
      this.ListCardItem()
      this.ListCardItem()
    }
    .height('100%')
    .width('100%')
    .backgroundColor('#004C5D')
    .space(LengthMetrics.px(10))
    .borderRadius(this.watchSize)
    .focusable(true)
    .focusOnTouch(true)
    .defaultFocus(true)
  }
}
Enter fullscreen mode Exit fullscreen mode

Basic usage of ArcList with static list-items

1_IWm89qXPjtkHl7_7p-haHg.gif

Basic Preview of ArcList, direct items

🛠️ Tips for Effective Usage

  • Use ArcList directly in the build() function of your component, or place it inside a Stack or any layout container that supports alignment.
  • Use ArcListItem as a direct child of ArcList, or within ForEach or LazyForEach for dynamic lists.
  • If your list items are intended to be interactive, wrap them with the Button component and set the type to ButtonType.Capsule for a visually appropriate style.
  • Style the currently focused item differently (e.g., larger size, highlight color) to improve clarity and navigation feedback.

✅ Use Case: Static Settings Check-List

import {
  ArcList,
  ArcListItem,
  ComponentContent,
  ArcListAttribute,
  ArcListItemAttribute,
  LengthMetrics
} from '@kit.ArkUI'

@Builder
function buildText() {
  Column() {
    Text('Settings')
      .fontSize('60px')
      .fontWeight(FontWeight.Bold)
      .fontColor(Color.White)
  }.margin(0)
}

@Entry
@Component
struct Index {
  @State private numItems: number[] = [0, 1, 2, 3, 4, 6, 7, 8, 9];
  @State private numItemsFlags: boolean[] = [false, false, false, false, false, false, false, false, false, false];
  private watchSize: string = '466px' // Default watch size: 466*466
  private listSize: string = '414px' // Item width
  context: UIContext = this.getUIContext()
  header: ComponentContent<Object> = new ComponentContent(this.context, wrapBuilder(buildText));

  @Builder
  ListCardItem(index: number, num: number) {
    ArcListItem() {
      Button({ type: ButtonType.Capsule }) {
        Row() {
          Checkbox({ name: `check_${index}` })
            .focusable(false)
            .select(this.numItemsFlags[index])
          Text(`Setting ${num}`)
            .fontSize('30px')
            .fontColor(Color.White)
        }
        .width('100%')
        .padding('8px')
      }
      .width(this.listSize)
      .height('100px')
      .focusable(true)
      .focusOnTouch(true)
      .backgroundColor('#0B798B')
      .onClick(() => {
        this.numItemsFlags[index] = !this.numItemsFlags[index];
      })
    }.align(Alignment.Center)
  }

  build() {
    ArcList({ initialIndex: 0, header: this.header }) {
      ForEach(this.numItems, (item: number, index: number) => {
        this.ListCardItem(index, item)
      }, (item: string, index: number) => `item_${item}_${index}`)
    }
    .height('100%')
    .width('100%')
    .backgroundColor('#004C5D')
    .space(LengthMetrics.px(10))
    .borderRadius(this.watchSize)
    .focusable(true)
    .focusOnTouch(true)
    .defaultFocus(true)
  }
}
Enter fullscreen mode Exit fullscreen mode

Static usage of ArcList with List items

1_AdjRhRUYyQZwDHBIlHiCMQ.gif

Static Preview of ArcList, list-items

🔄 Use Case: Dynamic Settings Check-List

To use LazyForEach, a data source class is required to define how the list handles operations such as add, remove, and move.

In this example, we’ll create a simple data source class to demonstrate its basic usage:

export class NumsDataSource implements IDataSource {
  private dataArray: number[] = [];
  private listeners: DataChangeListener[] = [];

  constructor(element: number[]) {
    for (let index = 0; index < element.length; index++) {
      this.dataArray.push(element[index]);
    }
  }

  public totalCount(): number {
    return this.dataArray.length;
  }

  public getData(index: number): number {
    return this.dataArray[index];
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      this.listeners.push(listener);
    }
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      this.listeners.splice(pos, 1);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Created NumsDataSource to be used as a LazyForEach's DataSource

🧩 Updating index.ets for Dynamic Content

To maintain the same static content list but render it dynamically, you can update the index.ets file using LazyForEach as shown below:

import {
  ArcList,
  ArcListItem,
  ComponentContent,
  ArcListAttribute,
  ArcListItemAttribute,
  LengthMetrics
} from '@kit.ArkUI'
import { NumsDataSource } from './test3';

@Builder
function buildText() {
  Column() {
    Text('Settings')
      .fontSize('60px')
      .fontWeight(FontWeight.Bold)
      .fontColor(Color.White)
  }.margin(0)
}

@Entry
@Component
struct Index {
  private numItems: number[] = [0, 1, 2, 3, 4, 6, 7, 8, 9];
  @State private numItemsFlags: boolean[] = [false, false, false, false, false, false, false, false, false, false];
  private watchSize: string = '466px' // Default watch size: 466*466
  private listSize: string = '414px' // Item width
  context: UIContext = this.getUIContext()
  header: ComponentContent<Object> = new ComponentContent(this.context, wrapBuilder(buildText));
  private numsDataSource: NumsDataSource = new NumsDataSource(this.numItems);

  @Builder
  ListCardItem(index: number, num: number) {
    ArcListItem() {
      Button({ type: ButtonType.Capsule }) {
        Row() {
          Checkbox({ name: `check_${index}` })
            .focusable(false)
            .select(this.numItemsFlags[index])
          Text(`Setting ${num}`)
            .fontSize('30px')
            .fontColor(Color.White)
        }
        .width('100%')
        .padding('8px')
      }
      .width(this.listSize)
      .height('100px')
      .focusable(true)
      .focusOnTouch(true)
      .backgroundColor('#0B798B')
      .onClick(() => {
        this.numItemsFlags[index] = !this.numItemsFlags[index];
      })
    }.align(Alignment.Center)
  }

  build() {
    ArcList({ initialIndex: 0, header: this.header }) {
      LazyForEach(this.numsDataSource, (item: number, index: number) => {
        this.ListCardItem(index, item)
      }, (item: string, index: number) => `item_${item}_${index}`)
    }
    .height('100%')
    .width('100%')
    .backgroundColor('#004C5D')
    .space(LengthMetrics.px(10))
    .borderRadius(this.watchSize)
    .focusable(true)
    .focusOnTouch(true)
    .defaultFocus(true)
  }
}
Enter fullscreen mode Exit fullscreen mode
Dynamic Preview of ArcList, list-items

🎯 Result: Dynamic List Output

After updating the list to use LazyForEach, the visual result and behavior remain unchanged compared to the static list. When you run the app, the list appears and functions the same, but now supports dynamic operations such as adding or removing items.

✨ Conclusion

ArcList is a must-know component for any HarmonyOS wearable developer. It not only simplifies list handling on circular screens but also provides a smooth, user-friendly experience tailored for the wrist. Whether you’re building a to-do app, media selector, or quick settings interface, ArcList should be your go-to choice.

📚 References

Written by Bilal Basboz

Top comments (0)