🍏
Cupertino widgets②
iOSのローディングUIを再現する
CupertinoActivityIndicator classを使用するとiPhoneでよくみるあのローディングを再現できます。
ローディング・インジケーターと呼ばれているものですかね。
iOSのローディングインジケーター(CupertinoActivityIndicator)
iOSのローディングインジケーター(CupertinoActivityIndicator)の一般的な使用例をいくつか実装してみましょう。よくあるケースとして:
- API通信時のローディング
- 画像の読み込み
- ページ遷移時のローディング
- プルトゥリフレッシュ
example
main.dart
import 'dart:async';
import 'package:flutter/cupertino.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const CupertinoApp(
theme: CupertinoThemeData(brightness: Brightness.light),
home: MyHomePage(),
);
}
}
// ホーム画面
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(
middle: Text('ローディング例'),
),
child: SafeArea(
child: ListView(
padding: const EdgeInsets.all(16),
children: [
// API通信例
const APILoadingExample(),
const SizedBox(height: 20),
// 画像読み込み例
const ImageLoadingExample(),
const SizedBox(height: 20),
// ページ遷移ボタン
CupertinoButton.filled(
onPressed: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => const DetailPage(),
),
);
},
child: const Text('詳細ページへ'),
),
const SizedBox(height: 20),
// プルトゥリフレッシュ例
const Text(
'↓ プルトゥリフレッシュを試してください',
textAlign: TextAlign.center,
),
],
),
),
);
}
}
// API通信のローディング例
class APILoadingExample extends StatefulWidget {
const APILoadingExample({super.key});
State<APILoadingExample> createState() => _APILoadingExampleState();
}
class _APILoadingExampleState extends State<APILoadingExample> {
bool _isLoading = false;
String _data = '未取得';
Future<void> _fetchData() async {
setState(() {
_isLoading = true;
});
// APIリクエストのシミュレーション
await Future.delayed(const Duration(seconds: 2));
setState(() {
_isLoading = false;
_data = '更新されたデータ: ${DateTime.now().toString()}';
});
}
Widget build(BuildContext context) {
return CupertinoButton.filled(
onPressed: _isLoading ? null : _fetchData,
child: _isLoading
? const CupertinoActivityIndicator(color: CupertinoColors.white)
: Text('データを取得: $_data'),
);
}
}
// 画像読み込みのローディング例
class ImageLoadingExample extends StatefulWidget {
const ImageLoadingExample({super.key});
State<ImageLoadingExample> createState() => _ImageLoadingExampleState();
}
class _ImageLoadingExampleState extends State<ImageLoadingExample> {
bool _isLoading = true;
void initState() {
super.initState();
_loadImage();
}
Future<void> _loadImage() async {
// 画像読み込みのシミュレーション
await Future.delayed(const Duration(seconds: 2));
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
Widget build(BuildContext context) {
return Container(
height: 200,
decoration: BoxDecoration(
color: CupertinoColors.systemGrey6,
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: _isLoading
? const CupertinoActivityIndicator(radius: 15)
: const Icon(
CupertinoIcons.photo,
size: 50,
color: CupertinoColors.systemGrey,
),
),
);
}
}
// 詳細ページ(ページ遷移時のローディング例)
class DetailPage extends StatefulWidget {
const DetailPage({super.key});
State<DetailPage> createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
bool _isLoading = true;
List<String> _items = [];
void initState() {
super.initState();
_loadData();
}
Future<void> _loadData() async {
// データ読み込みのシミュレーション
await Future.delayed(const Duration(seconds: 2));
if (mounted) {
setState(() {
_isLoading = false;
_items = List.generate(20, (index) => 'アイテム ${index + 1}');
});
}
}
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(
middle: Text('詳細ページ'),
),
child: SafeArea(
child: _isLoading
? const Center(
child: CupertinoActivityIndicator(radius: 15),
)
: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
CupertinoSliverRefreshControl(
onRefresh: _loadData,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: CupertinoColors.systemGrey5,
),
),
),
child: Text(_items[index]),
),
childCount: _items.length,
),
),
],
),
),
);
}
}
このサンプルでは、以下の一般的なローディングパターンを実装しています:
- APIリクエスト時のローディング
- ボタン内にインジケーターを表示
- ボタンを無効化してダブルタップを防止
- データ取得完了後に結果を表示
- 画像読み込み時のローディング
- プレースホルダー領域にインジケーターを表示
- 読み込み完了後にアイコンを表示
- エラー時の代替表示
- ページ遷移時のローディング
- 画面中央にインジケーターを表示
- データ読み込み完了後にコンテンツを表示
- プルトゥリフレッシュ機能
- プルトゥリフレッシュ
-
CupertinoSliverRefreshControl
を使用 - iOS標準のプルトゥリフレッシュ動作
- リフレッシュ時にデータを再読み込み
使用時の注意点:
- インジケーターのサイズ
CupertinoActivityIndicator(
radius: 15, // デフォルトは10
color: CupertinoColors.white, // 色の変更も可能
)
- 状態管理
bool _isLoading = true; // ローディング状態の管理
// メモリリーク防止
if (mounted) {
setState(() {
_isLoading = false;
});
}
- エラーハンドリング
try {
// データ取得処理
} catch (e) {
// エラー処理
} finally {
setState(() {
_isLoading = false;
});
}
このように、iOSのローディングインジケーターは様々なユースケースで使用され、ユーザーに処理中であることを適切に伝えることができます。
Discussion