はじめに
普段、使ったことないWidgetを使って記事を書こうと思いましたので、一度も使ったことがないDraggableScrollableSheet
を使ってコードを書いてみました。
公式の記事と動画の説明がわかりやすかったのでURLを貼っておきます。
作ったもの
ショッピングアプリで、商品リストを見ながら、カートの画面を見れるという画面です。
コード
作ったコードを貼り付けておきます。
表示部分しか作ってないので、商品を追加したり、削除したりというのはできません。
DraggableScrollableSheet
の使い方については次の章で説明します。
// 表示している画面
class ProductListPage extends StatelessWidget {
const ProductListPage();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Product List'),
),
body: Container(
padding: const EdgeInsets.all(16),
child: Stack(
children: [
// 商品一覧表示
const ProductListView(),
// カートを表示
DraggableScrollableSheet(
initialChildSize: 0.1,
minChildSize: 0.1,
maxChildSize: 0.9,
builder:
(BuildContext context, ScrollController scrollController) {
// カートの商品一覧を表示
return Container(
decoration: const BoxDecoration(
color: Color.fromARGB(255, 218, 243, 255),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
// カート商品表示のタイトルとアイテム一覧を表示
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// カート商品表示のタイトル
const SizedBox(
height: 40,
child: Center(
child: Text(
"Cart",
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w700,
),
),
),
),
// カートのアイテム一覧を表示
Expanded(
child: ListView.builder(
controller: scrollController,
itemCount: productsInCart.length,
itemBuilder: (context, index) {
final product = productsInCart[index];
return Padding(
padding: const EdgeInsets.all(8.0),
child: CartItem(
title: product.name,
price: product.price,
),
);
},
),
),
],
),
);
},
),
],
),
),
);
}
}
// 商品一覧表示の個別のアイテム
class ProductItem extends StatelessWidget {
final String title;
final String description;
final int price;
const ProductItem({
required this.title,
required this.description,
required this.price,
super.key,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
flex: 3,
child: Container(
height: 100,
color: Colors.grey.withOpacity(0.7),
child: const Center(
child: Text(
"Image",
style: TextStyle(
color: Colors.white,
),
)),
),
),
const SizedBox(
width: 8,
),
Expanded(
flex: 7,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ProductTitleText(title),
ProductDescriptionText(description),
ProductPriceText(price),
],
),
),
],
);
}
}
// カート一覧に表示する個別のアイテム
class CartItem extends StatelessWidget {
final String title;
final int price;
const CartItem({
required this.title,
required this.price,
super.key,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
flex: 3,
child: Container(
height: 100,
color: Colors.grey.withOpacity(0.7),
child: const Center(
child: Text(
"Image",
style: TextStyle(
color: Colors.white,
),
)),
),
),
const SizedBox(
width: 8,
),
Expanded(
flex: 7,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ProductTitleText(title),
ProductPriceText(price),
],
),
),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.delete,
),
),
],
);
}
}
// 商品一覧表示
class ProductListView extends StatelessWidget {
const ProductListView({super.key});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return Padding(
padding: const EdgeInsets.only(top: 8, bottom: 8),
child: ProductItem(
title: product.name,
description: product.description,
price: product.price,
),
);
},
);
}
}
// ProductItemのタイトルに使うテキスト
class ProductTitleText extends StatelessWidget {
final String title;
const ProductTitleText(this.title, {super.key});
@override
Widget build(BuildContext context) {
return Text(
title,
textAlign: TextAlign.start,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
),
);
}
}
// ProductItemの説明に使うテキスト
class ProductDescriptionText extends StatelessWidget {
final String description;
const ProductDescriptionText(this.description, {super.key});
@override
Widget build(BuildContext context) {
return Text(
description,
textAlign: TextAlign.start,
style: const TextStyle(
fontSize: 16,
),
);
}
}
// ProductItemの価格に使うテキスト
class ProductPriceText extends StatelessWidget {
final int price;
const ProductPriceText(this.price, {super.key});
@override
Widget build(BuildContext context) {
return Text(
"¥$price",
textAlign: TextAlign.start,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: Colors.red,
),
);
}
}
// 商品データのモデルクラス
class Product {
String name;
String description;
int price;
Product({required this.name, required this.description, required this.price});
}
// 商品一覧に表示する商品リスト
List<Product> products = [
Product(
name: "掃除機",
description: "スムーズな吸引力と高効率なフィルターが特徴。小型で軽量なので、家庭のあらゆる場所で使用できます。",
price: 8000),
Product(
name: "よく吸う掃除機",
description:
"パワフルなモーターと高性能のフィルターを搭載し、毛やダストを徹底的に吸い取ります。コンパクトなデザインで、収納にも便利です。",
price: 15000),
Product(
name: "エアクリーナー",
description:
"空気中の微粒子を取り除くための高性能フィルターを搭載した空気清浄機。コンパクトで静音なので、寝室やオフィスでも使用できます。",
price: 25000),
Product(
name: "コーヒーメーカー",
description: "豆から挽いたコーヒー豆で本格的なコーヒーを簡単に作れる。自動シャットオフ機能付きで、安心して使用できます。",
price: 12000),
Product(
name: "電気ケトル",
description:
"ワンタッチでお湯を沸かすことができる電気ケトル。高速で沸騰するので、忙しい朝にも便利です。コンパクトなデザインで、省スペースに収納できます。",
price: 5000),
Product(
name: "10000mAhモバイルバッテリー",
description:
"スマートフォンやタブレットの充電に使用できる10000mAhのモバイルバッテリー。コンパクトなデザインで、持ち運びにも便利です。",
price: 3000),
Product(
name: "照明器具",
description: "優れた省エネ性能を持ち、快適な明るさを提供する照明器具。調光機能があり、ライトの明るさを自由に調整できます。",
price: 6000),
Product(
name: "空気清浄機",
description:
"高性能のフィルターで空気中の花粉やほこりを取り除く空気清浄機。赤ちゃんやアレルギー持ちの方にもおすすめ、使い勝手の良いデザインで、リビングや寝室などのあらゆる場所で使用できます。",
price: 20000),
Product(
name: "Bluetoothスピーカー",
description:
"高品質な音質とワイヤレス接続による快適な操作性が特徴のBluetoothスピーカー。コンパクトで持ち運びに便利なデザインです。",
price: 7000),
Product(
name: "キッチンスケール",
description:
"正確な計量ができるキッチンスケール。液晶ディスプレイにより、計量結果を見やすく表示します。コンパクトなデザインで、キッチンの収納にも適しています。",
price: 4000),
Product(
name: "ノイズキャンセリングヘッドフォン",
description:
"ノイズキャンセリング機能により、周囲の騒音を最大限に軽減するヘッドフォン。高音質なサウンドで、音楽や映画をより楽しめます。",
price: 25000),
];
// カートに入っている商品リスト
List<Product> productsInCart = List.generate(10, (index) => products[index]);
DraggableScrollableSheetの使い方
前章のコードでは要点を掴みにくいと思いますので、DraggableScrollableSheet
に焦点を当てたコードを掲載します。
DraggableScrollableSheet(
// 初期の表示割合
initialChildSize: 0.2,
// 最小の表示割合
minChildSize: 0.1,
// 最大の表示割合
maxChildSize: 0.9,
// ドラッグを離した時に一番近いsnapSizeになるか
snap: true,
// snapで止める時の割合
snapSizes: const [0.1, 0.5, 0.9],
builder: (BuildContext context, ScrollController scrollController) {
// 表示したいWidgetを返す
return Container();
},
),
コード内の下記の引数は省略可能です
-
initialChildSize
(デフォルトは0.5) -
minChildSize
(デフォルトは0.25) -
maxChildSize
(デフォルトは1.0) -
snap
(デフォルトはfalse) -
snapSized
(nullable)
DraggableScrollableSheet
を使用する際の注意点は、builder
内でScrollController
を使用しないと、シートはinitialChildSize
のままになるという点です。
したがって、シートのサイズをドラッグ操作したい場合、builder
で返すWidgetにはListView
やSingleChildScrollView
を含めるようにしましょう。
他の用途
今回は、商品一覧とカート内の商品を一画面で見るという用途で使用してみました。
他に使えそうな用途としては、以下が挙げられると思います
- 一覧画面内で、画面遷移せずに詳細を見たい場合
詳細の表示にDraggableScrollableSheet
を使うことで遷移せずに詳細が確認できます。 - 設定画面で設定を変更した時のフィードバックを即座に確認したい場合
例えば、設定画面でメイン画面のアイコンの大きさを変えれると仮定して、設定画面とメイン画面が別画面だと、設定を変えた時に、メイン画面に戻るまでどのような画面になるかがわかりませんが、メイン画面にDraggableScrollableSheet
を使って設定できるようにすると、反映が即座に確認できます。
おわりに
使う場面は少ないかもしれませんが、お役に立てれば幸いです。