Open6

【Flutter】CupertinoWidgets

masa-futamasa-futa

概要

CupertinoWidgetsを普段から利用する機会がなかったため
メモ変わりにつらつら記載していく。

masa-futamasa-futa

公式

Why write a Cupertino app? The Material design language was created for any platform, not just Android. When you write a Material app in Flutter, it has the Material look and feel on all devices, even iOS. If you want your app to look like a standard iOS-styled app, then you would use the Cupertino library.
You can technically run a Cupertino app on either Android or iOS, but (due to licensing issues) Cupertino won't have the correct fonts on Android. For this reason, use an iOS-specific device when writing a Cupertino app.

なぜ Cupertino アプリを作成するのでしょうか? マテリアル デザイン言語は、Android だけでなく、あらゆるプラットフォーム向けに作成されました。Flutter でマテリアル アプリを作成すると、iOS を含むすべてのデバイスでマテリアルの外観と操作性が得られます。アプリを標準の iOS スタイルのアプリのように見せたい場合は、Cupertino ライブラリを使用します。

技術的には、Cupertino アプリを Android または iOS で実行できますが、(ライセンスの問題により) Cupertino では Android 上で正しいフォントが使用できません。このため、Cupertino アプリを作成するときは、iOS 専用のデバイスを使用してください。

上記にあるように、Apple のヒューマンインターフェイスガイドラインを考慮した設計を心がけているようです。

masa-futamasa-futa

CupertinoActionSheet

ヒューマンインターフェイスガイドライン

https://developer.apple.com/jp/design/human-interface-guidelines/action-sheets

実装

  void _showActionSheet(BuildContext context) {
    showCupertinoModalPopup<void>(
      context: context,
      builder: (context) => CupertinoActionSheet(
        actions: <CupertinoActionSheetAction>[
          CupertinoActionSheetAction(
            isDefaultAction: true,
            onPressed: () => Navigator.pop(context),
            child: const Text('Default Action'),
          ),
          CupertinoActionSheetAction(
            onPressed: () => Navigator.pop(context),
            child: const Text('Action'),
          ),
          CupertinoActionSheetAction(
            isDestructiveAction: true,
            onPressed: () => Navigator.pop(context),
            child: const Text('Destructive Action'),
          ),
        ],
        cancelButton: CupertinoActionSheetAction(
          onPressed: () => Navigator.pop(context),
          child: const Text('Cancel'),
        ),
      ),
    );
  }

詳細

  • actions

    • CupertinoActionSheetAction を利用してActionButon を定義する。
      • isDefaultActionを true にすることにより、font sizeを明示的にFontWeight.w600に設定している
          if (widget.isDefaultAction) {
            style = style.copyWith(fontWeight: FontWeight.w600);
          }
        
      • isDestructiveActionを true にすることにより font colorを明示的に CupertinoColors.systemRed に設定している
  • cancelButton

    • ヒューマンガイドラインの キャンセル ボタン部分のコンポーネントを担う
masa-futamasa-futa

CupertinoActivityIndicator

ヒューマンインターフェイスガイドライン

https://developer.apple.com/jp/design/human-interface-guidelines/progress-indicators

実装

  Widget build(BuildContext context) {
    return const CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text('CupertinoActivityIndicator Sample'),
      ),
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                CupertinoActivityIndicator(),
                SizedBox(height: 10),
                Text('Default'),
              ],
            ),
            Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                CupertinoActivityIndicator(
                  radius: 20.0,
                  color: CupertinoColors.activeBlue,
                ),
                SizedBox(height: 10),
                Text(
                  'radius: 20.0\ncolor: CupertinoColors.activeBlue',
                  textAlign: TextAlign.center,
                ),
              ],
            ),
            Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                CupertinoActivityIndicator(
                  radius: 20.0,
                  animating: false,
                ),
                SizedBox(height: 10),
                Text(
                  'radius: 20.0\nanimating: false',
                  textAlign: TextAlign.center,
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

詳細

  • radius
    • 大きさ
  • animating
    • アニメーションを実施するかのフラグ
  • color
    • colorはそのまま
      シンプルな実装となっているため扱いやすい印象
masa-futamasa-futa

CupertinoAdaptiveTextSelectionToolbar

テキスト選択用の Cupertino コンテキスト メニュー

実装

.editableText
  
  Widget build(BuildContext context) {
    return SelectableText(
      '長押ししてテキストを選択してください。',
      onSelectionChanged: (selection, cause) {
        debugPrint('選択範囲: $selection');
      },
      contextMenuBuilder: (context, state) {
        return CupertinoAdaptiveTextSelectionToolbar.editableText(
          editableTextState: state,
        );
      },
    );
  }

.buttonItems
  
  Widget build(BuildContext context) {
    return SelectableText(
      '長押ししてテキストを選択してください。',
      onSelectionChanged: (selection, cause) {
        debugPrint('選択範囲: $selection');
      },
      contextMenuBuilder: (context, state) {
        return CupertinoAdaptiveTextSelectionToolbar.buttonItems(
          anchors: state.contextMenuAnchors,
          buttonItems: [
            ContextMenuButtonItem(
                label: 'コピー',
                onPressed: () {
                  final selectionText = state.textEditingValue.selection
                      .textInside(state.textEditingValue.text);
                  Clipboard.setData(ClipboardData(text: selectionText));
                  _unfocus(context);
                }),
            ContextMenuButtonItem(
              label: 'キャンセル',
              onPressed: () => _unfocus(context),
            ),
          ],
        );
      },
    );
  }

  void _unfocus(BuildContext context) {
    ContextMenuController.removeAny();
    FocusScope.of(context).unfocus();
  }
}

.editable

@override
Widget build(BuildContext context) {
return SelectableText(
'長押ししてテキストを選択してください。',
onSelectionChanged: (selection, cause) {
debugPrint('選択範囲: $selection');
},
contextMenuBuilder: (context, state) {
return CupertinoAdaptiveTextSelectionToolbar.editable(
anchors: state.contextMenuAnchors,
clipboardStatus: ClipboardStatus.pasteable,
onCopy: () {
final selectionText = state.textEditingValue.selection
.textInside(state.textEditingValue.text);
Clipboard.setData(ClipboardData(text: selectionText));
_unfocus(context);
},
// 以下省略
onCut: () => _unfocus(context),
onLookUp: () => _unfocus(context),
onPaste: () => _unfocus(context),
onSelectAll: () => _unfocus(context),
onShare: () => _unfocus(context),
onSearchWeb: () => _unfocus(context),
onLiveTextInput: () => _unfocus(context),
);
},
);
}

.selectable
  
  Widget build(BuildContext context) {
    final hasContent = useState(false);
    return SelectableText(
      '長押ししてテキストを選択してください。',
      onSelectionChanged: (selection, cause) {
        hasContent.value = selection.isValid;
        debugPrint('選択範囲: $selection');
      },
      contextMenuBuilder: (context, state) {
        return CupertinoAdaptiveTextSelectionToolbar.selectable(
          anchors: state.contextMenuAnchors,
          selectionGeometry: SelectionGeometry(
            // uncollapsedにすることにより、Copyが有効になる
            status: SelectionStatus.uncollapsed,
            hasContent: hasContent.value,
          ),
          onCopy: () {
            final selectionText = state.textEditingValue.selection
                .textInside(state.textEditingValue.text);
            Clipboard.setData(ClipboardData(text: selectionText));
            _unfocus(context);
          },
          // 以下省略
          onSelectAll: () => _unfocus(context),
        );
      },
    );
  }