✨
画像を角丸にしたり端にくっつける方法
縦長だったり横並びだったり
Flutterで画像のサイズを設定して角丸にしたり縦長にしたりする方法ってどうするのか?
たまにしかやらないのでやり方をメモする目的で記事を書くことにした。
こんなUI作ります
タイトル変なのありますがお気になさらず😅
画像素材はGithubあります。自分の好みの画像を使っていただいて構いません。下のサイズは640×359ぐらいですね。pixabayというフリー画像のサービスからもらってきました。
画像はassets/iamges
に配置してpubspec.yaml
で読み込めるように設定します。
カードコンポーネントを作成。
画像を加工して表示するコンポーネントです。とあるプロジェクトに入ったときは関数で作られたコンポーネントがあったが、コードが見ずらかったりカスタマイズしずらいのでオススメしません💦
- クラスで作ると可読性が上がる。
- プロパティを追加できる。関数だと引数だからね。
- 後でRiverpodのコードに書き換えることができる。関数でもできるみたいだが。
import 'package:flutter/material.dart';
// 1. 縦長の画像カード
class VerticalImageCard extends StatelessWidget {
final String imageUrl;
final String title;
final String subtitle;
final String price;
const VerticalImageCard({
super.key,
required this.imageUrl,
required this.title,
required this.subtitle,
required this.price,
});
Widget build(BuildContext context) {
return Container(
width: 180,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(0x05),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
child: Image.asset(
imageUrl,
height: 240,
width: double.infinity,
fit: BoxFit.cover,
),
),
Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
const SizedBox(height: 8),
Text(
price,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
],
),
),
],
),
);
}
}
// 2. 正方形に近い画像カード(ショッピングアプリスタイル)
class SquareImageCard extends StatelessWidget {
final String imageUrl;
final String title;
final String subtitle;
final String price;
const SquareImageCard({
super.key,
required this.imageUrl,
required this.title,
required this.subtitle,
required this.price,
});
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(0x05),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: ClipRRect(
borderRadius:
const BorderRadius.vertical(top: Radius.circular(16)),
child: Image.asset(
imageUrl,
width: double.infinity,
fit: BoxFit.cover,
),
),
),
Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
const SizedBox(height: 8),
Text(
price,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
],
),
),
],
),
);
}
}
// 3. 横長の画像カード(画像左、テキスト右)
class HorizontalImageCard extends StatelessWidget {
final String imageUrl;
final String title;
final String subtitle;
final String price;
const HorizontalImageCard({
super.key,
required this.imageUrl,
required this.title,
required this.subtitle,
required this.price,
});
Widget build(BuildContext context) {
return Container(
height: 120,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(0x05),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: Row(
children: [
ClipRRect(
borderRadius:
const BorderRadius.horizontal(left: Radius.circular(16)),
child: Image.asset(
imageUrl,
width: 120,
height: 120,
fit: BoxFit.cover,
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
subtitle,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
const SizedBox(height: 8),
Text(
price,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
],
),
),
),
],
),
);
}
}
main.dart
でモジュールを呼び出してUIに表示するのをやってみましょうか。ただUIを作るだけなのですが時々見る綺麗なショッピングアプリのようなものがあると白い背景に文字を配置するだけよりは楽しく感じます。
import 'package:flutter/material.dart';
import 'image_cards.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
scaffoldBackgroundColor: Colors.grey[100],
),
home: const CardDemoPage(),
);
}
}
class CardDemoPage extends StatelessWidget {
const CardDemoPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('カードデザインデモ'),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
),
body: Padding(
padding: const EdgeInsets.all(10.0),
child: ListView(
children: [
const Text(
'1. 縦長カード',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
SizedBox(
height: 360,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
Padding(
padding: const EdgeInsets.only(right: 16),
child: VerticalImageCard(
imageUrl: 'assets/images/couch.jpg',
title: 'モダンソファ',
subtitle: '快適な座り心地',
price: '¥29,800',
),
),
Padding(
padding: const EdgeInsets.only(right: 16),
child: VerticalImageCard(
imageUrl: 'assets/images/da.jpg',
title: 'ダイニングセット',
subtitle: '4人掛けテーブル',
price: '¥45,800',
),
),
VerticalImageCard(
imageUrl: 'assets/images/living.jpg',
title: 'リビングセット',
subtitle: 'シンプルデザイン',
price: '¥89,800',
),
],
),
),
const SizedBox(height: 32),
const Text(
'2. 正方形カード',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: 16,
crossAxisSpacing: 16,
childAspectRatio: 0.8,
children: [
SquareImageCard(
imageUrl: 'assets/images/couch.jpg',
title: 'モダンソファ',
subtitle: '快適な座り心地',
price: '¥29,800',
),
SquareImageCard(
imageUrl: 'assets/images/da.jpg',
title: 'ダイニングセット',
subtitle: '4人掛けテーブル',
price: '¥45,800',
),
],
),
const SizedBox(height: 32),
const Text(
'3. 横長カード',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
HorizontalImageCard(
imageUrl: 'assets/images/couch.jpg',
title: 'モダンソファ',
subtitle: '快適な座り心地',
price: '¥29,800',
),
const SizedBox(height: 16),
HorizontalImageCard(
imageUrl: 'assets/images/da.jpg',
title: 'ダイニングセット',
subtitle: '4人掛けテーブル',
price: '¥45,800',
),
const SizedBox(height: 16),
HorizontalImageCard(
imageUrl: 'assets/images/living.jpg',
title: 'リビングセット',
subtitle: 'シンプルデザイン',
price: '¥89,800',
),
],
),
),
);
}
}
画像サイズの変更と角丸にする方法
Flutter Image Handling Guide
主要なウィジェットとプロパティ
1. ClipRRect
角丸の画像を実現するための主要なウィジェット
ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.asset(...),
)
公式ドキュメント: ClipRRect class
2. Image.asset
アセット画像を表示するためのウィジェット
Image.asset(
'assets/images/example.jpg',
width: double.infinity,
height: 240,
fit: BoxFit.cover,
)
公式ドキュメント: Image class
3. BoxFit プロパティ
画像のサイズ調整方法を指定する重要なプロパティ
-
BoxFit.cover
: アスペクト比を保ちながら、指定された領域を完全に覆うようにサイズ調整 -
BoxFit.contain
: アスペクト比を保ちながら、指定された領域内に収まるようにサイズ調整 -
BoxFit.fill
: アスペクト比を無視して、指定された領域いっぱいに引き伸ばす
公式ドキュメント: BoxFit enum
レイアウトパターン
1. 縦長カード
Container(
width: 180,
child: ClipRRect(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
child: Image.asset(
imageUrl,
height: 240,
width: double.infinity,
fit: BoxFit.cover,
),
),
)
2. 正方形カード
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
child: Image.asset(
imageUrl,
width: double.infinity,
fit: BoxFit.cover,
),
),
)
3. 横長カード
ClipRRect(
borderRadius: BorderRadius.horizontal(left: Radius.circular(16)),
child: Image.asset(
imageUrl,
width: 120,
height: 120,
fit: BoxFit.cover,
),
)
ベストプラクティス
-
画像の最適化
- 適切なサイズの画像を使用
- WebPやPNGなど適切なフォーマットを選択
-
pubspec.yaml
で画像アセットを正しく設定
-
パフォーマンス考慮
-
ClipRRect
は必要な場合のみ使用(パフォーマンスに影響あり) - 大きな画像は必要に応じてキャッシュを検討
-
-
レスポンシブ対応
-
double.infinity
を使用して親ウィジェットに合わせる -
Expanded
やFlexible
を使用して柔軟なレイアウトを実現
-
Discussion