DEV Community

Cover image for Flutter Layout ve UI Widget'ları 💫 🌌 ✨
Gülsen Keskin
Gülsen Keskin

Posted on • Updated on

Flutter Layout ve UI Widget'ları 💫 🌌 ✨

Stack widget

Stack widget widget'ları üst üste yerleştirmek (veya yığmak) için kullanılır.

Image description

Yukarıdaki resimde Güneş, bulutlar ve içerik, birbirinin üzerine yığılmış farklı Widget'lardır. Bunu Flutter'da stack widget kullanarak yaparız.

Stack widget alt widget'larını sol üst köşelerine göre hizalar ve bunları birbiri ardına yan yana yerleştirir. Alignment özelliği ile bir stack'e hangi yöne hizalanacağını söyleyebilirsiniz. Örneğin, hizalamayı horizontal (yatay olarak) ayarlarsanız, stack bir row gibi davranacaktır. Stack widget default olarak column gibi çalışır ve çocukları dikey olarak yerleştirir.

Bir widget'ı konumlandırmak için onu Positioned widget'ına sararsınız.

Image description

Positioned widget şu özelliklere sahiptir: top, left, right, bottom, width, ve height. Bu özelliklerin hiçbirini ayarlamanız gerekmez, ancak en fazla iki yatay özellik(horizontal properties) (left, right, ve width) ve iki dikey özellik (vertical properties) (top, bottom, and height) ayarlayabilirsiniz. Bu özellikler, Flutter'a widget'ın nereye boyanacağını söyler. Çocuklar, RenderStack algoritması tarafından boyanır:

RenderStack algoritması

• Konumlandırılmamış tüm çocukları bir row veya column'un yapacağı şekilde düzenler. Bu, stack'e son boyutunu söyler. Konumlandırılmamış çocuk yoksa stack mümkün olduğunca büyük olmaya çalışır.

top, left vb özelliklerini kullanarak konumlandırılmış tüm alt öğelerini (children) stack'in render box'ına göre düzenler. Konumlandırılmış özellikler(positioned properties), Flutter'a stack'in alt öğelerini paralel kenarına göre nereye yerleştireceğini söyler.
Örneğin, top: 10.0, positioned widget'ı stack box'ının üst kenarından 10.0 piksel içe yerleştirir.

Stack Widget'ları birbirinin üzerine veya açık bir şekilde birbiriyle ilişkili olarak yerleştirmek istiyorsanız, kullanabileceğiniz bir widget'tır.

Table widget

Adından da anlaşılacağı üzere tablo oluşturmak için kullanılır.

Image description

Table gördüğümüz diğer layout widget'larından daha katıdır, çünkü tabloların (teoride) tek bir amacı vardır: verileri okunabilir bir şekilde görüntülemek. Tablolar, widget öğelerini sütunlar ve satırlar halinde sıralar ve tablodaki her hücre, satırdaki diğer tüm hücrelerle aynı yüksekliğe ve sütunundaki her widget'la aynı genişliğe sahiptir. Flutter tablolarında sutunlara genişlik vermek gereklidir ve hiçbir tablo hücresi boş olamaz.

Table(
  columnWidths: Map<int, TableColumnWidth>{},
  border: Border(), 
  defaultColumnWidth: TableColumnWidth(),
  defaultVerticalAlignment:
    TableCellVerticalAlignment(),
  children: List<TableRow>[]
);

Enter fullscreen mode Exit fullscreen mode

Table widget ile çalışırken dikkat etmeniz gerekenler:

columnWidths kullanmak zorunda değilsiniz ama defaultColumnWidth parametresi null olamaz.

defaultColumnWidth default bir argumana sahiptir: FlexColumnWidth(1.0), yani hiçbir şey iletmeniz gerekmez ancak bu değer null olamaz.

defaulColumnWidth: null olması durumunda hata verir. Ancak defaultColumnWidth'in default bir argumanı olduğundan, her sütunun aynı boyutta olmasını ve tablonun mümkün olduğunca fazla genişlikte olmasını istiyorsanız bunu yok sayabilirsiniz.

Column width'leri, bir map'i columnWidths'e ileterek tanımlanır. Map, key olarak column'un index değerini(0'dan başlayarak) ve column'a vermek istediğiniz width değerini alır.

Border isteğe bağlıdır.

TableCellVerticalAlignment, yalnızca row'larınızın children'ları TableCells ise çalışır.

Image description

Aşağıdaki kod, bazı satırların boyutlarını tanımlar. 1. column'un genişliği için bir tanım olmadığına dikkat edin!

Table(
  columnWidths: {
    0: FixedColumnWidth(100.0),

    2: FixedColumnWidth(20.0),

    3: FixedColumnWidth(20.0),
  },
  defaultVerticalAlignment:
        TableCellVerticalAlignment.middle,

  children: <TableRow>[...],
);
Enter fullscreen mode Exit fullscreen mode

TableRow: Bir table row, normal bir row'dan daha basittir ve iki önemli yapılandırması vardır:

• Bir tablodaki her row'un eşit sayıda çocuğu olmalıdır.

• Çocukların alt widget ağaçlarında (sub-widget tree) TableCell'i kullanabilirsiniz, ancak kullanmak zorunda değilsiniz. TableCell, widget ağacında onun üzerinde bir yerde, ata olarak(ancestor) bir TableRow'a sahip olduğu sürece, TableRow'un doğrudan bir çocuğu olmak zorunda değildir.

Dart'ın List.generate() constructor'ından widget'lar oluşturma

Tablonun child özelliğine bir liste iletmek yerine, widget'ları döndüren fonksiyonları, constructor'ları ve class'ları kullanabiliriz.

Table(
  columnWidths: {
    0: FixedColumnWidth(100.0),
    2: FixedColumnWidth(20.0),
    3: FixedColumnWidth(20.0),
  },
  defaultVerticalAlignment: TableCellVerticalAlignment.middle,
  children: List.generate(7, (int index) {

    ForecastDay day = forecast.days[index];

    Weather dailyWeather =
            forecast.days[index].hourlyWeather[0];

    final weatherIcon =
            _getWeatherIcon(dailyWeather);

    return TableRow(
      children: [
        // ....
      ],
    ); // TableRow
  });
); // Table


Enter fullscreen mode Exit fullscreen mode

Burada ki List.generate constructor fonksiyonu, derleme zamanında yürütülür. List.generate'i bir döngü olarak düşünebilirsiniz. İşlevsel olarak şöyle bir şey yazmakla aynıdır:

List<Widget> myList = [];
for (int i = 0; i < 7; i++) {
    myList.add(TableRow(...));
}
Enter fullscreen mode Exit fullscreen mode

Tıpkı for döngüsü gibi, örnek koddaki List.generate constructor'ı , verdiğiniz kodu yedi kez çalıştıracaktır. (Yine de her döngü yinelemesindeki index'in aslında 0-6 arasında olacağını unutmamak önemlidir.)

List.generate bir Dart özelliğidir ve Flutter'a özgü değildir. Yine de bir row, column, table, veya list için birkaç widget oluşturmanız gerektiğinde Flutter'da oldukça kullanışlıdır.

List.generate'i kullanmasaydık, şuna benzeyen daha ayrıntılı bir kod yazmamız gerekirdi:

Table (
  children: [
    TableRow(
       children: [
         TableCell(),
         TableCell(),
         TableCell(),
         TableCell(),
       ]
    ),
    TableRow(
      children: [
        TableCell(),
        TableCell(),
        TableCell(),
        TableCell(),
      ]
    ),
  ]
)
Enter fullscreen mode Exit fullscreen mode

TableCell, Text, Iconve Padding'in tümü kullanılır.

children: List.generate(7, (int index) {
  ForecastDay day = forecast.days[index];
  Weather dailyWeather = forecast.days[index].hourlyWeather[0];
  final weatherIcon = _getWeatherIcon(dailyWeather);
  return TableRow(
    children: [
      TableCell(
        child: const Padding(
          padding: const EdgeInsets.all(4.0),
          child: ColorTransitionText(
            text: DateUtils.weekdays[dailyWeather.dateTime.weekday],
            style: textStyle,
            animation: textColorTween.animate(controller),
          ),
        ),
      ),
      TableCell(
        child: ColorTransitionIcon(
          icon: weatherIcon,
          animation: textColorTween.animate(controller),
          size: 16.0,
        ),
      ),
      TableCell(
        child: ColorTransitionText(
          text: _temperature(day.max).toString(),
          style: textStyle,
          animation: textColorTween.animate(controller),
        ),
      ),
      TableCell(
        child: ColorTransitionText(
          text: _temperature(day.min).toString(),
          style: textStyle,
          animation: textColorTween.animate(controller),
        ),
      ),
    ],
  );
}),
// ...


Enter fullscreen mode Exit fullscreen mode

VerticalDirection.up Sütunun default akışını tersine çevirmek için kullanılır.

TabBar widget

Tablar (sekmeler), mobil uygulamalarda yaygın olarak kullanılan bir UI öğesidir. Flutter Material library, tab'larla çalışmayı oldukça kolaylaştıran yerleşik tab widget'ları sağlar. Yerleşik TabBar widget'ı, alt öğelerini(children) yatay(horizontal) olarak kaydırılabilir bir şekilde görüntüler ve onları "dokunulabilir" (tappable) hale getirir. Tablar en çok, gerçekte gezinmeden farklı sayfalar veya UI componentleri arasında geçiş yapmak için kullanılır. Bu nedenle, tab bar'ın (sekme çubuğunun) alt widget öğelerine iletilen geri arama (callback), en yaygın olarak sayfadaki widget öğelerini değiştirmek için kullanılır.

Aşağıdaki şekil, tabların arkasındaki temel fikri temsil eder. Tab bardaki bir öğeye tıkladığınızda, ilgili tab içeriği değişir.

Image description

TabBar widget'ının iki önemli parçası vardır: çocukların kendileri (kullanıcının seçmek istediği widget'lar) ve TabController- (işlevselliği yöneten).

Image description

TabController widget

Flutter'da, etkileşim içeren birçok widget, olayları yönetmek için ilgili controller'lara(denetleyicilere) sahiptir.
Örneğin, widget'larla birlikte kullanılan ve kullanıcıların girdi yazmasına izin veren bir TextEditingController vardır.
Controller, yeni bir sekme seçildiğinde Flutter uygulamasına bildirimde bulunmaktan sorumludur, böylece uygulamanız istenen içeriği görüntülemek üzere sekmeyi güncelleyebilir. Controller, ağaçta tab bar'dan daha yüksekte oluşturulur ve ardından TabBar widget'ına iletilir. Bu mimari, tab bar'ın parent'ı (üst öğesi) aynı zamanda tab widget'larının parent'ı olduğundan gereklidir.

// Full TimePickerRow widget
class TimePickerRow extends StatefulWidget {
  final List<String> tabItems;
  final ForecastController forecastController;
  final Function onTabChange;
  final int startIndex;

  const TimePickerRow({
    Key key,
    this.forecastController,
    this.tabItems,
    this.onTabChange,
    this.startIndex,
  }) : super(key: key);

  @override
  _TimePickerRowState createState() => _TimePickerRowState();
}

class _TimePickerRowState extends State<TimePickerRow>
    with SingleTickerProviderStateMixin {
  TabController _tabController;
  int activeTabIndex;

  @override
  void initState() {
    _tabController = TabController(
      length: utils.hours.length,
      vsync: this,
       initialIndex: widget.startIndex,
    );
    _tabController.addListener(handleTabChange);
    super.initState();
  }

  void handleTabChange() {
    if (_tabController.indexIsChanging) return;
    widget.onTabChange(_tabController.index);
    setState(() {
      activeTabIndex = _tabController.index;
    });
  }
}

Enter fullscreen mode Exit fullscreen mode

Listener'lar

Listener'lar belirli bir nesne veya nesne türü değil, farklı zaman uyumsuz (asynchronous) fonksiyonlar için kullanılan bir adlandırma kuralıdır.

Listener, genellikle bilinmeyen bir zamanda gerçekleşecek bir olaya yanıt olarak çağrılan bir fonksiyonu ifade eder. Fonksiyonu sadece oturup birinin "Tamam, şimdi yürütme zamanınız" demesini dinlemektir.

Tab controller'ın addListener fonksiyonu, bir kullanıcı sekmeleri değiştirdiğinde çağrılır. Bu size, bir kullanıcı sekmeleri değiştirdiğinde bazı değerleri veya state'i güncelleme şansı verir.

Listener'ların yanı sıra TabController, sekmelerinizi ve ilgili içeriği yönetmenize yardımcı olan alıcılara (getters) sahiptir.

_handleTabChange methodunun içinde, uygulamanızın hangisinin "aktif" sekme olduğunu (o anda ekranda görüntülenen) bildiğinden emin olmak için şöyle bir şey yapabilirsiniz:

int activeTab;
void _handleTabChange() {
  setState(() =>
    this.activeTab = _tabController.index);
}

Enter fullscreen mode Exit fullscreen mode

Hava durumu uygulamasında, tab bar'da günün farklı bir saatine dokunduğunuzda, kullanıcı arayüzü günün o saatindeki hava koşullarıyla yeniden oluşturulur. Bu mümkündür çünkü setState, Flutter'a yeni seçilen tab'ı yeniden oluşturmasını ve bunu yaptığında görüntülemesini söyler. TabController.index getter'ı, o anda aktif olan tab'a başvurur.

TabController hakkında vermek istediğim son not, onu hiçbir zaman doğrudan değiştirmeniz gerekmediğidir. Sekmeler hakkında bilgi almak ve hangi sekmelerin aktif olduğunu güncellemek için kullanılan bir nesnedir. Ancak, yalnızca onunla etkileşime geçmeniz gerekir, onu özel bir sınıfa genişletmeniz(extend) değil.

@override
Widget build(BuildContext context) {
  return TabBar(
    labelColor: Colors.black,
    unselectedLabelColor: Colors.black38,
    unselectedLabelStyle:
        Theme.of(context).textTheme.caption.copyWith(fontSize: 10.0),
    labelStyle:
        Theme.of(context).textTheme.caption.copyWith(fontSize: 12.0),
    indicatorColor: Colors.transparent,
    labelPadding: EdgeInsets.symmetric(horizontal: 48.0, vertical: 8.0),
    controller: _tabController,

    tabs: widget.tabItems.map((t) => Text(t)).toList(),

    isScrollable: true,
  );
}
Enter fullscreen mode Exit fullscreen mode

Default olarak TabBar'daki sekmeler kaydırılmaz ancak isScrollable özelliğini true olarak ayarladığınızda kaydırılabilir hale gelir.

• Tabları kullanmak, bir TabController ve children widget'ları gerektirir. Çocuklar, görüntülenen ve dokunulabilen widget'lardır.

• Tab bar'daki bir widget'a dokunulduğunda sekmeleri değiştirme işlevi, bir callback yoluyla yapılır. Callback, Flutter'a ne zaman yeni bir sekme oluşturacağını söylemek için TabController tarafından gösterilen özellikleri kullanmalıdır.

ListView ve builder'lar
ListView widget'ı bir column veya row gibidir, çünkü alt widget'larını bir satırda görüntüler. Önemli olan nokta, kaydırılabilir olmasıdır. Çocuk sayısı bilinmediğinde yaygın olarak kullanılır. ListView, doğrusal olarak düzenlenmiş kaydırılabilir bir widget listesidir.

ListView, liste içeriğine göre seçim yapmaya olanak sağlayan birkaç farklı constructor'a sahiptir.

Gösterilecek statik, az sayıda öğeniz varsa, default constructor ile bir ListView oluşturabilirsiniz ve bu, bir satır veya sütuna çok benzer bir kodla oluşturulacaktır ve en performanslı seçenektir, ancak listeye koyacak onlarca veya yüzlerce öğeniz veya bilinmeyen sayıda öğeniz varsa ideal olmayabilir.

Builder pattern Flutter'ın her yerinde bulunur ve esas olarak Flutter'a gerektiğinde widget'lar oluşturmasını söyler. Default ListView constructor'ı FLutter'a çocukları bir kere de oluşturmasını söyler. ListView.builder constructor'ı ise itemBuilder özelliğinde bir callback alır ve bu callback(geri arama) bir widget döndürür. Bu oluşturucu, listenizde görüntülenecek çok fazla (veya sonsuz) sayıda liste öğeniz varsa, Flutter'ı öğeleri oluşturma konusunda daha akıllı hale getirir ve yalnızca ekranda görünen öğeler oluşturulur.

Temelde sonsuz bir tweet listesi olan Twitter gibi bir sosyal medya uygulaması hayal edin. Sonsuz sayıda tweet olduğu için, her state değiştiğinde o listedeki tüm tweet'leri oluşturmak mümkün olmazdı.

Expanded(
  child: ListView.builder(
    shrinkWrap: true,
    itemCount: allAddedCities.length,
    itemBuilder: (BuildContext context, int index) {
      final City city = allAddedCities[index];
        return Dismissible(
          // ...
          child: CheckboxListTile(
            value: city.active,
            title: Text(city.name),
            onChanged: (bool b) =>
                _handleCityActiveChange(b, city),
          ),
        );
      },
    ),
);

Enter fullscreen mode Exit fullscreen mode

ListView.separated, ListView.builder'a benzer, ancak iki builder methodu kullanır: biri liste öğelerini oluşturur , ikincisi ise liste öğeleri arasına yerleştirilmiş bir separator (ayırıcı) oluşturur.

ListView.custom: Bazı liste öğelerinin belirli bir widget ve diğer liste öğelerinin tamamen farklı bir widget olduğu durumlarda custom list view kullanılır.

Resource: Flutter in Action chapter 4

Top comments (0)