İçeriğe geç / Skip to content / Zum Inhalt
Ahmet Balaman LogoAhmet Balaman

Flutter: Kendi Widget'larımızı Oluşturmak

Ahmet Balaman
FlutterWidgetStatelessWidgetStatefulWidgetConstructor
Flutter: Kendi Widget'larımızı Oluşturmak

Flutter'da kendi widget'larınızı oluşturarak kodunuzu daha modüler, okunabilir ve yeniden kullanılabilir hale getirebilirsiniz. Constructor'ları kullanarak parametreler alıp özelleştirilebilir widget'lar yapabilirsiniz.

Neden Özel Widget Oluşturmalıyız?

  1. Kod Tekrarını Önleme: Aynı yapıyı farklı yerlerde kullanmak için
  2. Okunabilirlik: Karmaşık yapıları basit isimlerle ifade etmek
  3. Bakım Kolaylığı: Tek yerden değişiklik yapabilmek
  4. Test Edilebilirlik: Widget'ları ayrı ayrı test edebilmek

Canlı Demo: Özel Widget Oluşturma

Kendi widget'larınızı nasıl oluşturacağınızı interaktif olarak öğrenin:

💡 Eğer yukarıdaki örnek açılmazsa, DartPad linkine tıklayarak yeni sekmede çalıştırabilirsiniz.

StatelessWidget ile Özel Widget

StatelessWidget, durumu olmayan (state'i değişmeyen) widget'lar için kullanılır.

Temel Yapı

class MyCustomWidget extends StatelessWidget {
  const MyCustomWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(16),
      child: Text('Özel Widget\'ım'),
    );
  }
}

Kullanımı

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: MyCustomWidget(),
        ),
      ),
    );
  }
}

Constructor ile Parametreler

Widget'ınızı özelleştirilebilir yapmak için constructor'da parametreler alabilirsiniz.

Basit Parametreler

class CustomButton extends StatelessWidget {
  final String text;
  final Color color;
  final VoidCallback onPressed;

  const CustomButton({
    Key? key,
    required this.text,
    required this.color,
    required this.onPressed,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      style: ElevatedButton.styleFrom(
        backgroundColor: color,
        padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
      ),
      child: Text(text),
    );
  }
}

Kullanımı

CustomButton(
  text: 'Kaydet',
  color: Colors.blue,
  onPressed: () {
    print('Kaydedildi');
  },
)

Required ve Optional Parametreler

Required (Zorunlu) Parametreler

class UserCard extends StatelessWidget {
  final String name;
  final String email;

  const UserCard({
    Key? key,
    required this.name,    // Zorunlu
    required this.email,   // Zorunlu
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(name, style: TextStyle(fontWeight: FontWeight.bold)),
            SizedBox(height: 4),
            Text(email, style: TextStyle(color: Colors.grey)),
          ],
        ),
      ),
    );
  }
}

Optional (İsteğe Bağlı) Parametreler

class CustomCard extends StatelessWidget {
  final String title;
  final String? subtitle;  // İsteğe bağlı (nullable)
  final Color color;

  const CustomCard({
    Key? key,
    required this.title,
    this.subtitle,           // İsteğe bağlı
    this.color = Colors.blue, // Varsayılan değer
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      color: color,
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(title, style: TextStyle(fontSize: 18)),
            if (subtitle != null) // Varsa göster
              Text(subtitle!, style: TextStyle(fontSize: 14)),
          ],
        ),
      ),
    );
  }
}

Kullanımı:

CustomCard(title: 'Başlık')  // Subtitle yok

CustomCard(
  title: 'Başlık',
  subtitle: 'Alt başlık',
  color: Colors.green,
)

Widget Composition (Bileşim)

Karmaşık widget'ları daha küçük parçalara ayırabilirsiniz.

Örnek: Blog Post Card

class BlogPostCard extends StatelessWidget {
  final String title;
  final String author;
  final String date;
  final String excerpt;

  const BlogPostCard({
    Key? key,
    required this.title,
    required this.author,
    required this.date,
    required this.excerpt,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: EdgeInsets.all(8),
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildHeader(),
            SizedBox(height: 12),
            _buildTitle(),
            SizedBox(height: 8),
            _buildExcerpt(),
          ],
        ),
      ),
    );
  }

  Widget _buildHeader() {
    return Row(
      children: [
        CircleAvatar(
          child: Text(author[0]),
        ),
        SizedBox(width: 8),
        Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(author, style: TextStyle(fontWeight: FontWeight.bold)),
            Text(date, style: TextStyle(fontSize: 12, color: Colors.grey)),
          ],
        ),
      ],
    );
  }

  Widget _buildTitle() {
    return Text(
      title,
      style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
    );
  }

  Widget _buildExcerpt() {
    return Text(
      excerpt,
      style: TextStyle(color: Colors.grey[700]),
      maxLines: 3,
      overflow: TextOverflow.ellipsis,
    );
  }
}

Kullanımı

BlogPostCard(
  title: 'Flutter Widget\'ları',
  author: 'Ahmet Balaman',
  date: '29 Ekim 2025',
  excerpt: 'Flutter\'da widget oluşturma teknikleri...',
)

Child Parameter ile Esnek Widget'lar

Widget'ınıza başka widget'lar geçirilebilmesini sağlamak:

class CustomContainer extends StatelessWidget {
  final Widget child;
  final Color backgroundColor;
  final double borderRadius;

  const CustomContainer({
    Key? key,
    required this.child,
    this.backgroundColor = Colors.white,
    this.borderRadius = 8.0,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: backgroundColor,
        borderRadius: BorderRadius.circular(borderRadius),
        boxShadow: [
          BoxShadow(
            color: Colors.black12,
            blurRadius: 4,
            offset: Offset(0, 2),
          ),
        ],
      ),
      child: child,
    );
  }
}

Kullanımı:

CustomContainer(
  backgroundColor: Colors.blue.shade50,
  child: Text('İçerik'),
)

CustomContainer(
  child: Column(
    children: [
      Text('Başlık'),
      Text('Alt başlık'),
    ],
  ),
)

Pratik Örnekler

Örnek 1: Icon ile Buton

class IconTextButton extends StatelessWidget {
  final IconData icon;
  final String text;
  final VoidCallback onTap;
  final Color? color;

  const IconTextButton({
    Key? key,
    required this.icon,
    required this.text,
    required this.onTap,
    this.color,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final buttonColor = color ?? Theme.of(context).primaryColor;
    
    return InkWell(
      onTap: onTap,
      child: Container(
        padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
        decoration: BoxDecoration(
          color: buttonColor.withOpacity(0.1),
          borderRadius: BorderRadius.circular(8),
        ),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(icon, color: buttonColor),
            SizedBox(width: 8),
            Text(text, style: TextStyle(color: buttonColor)),
          ],
        ),
      ),
    );
  }
}

Örnek 2: Loading Widget

class LoadingWidget extends StatelessWidget {
  final String? message;
  final Color? color;

  const LoadingWidget({
    Key? key,
    this.message,
    this.color,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          CircularProgressIndicator(
            valueColor: AlwaysStoppedAnimation<Color>(
              color ?? Colors.blue,
            ),
          ),
          if (message != null) ...[
            SizedBox(height: 16),
            Text(message!),
          ],
        ],
      ),
    );
  }
}

Örnek 3: Boş Durum Widget'ı

class EmptyStateWidget extends StatelessWidget {
  final IconData icon;
  final String title;
  final String message;
  final String? buttonText;
  final VoidCallback? onButtonPressed;

  const EmptyStateWidget({
    Key? key,
    required this.icon,
    required this.title,
    required this.message,
    this.buttonText,
    this.onButtonPressed,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Padding(
        padding: EdgeInsets.all(24),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(icon, size: 64, color: Colors.grey),
            SizedBox(height: 16),
            Text(
              title,
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 8),
            Text(
              message,
              textAlign: TextAlign.center,
              style: TextStyle(color: Colors.grey[600]),
            ),
            if (buttonText != null && onButtonPressed != null) ...[
              SizedBox(height: 24),
              ElevatedButton(
                onPressed: onButtonPressed,
                child: Text(buttonText!),
              ),
            ],
          ],
        ),
      ),
    );
  }
}

StatefulWidget ile Özel Widget

Durumu değişen widget'lar için StatefulWidget kullanılır:

class CounterWidget extends StatefulWidget {
  final int initialValue;

  const CounterWidget({
    Key? key,
    this.initialValue = 0,
  }) : super(key: key);

  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  late int counter;

  @override
  void initState() {
    super.initState();
    counter = widget.initialValue;
  }

  void _increment() {
    setState(() {
      counter++;
    });
  }

  void _decrement() {
    setState(() {
      counter--;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        IconButton(
          icon: Icon(Icons.remove),
          onPressed: _decrement,
        ),
        Text(
          '$counter',
          style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
        ),
        IconButton(
          icon: Icon(Icons.add),
          onPressed: _increment,
        ),
      ],
    );
  }
}

Best Practices (En İyi Uygulamalar)

  1. const Constructor Kullanın:
const MyWidget({Key? key}) : super(key: key);
  1. Anlamlı İsimler Verin:
// ✅ İyi
class UserProfileCard extends StatelessWidget { ... }

// ❌ Kötü
class Widget1 extends StatelessWidget { ... }
  1. Tek Sorumluluk İlkesi:
    Her widget tek bir işi yapmalı.

  2. Dokümantasyon Yazın:

/// Kullanıcı bilgilerini gösteren kart widget'ı.
/// 
/// [name] ve [email] parametreleri zorunludur.
/// [avatarUrl] parametresi opsiyoneldir.
class UserCard extends StatelessWidget { ... }
  1. Private Helper Metodlar:
Widget _buildHeader() { ... }  // _ ile başlatın

Özet

Özel widget oluşturmak:

  • StatelessWidget veya StatefulWidget extend edilir
  • Constructor'da parametreler alınır
  • required ile zorunlu parametreler belirlenir
  • Varsayılan değerler atanabilir
  • child parametresi ile esnek widget'lar yapılır
  • Composition ile karmaşık yapılar basitleştirilir

Faydaları:

  • Kod tekrarını önler
  • Okunabilirliği artırır
  • Bakımı kolaylaştırır
  • Test edilebilirliği artırır
  • Yeniden kullanılabilirlik sağlar

Flutter'da widget oluşturmak, temiz ve sürdürülebilir kod yazmanın temelidir. Her tekrar eden yapıyı widget'a çevirmeyi düşünün!

Yorumlar