HarmonyOSにおけるフォーム操作向けUIコンポーネント(Button・Radio・Toggle)の実装ガイド

コンポーネントの役割と選定基準

HarmonyOSの宣言的UIフレームワーク(ArkUI)において、フォーム操作やユーザーインタラクションを実装する際には、ButtonRadioToggleの3つのコンポーネントが中核を担います。外見や振る舞いが類似している場合もありますが、状態管理の仕組みと適用シーンが明確に異なります。適切な場面で適切なコンポーネントを選定することが、保守性の高いUI構築の第一歩となります。

コンポーネント 主な役割 技術的特徴 代表的なユースケース
Button アクションの実行・送信 テキスト/アイコン埋め込み、4種の形状、クリックエフェクト制御 フォーム送信、画面遷移、ダイアログの確定/キャンセル
Radio 複数選択肢からの単一選択 group属性による排他制御、インジケーターのカスタマイズ可能 性別選択、配送方法、動作モードの切り替え
Toggle 2値状態のオン/オフ切り替え Switch/Checkbox/Buttonの3形態、状態の保持とバインディング Wi-Fi/Bluetoothの有効化、利用規約への同意、テーマ切り替え

プロジェクト構成とナビゲーション画面

検証用のプロジェクトでは、各コンポーネントの動作を確認するための専用ページへ遷移するルート画面を用意します。@kit.ArkUIrouterモジュールを用いた画面遷移の基礎パターンを示します。

import { router } from '@kit.ArkUI';

interface NavRoute {
  label: string;
  path: string;
}

@Entry
@Component
struct MainNavigator {
  private navItems: NavRoute[] = [
    { label: 'Buttonコンポーネント検証', path: 'pages/ButtonSample' },
    { label: 'Radioコンポーネント検証', path: 'pages/RadioSample' },
    { label: 'Toggleコンポーネント検証', path: 'pages/ToggleSample' }
  ];

  build() {
    Column({ space: 16 }) {
      Text('フォーム操作UIコンポーネント集')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 30 })
        .textAlign(TextAlign.Center);

      ForEach(this.navItems, (route: NavRoute) => {
        Button(route.label)
          .width('85%')
          .height(48)
          .backgroundColor($r('sys.color.brand'))
          .fontColor(Color.White)
          .fontSize(15)
          .borderRadius(10)
          .onClick(() => {
            router.pushUrl({ url: route.path });
          });
      }, (route: NavRoute) => route.path);
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#F9FAFB')
    .padding(24);
  }
}

Buttonコンポーネントの実装

Buttonはユーザーの操作を受け付け、特定の処理をトリガーする際に使用します。形状はButtonType列挙型で制御し、enabled属性で操作の可否を、stateEffectで押下時の視覚フィードバックを管理できます。また、子コンポーネントを1つだけ内包できるため、アイコンとテキストを併記したボタンも容易に実装可能です。

import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct ButtonSamplePage {
  @State isBtnLocked: boolean = false;

  build() {
    Scroll() {
      Column({ space: 18 }) {
        Text('Buttonの形状と状態制御')
          .fontSize(20)
          .fontWeight(FontWeight.Medium)
          .alignSelf(ItemAlign.Start);

        // デフォルトのカプセル型
        Button('カプセル型(デフォルト)', { type: ButtonType.Capsule })
          .width('85%')
          .height(46)
          .backgroundColor($r('sys.color.brand'))
          .fontColor(Color.White)
          .onClick(() => promptAction.showToast({ message: 'カプセル型が押されました' }));

        // 円形ボタン(アイコン配置)
        Button({ type: ButtonType.Circle, stateEffect: true }) {
          Image($r('app.media.ic_plus'))
            .width(22)
            .height(22)
            .fill(Color.White);
        }
        .width(56)
        .height(56)
        .backgroundColor($r('sys.color.brand'));

        // 通常型(角丸カスタマイズ)
        Button('通常型(角丸調整)', { type: ButtonType.Normal })
          .width('85%')
          .height(46)
          .borderRadius(6)
          .backgroundColor('#5A6B7C')
          .fontColor(Color.White);

        // 角丸矩形型
        Button('角丸矩形型', { type: ButtonType.ROUNDED_RECTANGLE })
          .width('85%')
          .height(46)
          .backgroundColor($r('sys.color.brand'))
          .fontColor(Color.White);

        Divider().width('100%').margin({ top: 10, bottom: 10 });

        Text('インタラクション制御')
          .fontSize(20)
          .fontWeight(FontWeight.Medium)
          .alignSelf(ItemAlign.Start);

        // 有効/無効切り替えトリガー
        Button(this.isBtnLocked ? 'ロックを解除' : 'ロックする')
          .width('85%')
          .height(46)
          .backgroundColor(this.isBtnLocked ? '#A0A0A0' : $r('sys.color.brand'))
          .fontColor(Color.White)
          .onClick(() => { this.isBtnLocked = !this.isBtnLocked; });

        // 制御対象ボタン
        Button('操作対象ボタン')
          .width('85%')
          .height(46)
          .enabled(!this.isBtnLocked)
          .backgroundColor($r('sys.color.brand'))
          .fontColor(Color.White)
          .onClick(() => promptAction.showToast({ message: '操作が実行されました' }));

        // 複合コンテンツボタン
        Button({ type: ButtonType.Capsule }) {
          Row({ space: 6 }) {
            Image($r('app.media.ic_search'))
              .width(18)
              .height(18)
              .fill(Color.White);
            Text('検索を実行')
              .fontSize(15)
              .fontColor(Color.White);
          }.alignItems(VerticalAlign.Center)
        }
        .width('85%')
        .height(46)
        .backgroundColor('#6B46C1');

        // フィードバック無効化
        Button('エフェクトなし', { stateEffect: false })
          .width('85%')
          .height(46)
          .backgroundColor('#888888')
          .fontColor(Color.White);
      }
      .width('100%')
      .padding(20)
      .backgroundColor('#F9FAFB');
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F9FAFB');
  }
}

Radioコンポーネントの実装

Radioは複数の選択肢から必ず1つだけを選択させる場面で利用します。同じgroup値を持つコンポーネントは自動的に排他制御されます。選択状態はchecked属性でバインドし、onChangeイベントで状態変化を検知します。インジケーターの形状はindicatorTypeで切り替えられ、@Builderを用いた完全なカスタマイズもサポートされています。

import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct RadioSamplePage {
  @State activeProfile: string = 'standard';
  private readonly profileGroup: string = 'notificationGroup';

  @Builder
  StarIndicator() {
    Image($r('app.media.ic_star'))
      .width(14)
      .height(14)
      .fill(Color.Amber);
  }

  build() {
    Column({ space: 18 }) {
      Text('Radioによる排他選択とスタイル適用')
        .fontSize(20)
        .fontWeight(FontWeight.Medium);

      Column({ space: 14 }) {
        // 標準ドットインジケーター
        Row() {
          Radio({ value: 'standard', group: this.profileGroup, indicatorType: RadioIndicatorType.DOT })
            .checked(this.activeProfile === 'standard')
            .radioStyle({ checkedBackgroundColor: Color.LightPink, indicatorColor: Color.Blue })
            .onChange((isSelected: boolean) => {
              if (isSelected) {
                this.activeProfile = 'standard';
                promptAction.showToast({ message: '標準モードを選択' });
              }
            });
          Text('標準通知').fontSize(15).margin({ left: 10 });
        }.alignItems(VerticalAlign.Center);

        // カスタムインジケーター
        Row() {
          Radio({
            value: 'priority',
            group: this.profileGroup,
            indicatorType: RadioIndicatorType.CUSTOM,
            indicatorBuilder: () => this.StarIndicator()
          })
            .checked(this.activeProfile === 'priority')
            .radioStyle({ checkedBackgroundColor: Color.LightPink })
            .onChange((isSelected: boolean) => {
              if (isSelected) {
                this.activeProfile = 'priority';
                promptAction.showToast({ message: '優先モードを選択' });
              }
            });
          Text('優先通知').fontSize(15).margin({ left: 10 });
        }.alignItems(VerticalAlign.Center);

        // チェックマークインジケーター
        Row() {
          Radio({ value: 'mute', group: this.profileGroup, indicatorType: RadioIndicatorType.TICK })
            .checked(this.activeProfile === 'mute')
            .radioStyle({ checkedBackgroundColor: Color.LightPink })
            .onChange((isSelected: boolean) => {
              if (isSelected) {
                this.activeProfile = 'mute';
                promptAction.showToast({ message: 'ミュートモードを選択' });
              }
            });
          Text('ミュート').fontSize(15).margin({ left: 10 });
        }.alignItems(VerticalAlign.Center);
      }
      .padding(18)
      .backgroundColor('#F3F4F6')
      .borderRadius(10)
      .width('92%');

      Text(`現在の設定: ${this.activeProfile}`)
        .fontSize(13)
        .fontColor('#6B7280')
        .margin({ top: 10 });
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#F9FAFB')
    .justifyContent(FlexAlign.Center);
  }
}

Toggleコンポーネントの実装

Toggleはオン/オフや有効/無効といった2値状態を切り替えるためのコンポーネントです。ToggleTypeによってスイッチ、チェックボックス、状態保持ボタンの3つの外観を使い分けられます。状態はisOnで初期化し、onChangeで変更後の値を受け取ります。スイッチのトラック色やスライダー色は専用属性で細かく調整可能です。

import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct ToggleSamplePage {
  @State wifiEnabled: boolean = false;
  @State termsAccepted: boolean = false;
  @State darkThemeActive: boolean = false;

  build() {
    Column({ space: 22 }) {
      Text('Toggleによる状態切り替えパターン')
        .fontSize(20)
        .fontWeight(FontWeight.Medium);

      // Switchタイプ
      Row({ space: 12 }) {
        Text('Wi-Fi接続').fontSize(15);
        Toggle({ type: ToggleType.Switch, isOn: this.wifiEnabled })
          .selectedColor($r('sys.color.brand'))
          .switchPointColor(Color.White)
          .onChange((status: boolean) => {
            this.wifiEnabled = status;
            promptAction.showToast({ message: `Wi-Fi: ${status ? 'ON' : 'OFF'}` });
          });
      }
      .alignItems(VerticalAlign.Center)
      .width('92%')
      .padding(12)
      .backgroundColor('#F3F4F6')
      .borderRadius(10);

      // Checkboxタイプ
      Row({ space: 8 }) {
        Toggle({ type: ToggleType.Checkbox, isOn: this.termsAccepted })
          .selectedColor($r('sys.color.brand'))
          .onChange((status: boolean) => { this.termsAccepted = status; });
        Text('利用規約およびプライバシーポリシーに同意する')
          .fontSize(13)
          .fontColor('#4B5563');
      }
      .alignItems(VerticalAlign.Center)
      .width('92%')
      .padding(12)
      .backgroundColor('#F3F4F6')
      .borderRadius(10);

      // Buttonタイプ
      Toggle({ type: ToggleType.Button, isOn: this.darkThemeActive }) {
        Text(this.darkThemeActive ? 'ダークテーマ: 有効' : 'ダークテーマ: 無効')
          .fontSize(15)
          .fontColor(this.darkThemeActive ? Color.White : '#1F2937');
      }
      .selectedColor($r('sys.color.brand'))
      .width('92%')
      .height(46)
      .onChange((status: boolean) => {
        this.darkThemeActive = status;
        promptAction.showToast({ message: `テーマ切替: ${status ? 'ダーク' : 'ライト'}` });
      });
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#F9FAFB')
    .justifyContent(FlexAlign.Center);
  }
}

実装時の留意点

各コンポーネントの特性を理解し、用途に応じて使い分けることが重要です。Buttonは単発のアクション実行に、Radioはグループ内での単一選択に、Toggleは2値状態の維持・切り替えに最適化されています。開発時には、Radioの排他制御がgroup属性に依存すること、ToggleswitchPointColorがSwitch形態限定であること、Buttonの無効化がenabled属性で制御される点に留意してください。状態変数とUI属性の適切なバインドにより、宣言的UIのデータ駆動モデルを最大限に活用できます。

タグ: HarmonyOS ArkUI ArkTS Buttonコンポーネント Radioコンポーネント

9月13日 14:54 投稿