【Flutter】CupertinoWidgets

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

公式
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 のヒューマンインターフェイスガイドライン
を考慮した設計を心がけているようです。

リンク
これらを行えば大体の方法は学べそう(ちょっと古い?)

CupertinoActionSheet
ヒューマンインターフェイスガイドライン
実装
例
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
- ヒューマンガイドラインの キャンセル ボタン部分のコンポーネントを担う

CupertinoActivityIndicator
ヒューマンインターフェイスガイドライン
実装
例
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はそのまま
シンプルな実装となっているため扱いやすい印象
- colorはそのまま

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),
);
},
);
}