DEV Community

HarmonyOS
HarmonyOS

Posted on

Fast Navigation Guide : ArcAlphabetIndexer in HarmonyOS Next Using ArkTS and ArkUI

Read the original article:Fast Navigation Guide : ArcAlphabetIndexer in HarmonyOS Next Using ArkTS and ArkUI

Hi, in this guide, you’ll learn how to use ArcAlphabetIndexer in HarmonyOS Next with ArkTS and ArkUI to build fast and circular-friendly navigation in your wearable apps.

📘 Introduction

Imagine you’re building a vocabulary or contacts app for a smartwatch. Users are swiping endlessly to find “Zebra” or “Tom.” Wouldn’t it be better if they could just tap a letter and jump there instantly?

That’s where ArcAlphabetIndexer comes in.
This component allows lightning-fast navigation through alphabetically sorted lists on circular screens, like HarmonyOS-powered smartwatches.

In this guide, you’ll learn how to:

  • Build and sort a vocabulary list
  • Create an index mapping for letters
  • Connect an ArcList with ArcAlphabetIndexer
  • Keep scrolling and selection in sync
  • Optimize for circular UI/UX

This tutorial uses ArkTS and ArkUI, targets HarmonyOS Next (API version 18+).

🧩 What is ArcAlphabetIndexer?

ArcAlphabetIndexer is a specialized circular component that shows all letters (A–Z and #) around the edge of a round screen. Users can select a letter to instantly scroll a list to the matching section.

Key Features

  • Optimized for circular UIs
  • Alphabetical fast-jumping
  • Synchronizes with list scroll position
  • Works great with ArcList

⚙️ How to Implement ArcAlphabetIndexer Step by Step

🔤 1. Create the Word List

We start by defining a vocabulary list, where each item contains a word and its meaning.

interface Word {
  text: string;
  meaning: string;
}

private rawWords: Word[] = [
  { text: "Apple", meaning: "A red or green fruit" },
  { text: "Zebra", meaning: "A striped animal" },
  { text: "Ball", meaning: "A round object" },
  ...
];
Enter fullscreen mode Exit fullscreen mode

🔠 2. Sort the List and Build Letter Index Map

Next, we sort the list alphabetically and build a map to track the first appearance of each letter.

sortAndGroupWords() {
    this.sortedWords = this.rawWords.sort((a, b) => {
      return a.text.localeCompare(b.text);
    });

    this.letterIndexMap.clear();
    let currentLetter = '';

    this.sortedWords.forEach((word, index) => {
      const firstLetter = this.getFirstLetter(word.text);
      if (firstLetter !== currentLetter) {
        currentLetter = firstLetter;
        this.letterIndexMap.set(firstLetter, index);
      }
    });
  }
Enter fullscreen mode Exit fullscreen mode

📜 3. Display Words Using ArcList and Track Scrolling

Use ArcList to display the words. We also detect the center item while scrolling, and update the selected letter in ArcAlphabetIndexer accordingly.

ArcList({ scroller: this.scrollerForList, initialIndex: 0, header: this.header }) {
              ForEach(this.sortedWords, (word: Word, index: number) => {
                ArcListItem() {
                    …
                  }
                  …
                }
              })
            }
            .onScrollIndex((firstIndex: number, lastIndex: number, centerIndex: number) => {
              if (centerIndex < this.sortedWords.length) {
                const currentWord = this.sortedWords[centerIndex];
                const letter = this.getFirstLetter(currentWord.text);
                const letterIndex = this.fullValue.indexOf(letter);
                if (letterIndex !== -1) {
                  this.indexerIndex = letterIndex;
                }
              }
            })
            …
Enter fullscreen mode Exit fullscreen mode

🔁 4. Enable Fast Navigation with ArcAlphabetIndexer

Here we wire up the ArcAlphabetIndexer to scroll the list to the correct item when a letter is selected.

ArcAlphabetIndexer({ arrayValue: this.fullValue, selected: 0 })
              .onSelect((index: number) => {
                this.indexerIndex = index;
                const selectedLetter = this.fullValue[index];
                if (selectedLetter !== '#' && this.letterIndexMap.has(selectedLetter)) {
                  const wordIndex = this.letterIndexMap.get(selectedLetter);
                  if (wordIndex !== undefined) {
                    this.scrollerForList.scrollToIndex(wordIndex);
                  }
                }
              })
                    …
Enter fullscreen mode Exit fullscreen mode

📌 When the user taps “M”, the list instantly jumps to the first “M” word. Smooth and fast!

💻 5. Full Code Example

import {
  LengthMetrics,
  ColorMetrics,
  ArcList,
  ArcListItem,
  ArcListAttribute,
  ArcAlphabetIndexer,
  ArcAlphabetIndexerAttribute,
  ComponentContent
} from '@kit.ArkUI';

interface Word {
  text: string;
  meaning: string;
}

@Builder
function buildText() {
  Column() {
    Text('Word List')
      .fontSize(26)
      .fontWeight(FontWeight.Bold)
      .fontColor(Color.White)
  }.margin(6)
}

@Entry
@Component
struct Index {
  private fullValue: string[] = [
    '#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
    'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
  ];

  private rawWords: Word[] = [
    { text: "Apple", meaning: "A red or green fruit" },
    { text: "Jump", meaning: "To leap up" },
    { text: "Zebra", meaning: "A striped animal" },
    { text: "Fan", meaning: "Cools the air" },
    { text: "X-ray", meaning: "Used to see bones" },
    { text: "Desk", meaning: "A table for work or study" },
    { text: "Orange", meaning: "A citrus fruit" },
    { text: "Train", meaning: "Runs on tracks" },
    { text: "Rain", meaning: "Water from sky" },
    { text: "Lion", meaning: "King of jungle" },
    { text: "Mouse", meaning: "A small rodent" },
    { text: "Kite", meaning: "Flies in the wind" },
    { text: "Unicorn", meaning: "Mythical horse" },
    { text: "Queen", meaning: "Female ruler" },
    { text: "Whale", meaning: "Large sea animal" },
    { text: "Car", meaning: "A vehicle with wheels" },
    { text: "Needle", meaning: "Used in sewing" },
    { text: "Egg", meaning: "Laid by birds" },
    { text: "Xylophone", meaning: "A musical instrument" },
    { text: "Guitar", meaning: "A musical instrument" },
    { text: "Ball", meaning: "A round object for playing" },
    { text: "Violin", meaning: "A string instrument" },
    { text: "Book", meaning: "Something you read" },
    { text: "Lamp", meaning: "Gives light" },
    { text: "Van", meaning: "A type of vehicle" },
    { text: "Yellow", meaning: "A bright color" },
    { text: "Dog", meaning: "A loyal pet animal" },
    { text: "Queen", meaning: "Female ruler" },
    { text: "Jelly", meaning: "A soft sweet food" },
    { text: "House", meaning: "A place to live" },
    { text: "Igloo", meaning: "A snow house" },
    { text: "Zoo", meaning: "Place for animals" },
    { text: "Ice", meaning: "Frozen water" },
    { text: "Moon", meaning: "Earth's satellite" },
    { text: "Pen", meaning: "Used to write" },
    { text: "Quilt", meaning: "A warm bedcover" },
    { text: "Cat", meaning: "A small pet animal" },
    { text: "Nest", meaning: "A bird's home" },
    { text: "Key", meaning: "Opens doors" },
    { text: "Ant", meaning: "A small insect" },
    { text: "Yacht", meaning: "A luxury boat" },
    { text: "Piano", meaning: "A musical instrument" },
    { text: "Elephant", meaning: "A large grey animal" },
    { text: "Game", meaning: "Something you play" },
    { text: "Hat", meaning: "Worn on the head" },
    { text: "Oven", meaning: "Used for baking" },
    { text: "Sun", meaning: "Shines in the sky" },
    { text: "Water", meaning: "Essential liquid" },
    { text: "Jacket", meaning: "Worn for warmth" },
    { text: "Robot", meaning: "A programmable machine" }
  ]

  @State sortedWords: Word[] = [];
  @State letterIndexMap: Map<string, number> = new Map();
  private scrollerForList: Scroller = new Scroller();
  @State indexerIndex: number = 0;
  @State selectedWord: Word | null = null;
  private watchSize: string = '466px';
  private itemSize: number = 16;
  context: UIContext = this.getUIContext()
  header: ComponentContent<Object> = new ComponentContent(this.context, wrapBuilder(buildText));

  aboutToAppear() {
    this.sortAndGroupWords();
  }

  getFirstLetter(word: string): string {
    return word.charAt(0).toUpperCase();
  }

  sortAndGroupWords() {
    this.sortedWords = this.rawWords.sort((a, b) => {
      return a.text.localeCompare(b.text);
    });

    this.letterIndexMap.clear();
    let currentLetter = '';

    this.sortedWords.forEach((word, index) => {
      const firstLetter = this.getFirstLetter(word.text);
      if (firstLetter !== currentLetter) {
        currentLetter = firstLetter;
        this.letterIndexMap.set(firstLetter, index);
      }
    });
  }

  build() {
      Column() {
        Row() {
          Stack() {
            ArcList({ scroller: this.scrollerForList, initialIndex: 0, header: this.header }) {
              ForEach(this.sortedWords, (word: Word, index: number) => {
                ArcListItem() {
                  Column({space: 6}) {
                    Text(word.text)
                      .fontSize(18)
                      .fontWeight(FontWeight.Bold)
                      .textAlign(TextAlign.Center)
                      .fontColor("#FF0B1F3B")

                    Text(word.meaning)
                      .fontSize(12)
                      .fontColor(Color.Gray)
                      .textAlign(TextAlign.Center)
                      .maxLines(2)
                      .textOverflow({ overflow: TextOverflow.Ellipsis })
                      .fontColor(Color.Gray)
                  }
                  .backgroundColor(Color.White)
                  .width('90%')
                  .height(50)
                  .padding(8)
                  .borderRadius(23)
                  .justifyContent(FlexAlign.Center)
                  .onClick(() => {
                    this.selectedWord = word;
                  })
                }
              })
            }
            .scrollBar(BarState.Off)
            .onScrollIndex((firstIndex: number, lastIndex: number, centerIndex: number) => {
              if (centerIndex < this.sortedWords.length) {
                const currentWord = this.sortedWords[centerIndex];
                const letter = this.getFirstLetter(currentWord.text);
                const letterIndex = this.fullValue.indexOf(letter);
                if (letterIndex !== -1) {
                  this.indexerIndex = letterIndex;
                }
              }
            })
            .borderWidth(1)
            .width(this.watchSize)
            .height(this.watchSize)
            .borderRadius(this.watchSize)
            .space(LengthMetrics.px(4))

            ArcAlphabetIndexer({ arrayValue: this.fullValue, selected: 0 })
              .autoCollapse(true)
              .width(this.watchSize)
              .height(this.watchSize)
              .usePopup(false)
              .selected(this.indexerIndex)
              .onSelect((index: number) => {
                this.indexerIndex = index;
                const selectedLetter = this.fullValue[index];
                if (selectedLetter !== '#' && this.letterIndexMap.has(selectedLetter)) {
                  const wordIndex = this.letterIndexMap.get(selectedLetter);
                  if (wordIndex !== undefined) {
                    this.scrollerForList.scrollToIndex(wordIndex);
                  }
                }
              })
              .borderWidth(1)
              .hitTestBehavior(HitTestMode.Transparent)
              .selectedColor(ColorMetrics.resourceColor(0xFFFFFF))
              .selectedBackgroundColor(ColorMetrics.resourceColor(0x1F71FF))
              .color(ColorMetrics.resourceColor(0xFFFFFF))
              .popupColor(ColorMetrics.resourceColor(0xFFFFFF))
              .popupBackground(ColorMetrics.resourceColor(0xD8404040))
              .itemSize(LengthMetrics.px(this.itemSize))
              .selectedFont({
                size: '11.0fp',
                style: FontStyle.Normal,
                weight: 500,
                family: 'HarmonyOS Sans'
              })
              .font({
                size: '11.0fp',
                style: FontStyle.Normal,
                weight: 500,
                family: 'HarmonyOS Sans'
              })
          }
          .width('100%')
          .height('100%')
        }
        .width('100%')
        .height('100%')
      }
      .width('100%')
      .height('100%')
      .linearGradient({
        direction: GradientDirection.Bottom,
        colors:
        [
          ["#FF0B1F3B", 0.0],
          ["#FF00050A", 1],
        ]
      })
  }
}
Enter fullscreen mode Exit fullscreen mode

🧠 Tips and Warnings

Use short words to avoid layout overflow on round screens.

Sort list alphabetically before using the indexer.

Use ArcList and ArcAlphabetIndexer inside a Stack to align them properly.

⚠️ Avoid overcrowding the UI with too many elements.

⚠️ Works only on API 18+ and circular devices.

✅ Conclusion

ArcAlphabetIndexer gives your wearable apps a professional, responsive, and elegant navigation experience — just like scrolling through contacts or vocabulary with ease.

This component is lightweight but powerful, especially when combined with ArcList.

🚀 Try it in your next HarmonyOS Next wearable project and elevate your UX!

📚 References

For more information about ArcList, here is an article written by my teammate (Bilal Basboz) : ArcList
For more information about ArcAlphabetIndexer, please visit the link : ArcAlphabetIndexer

Written by Sefa Koyuncu

Top comments (0)