🌼
【Dart/Flutter】Flutter Apprenticeのテクニック抜粋
無料で素晴らしい教育資料Flutter Apprenticeの中で、
個人的に覚えておきたいテクニックをソースコード部分と共に抜粋したものとなります。
順を追っての説明はありませんので、詳細はFlutter Apprentice にてご確認ください。
何か思い出す際等に、お役立ちできれば幸いです。
関連記事
【Dart/Flutter】Flutter Apprenticeのテクニック抜粋
【Dart/Flutter】Flutter Apprenticeのテクニック抜粋 - 備考1
【Dart/Flutter】Flutter Apprenticeのテクニック抜粋 - 備考2
【Dart/Flutter】Flutter Apprenticeのテクニック集
はじめに
-
【参考】Flutter Apprentice:公式の学習サイト(無料)
-
Flutter実行環境
sdk: ">=2.12.0 <3.0.0"
Widget小ネタ
- 【CircleAvatar】CircleAvatar(○の形)を二つ利用し、外枠作成
return CircleAvatar(
backgroundColor: Colors.white,
radius: imageRadius,
// 4
child: CircleAvatar(
radius: imageRadius - 5,
backgroundImage: imageProvider,
),
);
- 【RotatedBox】RotatedBoxのquarterTurns指定により、1毎にWidgetを右に90°回転
child: RotatedBox(
quarterTurns: 3,
child: Text(
'Smoothies',
style: FooderlichTheme.lightTextTheme.headline1,
),
),
- 【Container】ウィジェットの上に暗いオーバーレイ
Container(
decoration: BoxDecoration(
// 1
color: Colors.black.withOpacity(0.6),
// 2
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
),
),
- 【ListView】ListViewのアイテムの間に何かウィジェットを含める場合は、
ListView.separated
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: recipes.length,
itemBuilder: (context, index) {
final recipe = recipes[index];
return buildCard(recipe);
},
separatorBuilder: (context, index) {
return const SizedBox(width: 16);
},
),
- 【ListView】ListViewをネスト(Not Column! ListViewにListViewをネスト)
-
https://github.com/raywenderlich/flta-materials/blob/f7d9e37d7c4212517e8f4271244e89ec2d3610f2/05-scrollable-widgets/projects/final/lib/screens/explore_screen.dart#L18
- ListViewの中で、ListViewを呼ぶことで、下部半分が見えて固定でスクロールされるではなく、全画面で表示されて、下までスクロール可能とする
- 以下、ネストされている
FriendPostListView
のListView-
https://github.com/raywenderlich/flta-materials/blob/f7d9e37d7c4212517e8f4271244e89ec2d3610f2/05-scrollable-widgets/projects/final/lib/components/friend_post_list_view.dart#L30
- ネストされたリストビューでは、
primary: false,
を追加 -
primary: false,
を追加した場合も、physics: const NeverScrollableScrollPhysics(),
でスクロール無効を推奨 - ネストされたリストビューでは、リスト内のすべてのアイテムのスクロールビューに固定の高さを与えることができるように、shrinkWrapをtrueに設定
- 無いとエラー:「Vertical viewport was given unbounded height.」
- ネストされたリストビューでは、
-
https://github.com/raywenderlich/flta-materials/blob/f7d9e37d7c4212517e8f4271244e89ec2d3610f2/05-scrollable-widgets/projects/final/lib/components/friend_post_list_view.dart#L30
-
https://github.com/raywenderlich/flta-materials/blob/f7d9e37d7c4212517e8f4271244e89ec2d3610f2/05-scrollable-widgets/projects/final/lib/screens/explore_screen.dart#L18
return ListView(
scrollDirection: Axis.vertical,
children: [
TodayRecipeListView(recipes: snapshot.data?.todayRecipes ?? []),
const SizedBox(height: 16),
FriendPostListView(
friendPosts: snapshot.data?.friendPosts ?? [],
)
],
);
以下、上記のFriendPostListView
のListView
ListView.separated(
primary: false,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: friendPosts.length,
itemBuilder: (context, index) {
final post = friendPosts[index];
return FriendPostTile(post: post);
},
separatorBuilder: (context, index) {
return const SizedBox(height: 16);
},
),
- 【GridView】GridViewの
gridDelegate
-
https://github.com/raywenderlich/flta-materials/blob/f7d9e37d7c4212517e8f4271244e89ec2d3610f2/05-scrollable-widgets/projects/challenge/challenge-final/lib/components/recipes_grid_view.dart#L22
- SliverGridDelegateWithFixedCrossAxisCount
- 交差軸に沿って固定数のタイルを持つレイアウトを作成
- 例:
gridDelegate:const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
- 2列指定
- SliverGridDelegateWithMaxCrossAxisExtent
- 最大の交差軸範囲を持つタイルを使用
- 例:
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 500.0),
- デバイスのwidthよりも大きい500.0指定すると1列表示
- 例:
- 最大の交差軸範囲を持つタイルを使用
- SliverGridDelegateWithFixedCrossAxisCount
-
https://github.com/raywenderlich/flta-materials/blob/f7d9e37d7c4212517e8f4271244e89ec2d3610f2/05-scrollable-widgets/projects/challenge/challenge-final/lib/components/recipes_grid_view.dart#L22
child: GridView.builder(
itemCount: recipes.length,
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 500.0),
itemBuilder: (context, index) {
final simpleRecipe = recipes[index];
return RecipeThumbnail(recipe: simpleRecipe);
},
),
- 【IndexedStack】BottomNavigationBar等のタブ切り替え時に、スクロール位置保持
body: IndexedStack(
index: tabManager.selectedTab,
children: pages,
),
- 【AspectRatio】指定のwidth / height比率とする
-
https://github.com/raywenderlich/flta-materials/blob/f7d9e37d7c4212517e8f4271244e89ec2d3610f2/06-interactive-widgets/projects/final/lib/screens/empty_grocery_screen.dart#L21
- その子を指定されたサイズに設定
- width / height代わりに、計算結果の1 / 1
- 16:9の比率が必要な場合→16 / 9
-
https://github.com/raywenderlich/flta-materials/blob/f7d9e37d7c4212517e8f4271244e89ec2d3610f2/06-interactive-widgets/projects/final/lib/screens/empty_grocery_screen.dart#L21
child: AspectRatio(
aspectRatio: 1 / 1,
child: Image.asset('assets/fooderlich_assets/empty_list.png'),
),
- 【BlockPicker】色を選択させるポップアップ
return AlertDialog(
content: BlockPicker(
pickerColor: Colors.white,
// 6
onColorChanged: (color) {
setState(() => _currentColor = color);
}),
actions: [
// 7
TextButton(
child: const Text('Save'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
- 【Dismissible】左または右にスワイプしたときに削除可能にする
return Dismissible(
// 6
key: Key(item.id),
// 7
direction: DismissDirection.endToStart,
// 8
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
child: const Icon(
Icons.delete_forever,
color: Colors.white,
size: 50.0,
),
),
// 9
onDismissed: (direction) {
// 10
manager.deleteItem(index);
// 11
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('${item.name} dismissed'),
),
);
},
child: InkWell(
// ・・・省略・・・
);
- 【CheckboxListTile】☑チェックボックスの便利ウィジェット
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../data/memory_repository.dart';
class ShoppingList extends StatefulWidget {
const ShoppingList({Key? key}) : super(key: key);
State<ShoppingList> createState() => _ShoppingListState();
}
class _ShoppingListState extends State<ShoppingList> {
/// 食材購入のチェックボックス管理
final checkBoxValues = Map<int, bool>();
Widget build(BuildContext context) {
/// Consumerを利用し、監視(このウィジェットツリー下を再描画範囲とする)
return Consumer<MemoryRepository>(builder: (context, repository, child) {
final ingredients = repository.findAllIngredients();
return ListView.builder(
itemCount: ingredients.length,
itemBuilder: (BuildContext context, int index) {
/// ☑チェックボックスの便利ウィジェット
return CheckboxListTile(
/// checkBoxValuesのindexとingredientsのindexを同期させて管理
value:
checkBoxValues.containsKey(index) && checkBoxValues[index]!,
title: Text(ingredients[index].name ?? ''),
onChanged: (newValue) {
if (newValue != null) {
setState(() {
checkBoxValues[index] = newValue;
});
}
},
);
});
});
}
}
ロジック小ネタ
- スクロール位置の監視
-
https://github.com/raywenderlich/flta-materials/blob/f7d9e37d7c4212517e8f4271244e89ec2d3610f2/05-scrollable-widgets/projects/challenge/challenge-final/lib/screens/explore_screen.dart#L25
- ListViewのcontrollerに以下をaddListenerすることで、スクロールで一番上か、下か判断可能
-
https://github.com/raywenderlich/flta-materials/blob/f7d9e37d7c4212517e8f4271244e89ec2d3610f2/05-scrollable-widgets/projects/challenge/challenge-final/lib/screens/explore_screen.dart#L25
void _scrollListener() {
if (_controller.offset >= _controller.position.maxScrollExtent &&
!_controller.position.outOfRange) {
print('reached the bottom');
}
if (_controller.offset <= _controller.position.minScrollExtent &&
!_controller.position.outOfRange) {
print('reached the top!');
}
}
- DateTimeを渡す際に、日付と時間を別に管理していても上手く渡す
date: DateTime(
_dueDate.year,
_dueDate.month,
_dueDate.day,
_timeOfDay.hour,
_timeOfDay.minute,
),
- 便利パッケージ「Equatable」(等価性チェックのためのサポート)
-
https://github.com/raywenderlich/flta-materials/blob/f7d9e37d7c4212517e8f4271244e89ec2d3610f2/13-state-management/projects/final/lib/data/models/recipe.dart#L26
- props関数をオーバーライド
- 同等性をチェックするために使用するフィールドを指定
- props関数をオーバーライド
-
https://github.com/raywenderlich/flta-materials/blob/f7d9e37d7c4212517e8f4271244e89ec2d3610f2/13-state-management/projects/final/lib/data/models/recipe.dart#L26
import 'package:equatable/equatable.dart';
import 'ingredient.dart';
// ignore: must_be_immutable
class Recipe extends Equatable {
int? id;
final String? label;
final String? image;
final String? url;
List<Ingredient>? ingredients;
final double? calories;
final double? totalWeight;
final double? totalTime;
Recipe(
{this.id,
this.label,
this.image,
this.url,
this.calories,
this.totalWeight,
this.totalTime});
List<Object?> get props =>
[label, image, url, calories, totalWeight, totalTime];
}
範囲大きめなテクニック
-
備考レベルですが、以下に記載しております。
Discussion