コンポーネントの役割と選定基準
HarmonyOSの宣言的UIフレームワーク(ArkUI)において、フォーム操作やユーザーインタラクションを実装する際には、Button、Radio、Toggleの3つのコンポーネントが中核を担います。外見や振る舞いが類似している場合もありますが、状態管理の仕組みと適用シーンが明確に異なります。適切な場面で適切なコンポーネントを選定することが、保守性の高いUI構築の第一歩となります。
| コンポーネント | 主な役割 | 技術的特徴 | 代表的なユースケース |
|---|---|---|---|
| Button | アクションの実行・送信 | テキスト/アイコン埋め込み、4種の形状、クリックエフェクト制御 | フォーム送信、画面遷移、ダイアログの確定/キャンセル |
| Radio | 複数選択肢からの単一選択 | group属性による排他制御、インジケーターのカスタマイズ可能 |
性別選択、配送方法、動作モードの切り替え |
| Toggle | 2値状態のオン/オフ切り替え | Switch/Checkbox/Buttonの3形態、状態の保持とバインディング | Wi-Fi/Bluetoothの有効化、利用規約への同意、テーマ切り替え |
プロジェクト構成とナビゲーション画面
検証用のプロジェクトでは、各コンポーネントの動作を確認するための専用ページへ遷移するルート画面を用意します。@kit.ArkUIのrouterモジュールを用いた画面遷移の基礎パターンを示します。
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属性に依存すること、ToggleのswitchPointColorがSwitch形態限定であること、Buttonの無効化がenabled属性で制御される点に留意してください。状態変数とUI属性の適切なバインドにより、宣言的UIのデータ駆動モデルを最大限に活用できます。