Flutter: Card Widget ve Tasarım Kullanımı
Son güncelleme:
5 dk okuma

Card, birbiriyle ilgili bilgileri hafif bir gölge ve yuvarlak köşelerle tek bir yüzeyde toplayan Material Design widget'ıdır. Bir ürün, bir haber, bir kullanıcı özeti gibi "kendi başına bir bütün" olan içerikleri arka plandan ayırmak için kullanırız. Kendi boyutu yoktur; içine koyduğumuz çocuğa göre şekillenir, bu yüzden hemen her zaman Padding, Column ya da ListTile ile birlikte görülür.
Canlı Demo
Aşağıdaki interaktif örnekte bu widget'ı deneyebilirsiniz:
💡 Eğer yukarıdaki örnek açılmazsa, DartPad linkine tıklayarak yeni sekmede çalıştırabilirsiniz.
Temel Kullanım
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Text('Kart İçeriği'),
),
)Card içeriğine kendiliğinden iç boşluk vermez; Padding eklemezsek yazı kartın kenarına yapışır. Dış tarafta ise varsayılan olarak her yönden küçük bir margin bırakır, bu sayede alt alta dizilen kartlar birbirine değmez.
Önemli Özellikler
| Özellik | Açıklama |
|---|---|
elevation |
Gölge yüksekliği |
shadowColor |
Gölge rengi |
shape |
Kart şekli (köşe yuvarlaklığı, kenarlık) |
color |
Arka plan rengi |
margin |
Dış boşluk |
clipBehavior |
İçeriğin kart şekline göre kırpılıp kırpılmayacağı |
Material 3 Kart Türleri
Material 3 üç kart türü tanımlar ve Flutter'da üçünün de hazır constructor'ı vardır:
// Elevated: hafif gölgeli, varsayılan kart
Card(child: ...)
// Filled: gölgesiz, dolgu rengiyle ayrışır
Card.filled(child: ...)
// Outlined: gölgesiz, ince kenarlıklı
Card.outlined(child: ...)Hangisini seçeceğimiz arka plana ve kartın önemine bağlı. Gölgeli kart en dikkat çekici olandır; çok sayıda kartın alt alta geldiği kalabalık listelerde Card.outlined ya da Card.filled daha sakin bir görüntü verir. Renkleri temadaki ColorScheme'den aldıkları için açık ve koyu temada kendiliğinden doğru görünürler.
Özelleştirilmiş Card
Hazır türler yetmediğinde shape, elevation ve color ile ince ayar yapabiliriz:
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
color: Theme.of(context).colorScheme.surfaceContainerHigh,
margin: const EdgeInsets.all(8),
child: ...
)Kenarlığı kendimiz tanımlamak istersek shape içinde side kullanırız. Bu, Card.outlined'ın elle yazılmış hâlidir ve kenarlık rengini ya da kalınlığını değiştirmek gerektiğinde işe yarar:
Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: Theme.of(context).colorScheme.outlineVariant),
),
child: ...
)Resimli Card ve clipBehavior
Card'ın köşeleri yuvarlaktır ama varsayılan clipBehavior değeri Clip.none'dır; yani kart çocuğunu kırpmaz. Üstüne tam genişlikte bir resim koyduğumuzda resmin keskin köşeleri kartın yuvarlak köşelerinden dışarı taşar. Clip.antiAlias bunu düzeltir:
Card(
clipBehavior: Clip.antiAlias, // Resmin köşeleri kart şekline göre kırpılsın
child: Column(
children: [
Image.network(
'https://example.com/image.jpg',
height: 150,
width: double.infinity,
fit: BoxFit.cover,
),
const Padding(
padding: EdgeInsets.all(16),
child: Text('Kart Başlığı'),
),
],
),
)Kırpma ücretsiz bir işlem olmadığı için Flutter bunu varsayılan olarak kapalı tutar. İhtiyaç olan kartta açmak yeterli. Resimleri projeye eklemenin yolunu assets ve Image.asset yazısında anlattım.
ListTile ile Kullanım
Tek satırlık bilgi kartları için en az kodla en düzgün sonucu ListTile verir:
Card(
child: ListTile(
leading: const CircleAvatar(child: Icon(Icons.person)),
title: const Text('Kullanıcı Adı'),
subtitle: const Text('Alt başlık'),
trailing: const Icon(Icons.chevron_right),
onTap: () {},
),
)InkWell ile Tıklanabilir Card
Card'ın kendi onTap parametresi yoktur. Tıklanabilir yapmak için içine InkWell koyarız; Card bir Material yüzeyi olduğu için dalga (ripple) efekti doğrudan kartın üzerinde görünür:
Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: () {
print('Kart tıklandı');
},
child: const Padding(
padding: EdgeInsets.all(16),
child: Text('Tıklanabilir Kart'),
),
),
)Buradaki clipBehavior yine önemli: o olmazsa ripple dikdörtgen olarak yayılır ve kartın yuvarlak köşelerinden taşar. InkWell ile GestureDetector arasındaki farkı GestureDetector yazısında karşılaştırdım.
Ne Zaman Kullanılır, Ne Zaman Kullanılmaz?
Card, ekranda birden fazla bağımsız içerik parçası olduğunda ve bunların birbirinden ayrışması gerektiğinde doğru tercihtir: ürün ızgarası, haber akışı, panodaki özet kutuları.
Şu durumlarda başka bir çözüm daha iyi olur:
- Basit, tek tip liste satırları: Ayarlar ya da kişi listesi gibi ekranlarda her satırı karta sarmak görüntüyü ağırlaştırır.
ListTileve arayaDivideryeterlidir. - Gradyan, özel gölge ya da Material dışı bir görünüm: Card'ın
colorparametresi tek renk alır. Gradyan içinContainerveBoxDecorationkullanmak gerekir. - Sayfanın tamamını ya da büyük bir bölümünü kaplayan alan: Kart, içeriği arka plandan ayırmak içindir; her şey kartsa hiçbir şey ayrışmaz. Kart içinde kart kullanmak da aynı sebeple kaçınılması gereken bir desendir.
Sık Yapılan Hatalar
1. Resmin köşelerinin karttan taşması
Belirti: Kartın alt köşeleri yuvarlak, resmin olduğu üst köşeleri keskin. Yukarıda anlattığım gibi sebep varsayılan Clip.none değeridir; Card'a clipBehavior: Clip.antiAlias eklemek çözer. Resmi ayrıca ClipRRect ile sarmaya gerek kalmaz.
2. Card'ı dışarıdan GestureDetector ile sarmak
Belirti: Kart tıklanıyor ama hiçbir görsel tepki yok, kullanıcı dokunuşunun algılandığından emin olamıyor. InkWell'i dışarı koymak da çözüm değildir; ripple bu kez kartın arkasında kalır. Doğru sıra Card > InkWell > içerik şeklindedir.
3. Rengi sabit vermek
Belirti: Açık temada güzel görünen kart, koyu temada bembeyaz kalıyor ve içindeki açık renkli yazı okunmuyor. Sebep color: Colors.white gibi sabit bir değer. Rengi hiç vermemek ya da Theme.of(context).colorScheme içinden seçmek kartın iki temada da doğru görünmesini sağlar. Renk rollerinin ve açık-koyu tema kurulumunun ayrıntısı Theme ve ThemeData yazısında.
4. Row içinde genişliği belirsiz kart
Belirti: İçinde ListTile olan bir kartı Row içine koyduğumuzda sonsuz genişlikle ilgili bir layout hatası alırız. Row çocuklarına genişlik sınırı koymaz, ListTile ise bulabildiği tüm genişliği ister. Kartı Expanded ile sarmak ya da SizedBox ile genişlik vermek gerekir. Aynı durum yatay kayan listelerde de geçerlidir.
Mini Senaryo: Ürün Kartı
Bir alışveriş uygulamasının ürün ızgarasını düşünelim. Her kart bir görsel, iki satıra kadar başlık ve fiyat gösteriyor; karta dokununca ürün detayına gidiliyor. Bu yapıyı tekrar tekrar kullanacağımız için ayrı bir widget'a çıkarıyoruz:
class ProductCard extends StatelessWidget {
const ProductCard({
super.key,
required this.title,
required this.price,
required this.imageUrl,
required this.onTap,
});
final String title;
final String price;
final String imageUrl;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: Image.network(imageUrl, fit: BoxFit.cover),
),
Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: textTheme.titleMedium,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(price, style: textTheme.bodyMedium),
],
),
),
],
),
),
);
}
}Birkaç bilinçli karar var. Resme sabit yükseklik vermek yerine AspectRatio kullandık; kart dar ya da geniş olsa da görsel oranını korur. Başlığı maxLines ve ellipsis ile sınırladık, çünkü uzun bir ürün adı ızgaradaki kartların boyunu bozabilir. Yazı stillerini elle yazmak yerine temadan aldık. Bir ayrıntı daha: ripple Material yüzeyine çizildiği için resmin üstünde görünmez, yalnızca yazı alanında görünür. Resmin üzerinde de görünmesi istenirse Image.network yerine Ink.image kullanılabilir.
Bu kartı bir GridView içinde kullanmak ürün ızgarasını tamamlar. Tekrar eden arayüz parçalarını böyle ayrı sınıflara çıkarmanın mantığını kendi widget'larımızı oluşturma yazısında ayrıntılı anlattım.
Sık Sorulan Sorular
Card ile Container arasındaki fark nedir?
Card, Material Design'a uygun gölge, köşe yuvarlaklığı ve tema renkleriyle hazır gelen bir yüzeydir ve InkWell için gereken Material atasını sağlar. Container ise genel amaçlı bir kutudur; gradyan ya da tamamen özel bir dekorasyon gerektiğinde tercih edilir.
Card nasıl tıklanabilir yapılır?
Card'ın onTap parametresi yoktur. İçeriği bir InkWell ile sarıp Card'a clipBehavior: Clip.antiAlias vermek, ripple efektinin kartın köşelerine uyarak çalışmasını sağlar. İçerik tek satırsa ListTile'ın kendi onTap parametresi de kullanılabilir.
Card.filled ve Card.outlined ne zaman kullanılır?
İkisi de gölgesiz Material 3 kart türleridir. Çok sayıda kartın bir arada olduğu yoğun ekranlarda ya da kartın arka plandan hafifçe ayrışmasının yeterli olduğu durumlarda gölgeli varsayılan karta göre daha sakin bir görüntü verirler.
Card içindeki resmin köşeleri neden yuvarlanmıyor?
Card varsayılan olarak çocuğunu kırpmaz, çünkü clipBehavior değeri Clip.none'dır. Card'a clipBehavior: Clip.antiAlias eklendiğinde resim kartın şekline göre kırpılır.
İlgili Yazılar
Flutter: AppBar Widget ve Özelleştirme
Flutter AppBar rehberi: title, leading, actions ve bottom kullanımı, Material 3'te kaydırınca renk değişimi ve sık yapılan hataların çözümü.
Flutter: BottomSheet ve showModalBottomSheet Kullanımı
Flutter'da showModalBottomSheet ile değer döndürmek, isScrollControlled, useSafeArea, showDragHandle, klavye boşluğu, DraggableScrollableSheet ve kalıcı sheet.
Flutter: AlertDialog Kullanımı ve Özelleştirme
Flutter AlertDialog ile onay alma, girdi toplama ve adaptive dialog; context.mounted, StatefulBuilder ve taşma hatalarının çözümü.