Ahmet Balaman LogoAhmet Balaman

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

personAhmet Balaman
calendar_today
FlutterWidgetStatelessWidgetStatefulWidgetConstructor

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:

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!