Ahmet Balaman LogoAhmet Balaman

Flutter: LayoutBuilder ile Ekran Boyutuna Göre Tasarım

personAhmet Balaman
calendar_today
FlutterLayoutBuilderResponsiveAdaptiveUI

Flutter her ne kadar iOS ve Android'de çalışabiliyor olsa da, varsayılan olarak dinamik değildir. Farklı ekran boyutlarına uyum sağlamak için LayoutBuilder gibi araçlar kullanarak responsive tasarımlar oluşturmalıyız.

Çoklu Ekran Desteği Neden Önemli?

Flutter uygulamaları şu cihazlarda çalışabilir:

  • Küçük ekranlı telefonlar (iPhone SE)
  • Büyük ekranlı telefonlar (iPhone Pro Max)
  • Tabletler (iPad)
  • Masaüstü bilgisayarlar
  • Web tarayıcıları

Her ekran boyutunda aynı sabit değerleri kullanırsanız, bazı cihazlarda tasarımınız bozuk görünebilir.

Canlı Demo: LayoutBuilder Responsive Tasarım

LayoutBuilder ile responsive tasarım örneklerini dikey çizgiden genişleterek deneyin:

LayoutBuilder Nedir?

LayoutBuilder, parent widget'ın boyut kısıtlamalarını (constraints) sağlayan bir widget'tır. Bu bilgi ile ekran boyutuna göre farklı tasarımlar oluşturabilirsiniz.

Temel Kullanım

LayoutBuilder(
  builder: (BuildContext context, BoxConstraints constraints) {
    return Container(
      width: constraints.maxWidth,
      height: constraints.maxHeight,
      child: Text('Mevcut genişlik: ${constraints.maxWidth}'),
    );
  },
)

BoxConstraints Özellikleri

  • maxWidth: Mevcut maksimum genişlik
  • maxHeight: Mevcut maksimum yükseklik
  • minWidth: Minimum genişlik
  • minHeight: Minimum yükseklik

1. Yöntem: Her Ekrana Göre Tasarım Yapmak

Ekran boyutunu kontrol ederek farklı layout'lar gösterebilirsiniz.

Breakpoint'ler Tanımlama

class ScreenSize {
  static bool isSmall(double width) => width < 600;
  static bool isMedium(double width) => width >= 600 && width < 900;
  static bool isLarge(double width) => width >= 900;
}

Responsive Widget Örneği

class ResponsiveLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth < 600) {
          // Küçük ekran (Mobil)
          return _buildMobileLayout();
        } else if (constraints.maxWidth < 900) {
          // Orta ekran (Tablet)
          return _buildTabletLayout();
        } else {
          // Büyük ekran (Desktop)
          return _buildDesktopLayout();
        }
      },
    );
  }

  Widget _buildMobileLayout() {
    return Column(
      children: [
        AppBar(title: Text('Mobil Görünüm')),
        Expanded(
          child: ListView(
            children: [
              _buildCard('Öğe 1'),
              _buildCard('Öğe 2'),
              _buildCard('Öğe 3'),
            ],
          ),
        ),
      ],
    );
  }

  Widget _buildTabletLayout() {
    return Row(
      children: [
        // Yan panel
        Container(
          width: 200,
          color: Colors.grey[200],
          child: ListView(
            children: [
              ListTile(title: Text('Menü 1')),
              ListTile(title: Text('Menü 2')),
            ],
          ),
        ),
        // Ana içerik
        Expanded(
          child: Column(
            children: [
              AppBar(title: Text('Tablet Görünüm')),
              Expanded(
                child: GridView.count(
                  crossAxisCount: 2,
                  children: [
                    _buildCard('Öğe 1'),
                    _buildCard('Öğe 2'),
                    _buildCard('Öğe 3'),
                    _buildCard('Öğe 4'),
                  ],
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }

  Widget _buildDesktopLayout() {
    return Row(
      children: [
        // Sol panel
        Container(
          width: 250,
          color: Colors.grey[200],
          child: ListView(
            children: [
              ListTile(title: Text('Menü 1')),
              ListTile(title: Text('Menü 2')),
              ListTile(title: Text('Menü 3')),
            ],
          ),
        ),
        // Ana içerik
        Expanded(
          child: Column(
            children: [
              AppBar(title: Text('Desktop Görünüm')),
              Expanded(
                child: GridView.count(
                  crossAxisCount: 3,
                  children: [
                    _buildCard('Öğe 1'),
                    _buildCard('Öğe 2'),
                    _buildCard('Öğe 3'),
                    _buildCard('Öğe 4'),
                    _buildCard('Öğe 5'),
                    _buildCard('Öğe 6'),
                  ],
                ),
              ),
            ],
          ),
        ),
        // Sağ panel
        Container(
          width: 250,
          color: Colors.grey[100],
          child: Column(
            children: [
              Padding(
                padding: EdgeInsets.all(16),
                child: Text('Yan Panel'),
              ),
            ],
          ),
        ),
      ],
    );
  }

  Widget _buildCard(String title) {
    return Card(
      margin: EdgeInsets.all(8),
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Center(child: Text(title)),
      ),
    );
  }
}

Resim Çözünürlüğü Seçimi

Ekran boyutuna göre farklı çözünürlükteki resimleri kullanabilirsiniz:

Resimleri İsimlendirme

assets/
  └── images/
      ├── banner_low.jpg      // Küçük ekranlar için
      ├── banner_medium.jpg   // Orta ekranlar için
      └── banner_high.jpg     // Büyük ekranlar için

Kullanımı

LayoutBuilder(
  builder: (context, constraints) {
    String imagePath;
    
    if (constraints.maxWidth < 600) {
      imagePath = 'assets/images/banner_low.jpg';
    } else if (constraints.maxWidth < 1200) {
      imagePath = 'assets/images/banner_medium.jpg';
    } else {
      imagePath = 'assets/images/banner_high.jpg';
    }
    
    return Image.asset(
      imagePath,
      width: constraints.maxWidth,
      fit: BoxFit.cover,
    );
  },
)

Fayda: Küçük ekranlarda küçük resim yükleyerek performans artışı sağlarsınız.

Column Count Değiştirme

GridView'da ekran genişliğine göre sütun sayısını ayarlama:

LayoutBuilder(
  builder: (context, constraints) {
    int crossAxisCount;
    
    if (constraints.maxWidth < 600) {
      crossAxisCount = 2; // Mobilde 2 sütun
    } else if (constraints.maxWidth < 900) {
      crossAxisCount = 3; // Tablette 3 sütun
    } else {
      crossAxisCount = 4; // Desktop'ta 4 sütun
    }
    
    return GridView.builder(
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: crossAxisCount,
        crossAxisSpacing: 8,
        mainAxisSpacing: 8,
      ),
      itemCount: 20,
      itemBuilder: (context, index) {
        return Card(
          child: Center(child: Text('Öğe ${index + 1}')),
        );
      },
    );
  },
)

2. Yöntem: Tasarımı Ekrana Göre Oranlama

Sabit değerler yerine yüzdelik oranlar kullanarak responsive tasarım:

LayoutBuilder(
  builder: (context, constraints) {
    final screenWidth = constraints.maxWidth;
    final screenHeight = constraints.maxHeight;
    
    return Container(
      width: screenWidth * 0.8,  // Ekranın %80'i genişliğinde
      height: screenHeight * 0.5, // Ekranın %50'si yüksekliğinde
      color: Colors.blue,
      child: Center(
        child: Text(
          'Ekranın %80 x %50\'si',
          style: TextStyle(color: Colors.white),
        ),
      ),
    );
  },
)

Padding'i Oranlama

LayoutBuilder(
  builder: (context, constraints) {
    final padding = constraints.maxWidth * 0.05; // Ekranın %5'i
    
    return Padding(
      padding: EdgeInsets.all(padding),
      child: Text('Oransal padding'),
    );
  },
)

Font Boyutunu Oranlama

LayoutBuilder(
  builder: (context, constraints) {
    final fontSize = constraints.maxWidth * 0.05; // Genişliğin %5'i
    
    return Text(
      'Responsive metin',
      style: TextStyle(fontSize: fontSize),
    );
  },
)

MediaQuery ile Birlikte Kullanım

LayoutBuilder yerel constraints verir, MediaQuery tüm ekran bilgisini verir:

LayoutBuilder(
  builder: (context, constraints) {
    final mediaQuery = MediaQuery.of(context);
    final screenWidth = mediaQuery.size.width;
    final screenHeight = mediaQuery.size.height;
    final localWidth = constraints.maxWidth;
    
    return Column(
      children: [
        Text('Ekran genişliği: $screenWidth'),
        Text('Local genişlik: $localWidth'),
        Text('Ekran yüksekliği: $screenHeight'),
      ],
    );
  },
)

Ne zaman hangisi?

  • LayoutBuilder: Parent widget'a göre boyutlandırma
  • MediaQuery: Tüm ekrana göre boyutlandırma

Orientation Kontrolü

Dikey (portrait) veya yatay (landscape) modda farklı tasarımlar:

LayoutBuilder(
  builder: (context, constraints) {
    final isPortrait = constraints.maxHeight > constraints.maxWidth;
    
    if (isPortrait) {
      return Column(
        children: [
          Text('Dikey Mod'),
          Expanded(child: Container(color: Colors.blue)),
        ],
      );
    } else {
      return Row(
        children: [
          Text('Yatay Mod'),
          Expanded(child: Container(color: Colors.green)),
        ],
      );
    }
  },
)

Responsive Widget Helper

Kendi responsive widget'ınızı oluşturabilirsiniz:

class ResponsiveBuilder extends StatelessWidget {
  final Widget Function(BuildContext context, ScreenType screenType) builder;

  const ResponsiveBuilder({Key? key, required this.builder}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        ScreenType screenType;
        
        if (constraints.maxWidth < 600) {
          screenType = ScreenType.mobile;
        } else if (constraints.maxWidth < 900) {
          screenType = ScreenType.tablet;
        } else {
          screenType = ScreenType.desktop;
        }
        
        return builder(context, screenType);
      },
    );
  }
}

enum ScreenType { mobile, tablet, desktop }

Kullanımı

ResponsiveBuilder(
  builder: (context, screenType) {
    switch (screenType) {
      case ScreenType.mobile:
        return Text('Mobil Görünüm');
      case ScreenType.tablet:
        return Text('Tablet Görünüm');
      case ScreenType.desktop:
        return Text('Desktop Görünüm');
    }
  },
)

Performans İpuçları

  1. Gereksiz rebuild'lerden kaçının: LayoutBuilder her boyut değişiminde rebuild eder.

  2. Const kullanın: Değişmeyen widget'ları const yapın.

  3. Heavy computation'ı önleyin: Builder içinde ağır hesaplamalardan kaçının.

  4. Cache kullanın: Hesaplanan değerleri cache'leyin.

Özet

LayoutBuilder ile responsive tasarım:

  1. Ekran boyutunu algılama: constraints.maxWidth ve constraints.maxHeight
  2. Breakpoint'lerle tasarım: Mobil, tablet, desktop
  3. Oransal boyutlandırma: Yüzdelik değerler kullanma
  4. Farklı resim çözünürlükleri: Performans optimizasyonu
  5. Dinamik layout: Column count, padding, font size

Best Practices:

  • Standart breakpoint'ler kullanın (600px, 900px)
  • Oransal değerler tercih edin
  • Her cihazda test edin
  • Performansa dikkat edin

Alternatif Paketler:

  • responsive_framework: Hazır responsive çözümler
  • flutter_screenutil: Ekran adaptasyonu
  • sizer: Responsive boyutlandırma

LayoutBuilder, Flutter'da profesyonel ve her cihazda mükemmel görünen uygulamalar yapmanın temelidir!