Berkibar di bawah kap

Halo semuanya! Nama saya Mikhail Zotiev, saya bekerja sebagai pengembang Flutter di Surf. Saya, seperti mungkin sebagian besar pengembang lain yang bekerja dengan Flutter, kebanyakan menyukai betapa mudahnya membuat aplikasi yang indah dan nyaman dengan bantuannya. Butuh sedikit waktu untuk masuk ke pengembangan Flutter. Saya baru-baru ini bekerja di pengembang game, dan sekarang saya telah sepenuhnya beralih ke pengembangan seluler lintas platform di Flutter.

Apa kesederhanaannya? Dengan selusin widget dasar, Anda dapat membangun antarmuka pengguna yang cukup baik. Dan seiring berjalannya waktu, ketika bagasi yang digunakan cukup layak, tidak mungkin ada tugas yang akan membuat Anda macet: baik itu desain yang tidak biasa atau animasi yang canggih. Dan yang paling menarik - kemungkinan besar Anda dapat menggunakannya tanpa memikirkan pertanyaan: "Bagaimana cara kerjanya?"

Karena Flutter memiliki sumber terbuka, saya memutuskan untuk mencari tahu apa yang ada di bawah tenda (di sisi Dart the Force) dan membaginya dengan Anda.



Widget


Kita semua telah mendengar ungkapan dari tim pengembang kerangka kerja lebih dari sekali: "Semua yang ada di Flutter adalah widget . " Mari kita lihat apakah ini benar-benar demikian. Untuk melakukan ini, kita beralih ke kelas Widget (selanjutnya - widget) dan mulai secara bertahap membiasakan diri dengan konten.

Hal pertama yang akan kita baca di dokumentasi untuk kelas:
Menjelaskan konfigurasi untuk [Elemen].

Ternyata widget itu sendiri hanyalah deskripsi dari beberapa Elemen (selanjutnya - elemen).
Widget adalah hierarki kelas pusat dalam kerangka Flutter. Widget adalah deskripsi abadi dari bagian antarmuka pengguna. Widget dapat digembungkan menjadi elemen, yang mengelola pohon render yang mendasarinya.
Untuk meringkas, frasa "Segala sesuatu di Flutter adalah widget" adalah tingkat minimum memahami bagaimana semuanya diatur untuk menggunakan Flutter. Widget adalah kelas tengah dalam hierarki Flutter. Pada saat yang sama, ada banyak mekanisme tambahan di sekitarnya yang membantu kerangka mengatasi tugasnya.

Jadi, kami belajar beberapa fakta lagi:

  • widget - deskripsi abadi dari bagian antarmuka pengguna;
  • widget dikaitkan dengan beberapa tampilan lanjutan yang disebut elemen;
  • sebuah elemen mengendalikan beberapa entitas pohon render.

Anda pasti memperhatikan hal yang aneh. Antarmuka pengguna dan kekekalan cocok sangat buruk, saya bahkan akan mengatakan bahwa ini adalah konsep yang sepenuhnya tidak kompatibel. Tetapi kami akan kembali ke ini ketika gambar yang lebih lengkap dari perangkat Flutter world akan muncul, tetapi untuk saat ini, kami akan terus berkenalan dengan dokumentasi widget.
Widget itu sendiri tidak memiliki status bisa berubah (semua bidangnya harus final).
Jika Anda ingin mengaitkan status yang dapat diubah dengan widget, pertimbangkan untuk menggunakan [StatefulWidget], yang membuat objek [Status] (melalui [StatefulWidget.createState]) setiap kali itu digembungkan ke dalam elemen dan dimasukkan ke dalam pohon.
Paragraf ini sedikit melengkapi paragraf pertama: jika kita memerlukan konfigurasi yang bisa berubah, kita menggunakan entitas Negara khusus (selanjutnya disebut sebagai negara), yang menggambarkan keadaan widget saat ini. Namun, negara tidak terkait dengan widget, tetapi dengan representasi elemennya.
Widget yang diberikan dapat dimasukkan dalam pohon nol atau lebih banyak kali. Khususnya widget yang diberikan dapat ditempatkan di pohon beberapa kali. Setiap kali widget ditempatkan di pohon, itu digembungkan ke dalam [Elemen], yang berarti widget yang dimasukkan ke pohon beberapa kali akan meningkat beberapa kali.
Widget yang sama dapat dimasukkan dalam pohon widget berkali-kali, atau tidak dimasukkan sama sekali. Tetapi setiap kali widget dimasukkan dalam pohon widget, sebuah elemen dipetakan padanya.

Jadi, pada tahap ini, widget hampir selesai, mari kita simpulkan:

  • widget - kelas pusat hirarki;
  • widget adalah beberapa konfigurasi;
  • widget - deskripsi abadi dari bagian antarmuka pengguna;
  • widget dikaitkan dengan elemen yang mengontrol rendering dalam beberapa cara;
  • status perubahan widget dapat dijelaskan oleh beberapa entitas, tetapi terhubung tidak dengan widget, tetapi dengan elemen yang mewakili widget ini.

Elemen


Dari apa yang kami pelajari, pertanyaan itu memohon, "Apa elemen-elemen ini yang mengatur segalanya?" Lakukan hal yang sama - buka dokumentasi untuk kelas Elemen.
Instansiasi dari [Widget] di lokasi tertentu di pohon.
Elemen adalah beberapa representasi widget di tempat tertentu di pohon.
Widget menjelaskan cara mengonfigurasi subtree tetapi widget yang sama dapat digunakan untuk mengonfigurasi beberapa subtree secara bersamaan karena widget tidak dapat diubah. [Elemen] mewakili penggunaan widget untuk mengonfigurasi lokasi tertentu di pohon. Seiring waktu, widget yang dikaitkan dengan elemen yang diberikan dapat berubah, misalnya, jika widget induk dibangun kembali dan membuat widget baru untuk lokasi ini.
Widget menggambarkan konfigurasi beberapa bagian dari antarmuka pengguna, tetapi seperti yang telah kita ketahui, widget yang sama dapat digunakan di berbagai tempat pohon. Setiap tempat tersebut akan diwakili oleh elemen yang sesuai. Namun seiring waktu, widget yang dikaitkan dengan item dapat berubah. Ini berarti bahwa elemen lebih ulet dan terus digunakan, hanya memperbarui koneksi mereka.

Ini adalah keputusan yang cukup rasional. Seperti yang telah kami definisikan di atas, widget adalah konfigurasi yang tidak dapat diubah yang hanya menggambarkan bagian tertentu dari antarmuka, yang berarti mereka harus sangat ringan. Dan elemen-elemen di area yang kontrolnya jauh lebih berat, tetapi mereka tidak perlu diciptakan kembali.

Untuk memahami bagaimana ini dilakukan, pertimbangkan siklus hidup suatu elemen:

  • Widget.createElement , .
  • mount . .
  • .
  • , (, ), . runtimeType key, . , , .
  • , , , , ( deactivate).
  • , . , , (unmount), .
  • Ketika Anda memasukkan kembali elemen dalam pohon, misalnya, jika elemen atau leluhurnya memiliki kunci global, itu akan dihapus dari daftar elemen tidak aktif, metode aktivasi akan dipanggil, dan objek yang diberikan terkait dengan elemen ini akan kembali tertanam dalam pohon render. Ini berarti bahwa item tersebut akan muncul di layar lagi.

Dalam deklarasi kelas, kita melihat bahwa elemen mengimplementasikan antarmuka BuildContext. BuildContext adalah sesuatu yang mengontrol posisi widget di pohon widget, sebagai berikut dari dokumentasinya. Hampir sama persis dengan deskripsi item. Antarmuka ini digunakan untuk menghindari manipulasi elemen secara langsung, tetapi pada saat yang sama memberikan akses ke metode konteks yang diperlukan. Misalnya, findRenderObject, yang memungkinkan Anda menemukan objek render tree yang sesuai dengan elemen ini.

Renderderbject


Masih berurusan dengan tautan terakhir dari triad ini - RenderObject . Seperti namanya, ini adalah objek dari pohon visualisasi. Ini memiliki objek induk, serta bidang data yang menggunakan objek induk untuk menyimpan informasi spesifik mengenai objek ini sendiri, misalnya, posisinya. Objek ini bertanggung jawab untuk implementasi render dasar dan protokol tata letak.

RenderObject tidak membatasi model menggunakan objek anak: mungkin tidak ada, satu atau banyak. Juga, sistem penentuan posisi tidak terbatas pada: sistem Cartesian, koordinat kutub, semua ini dan banyak lagi tersedia untuk digunakan. Tidak ada batasan dalam penggunaan protokol lokasi: menyesuaikan lebar atau tinggi, membatasi ukuran, mengatur ukuran dan lokasi induk atau, jika perlu, menggunakan data objek induk.

Flutter World Picture


Mari kita coba membangun gambaran besar tentang bagaimana semuanya bekerja bersama.

Kami sudah mencatat di atas, widget adalah deskripsi abadi, tetapi antarmuka pengguna sama sekali tidak statis. Perbedaan ini dihapus dengan membagi menjadi 3 level objek dan pembagian zona tanggung jawab.

  • , .
  • , .
  • , — , .

gambar

Mari kita lihat bagaimana tampilan pohon-pohon ini dengan contoh sederhana:

gambar

Dalam hal ini, kami memiliki beberapa StatelessWidget yang dibungkus dengan widget Padding dan berisi teks di dalamnya.

Mari kita tempatkan Flutter - kita diberi pohon widget ini.

Flutter: "Hei, Padding, aku butuh elemenmu "
Padding: "Tentu saja, pegang SingleChildRenderObjectElement"

gambar

Flutter: "Element, ini
tempatmu, tenanglah " SingleChildRenderObjectElement: "Guys, semuanya baik-baik saja, tapi aku butuh RenderObject"
Flutter: "Padding, seperti untuk menarik Anda sama sekali? "
Padding: "Tunggu, RenderPadding"
SingleChildRenderObjectElement: "Hebat, mulai bekerja"

gambar

Flutter:"Jadi, siapa selanjutnya?" StatelessWidget, sekarang Anda membiarkan elemen »
StatelessWidget: «Di sini StatelessElement»
Flutter: «StatelessElement, Anda akan tunduk kepada SingleChildRenderObjectElement, di sini adalah tempat, memulai»
StatelessElement: «OK»

gambar

Flutter: «RichText itu, Hadir elementik, silakan»
yang RichText memberikan MultiChildRenderObjectElement
Flutter: "MultiChildRenderObjectElement, ini dia, memulai"
MultiChildRenderObjectElement: "Saya butuh render untuk bekerja"
Flutter: "RichText, kami membutuhkan objek render"
RichText: "Ini adalah RenderParagraph"
Flutter:"RenderParagraph Anda akan menerima instruksi RenderPadding, dan MultiChildRenderObjectElement akan mengendalikan Anda"
MultiChildRenderObjectElement: "Sekarang semuanya baik-baik saja, saya siap"

gambar

Tentunya Anda akan mengajukan pertanyaan logis: "Di mana objek render untuk StatelessWidget, mengapa tidak ada, kami memutuskan di atas bahwa elemen mengikat konfigurasi dengan tampilan? " Mari kita perhatikan implementasi dasar metode mount, yang telah dibahas di bagian deskripsi siklus hidup ini.

void mount(Element parent, dynamic newSlot) {
    assert(_debugLifecycleState == _ElementLifecycle.initial);
    assert(widget != null);
    assert(_parent == null);
    assert(parent == null || parent._debugLifecycleState == _ElementLifecycle.active);
    assert(slot == null);
    assert(depth == null);
    assert(!_active);
    _parent = parent;
    _slot = newSlot;
    _depth = _parent != null ? _parent.depth + 1 : 1;
    _active = true;
    if (parent != null)
        _owner = parent.owner;
    if (widget.key is GlobalKey) {
        final GlobalKey key = widget.key;
        key._register(this);
    }
    _updateInheritance();
    assert(() {
        _debugLifecycleState = _ElementLifecycle.active;
        return true;
    }());
}

Kita tidak akan melihat di dalamnya penciptaan objek render. Tetapi elemen mengimplementasikan BuildContext, yang memiliki metode pencarian objek visualisasi findRenderObject, yang akan membawa kita ke pengambil berikut:

RenderObject get renderObject {
    RenderObject result;
    void visit(Element element) {
        assert(result == null); 
        if (element is RenderObjectElement)
            result = element.renderObject;
        else
            element.visitChildren(visit);
    }
    visit(this);
    return result;
}

Dalam kasus dasar, elemen mungkin tidak membuat objek rendering, hanya RenderObjectElement dan turunannya yang diwajibkan untuk melakukan ini, tetapi dalam kasus ini, elemen di beberapa level bersarang harus memiliki elemen turunan yang memiliki objek rendering.

Tampaknya mengapa semua kesulitan ini. Sebanyak 3 pohon, berbagai bidang tanggung jawab, dll. Jawabannya cukup sederhana - di sinilah kinerja Flutter dibangun. Widget adalah konfigurasi yang tidak dapat diubah, oleh karena itu, widget tersebut sering dibuat ulang, tetapi pada saat yang sama sangat ringan, yang tidak memengaruhi kinerja. Tapi Flutter berusaha menggunakan kembali elemen berat sebanyak mungkin.

Pertimbangkan sebuah contoh.

Teks di tengah layar. Kode dalam hal ini akan terlihat seperti ini:

body: Center(
    child: Text(“Hello world!”)
),

Dalam hal ini, pohon widget akan terlihat seperti ini:

gambar

Setelah Flutter membangun semua 3 pohon, kita mendapatkan gambar berikut:

gambar

Apa yang terjadi jika kita mengubah teks yang akan kita tampilkan?

gambar

Kami sekarang memiliki pohon widget baru. Di atas kami berbicara tentang penggunaan kembali elemen secara maksimal. Lihatlah metode kelas Widget, di bawah nama berbicara canUpdate .

static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;
}

Kami memeriksa jenis widget sebelumnya dan yang baru, serta kunci-kuncinya. Jika mereka sama, maka tidak perlu mengubah item.

Jadi, sebelum upgrade, elemen pertama adalah Center, setelah upgrade, juga Center. Keduanya tidak memiliki kunci, kebetulan yang lengkap. Kami dapat memperbarui tautan item ke widget baru.

gambar

Namun selain jenis dan kunci, widget adalah deskripsi dan konfigurasi, dan nilai-nilai parameter yang diperlukan untuk tampilan bisa berubah. Itulah sebabnya elemen, setelah memperbarui tautan ke widget, harus memulai pembaruan ke objek rendering. Dalam kasus Center, tidak ada yang berubah, dan kami terus membandingkan lebih lanjut.

Sekali lagi, jenis dan kunci memberi tahu kami bahwa tidak masuk akal untuk membuat ulang elemen. Teks tersebut adalah turunan dari StatelessWidget, tidak memiliki objek tampilan langsung.

gambar

Pergi ke RichText. Widget juga tidak mengubah tipenya, tidak ada perbedaan dalam tombol. Item memperbarui hubungannya dengan widget baru.

gambar

Koneksi diperbarui, tetap hanya untuk memperbarui properti. Akibatnya, RenderParagraph akan menampilkan nilai teks baru.

gambar

Dan begitu saatnya tiba untuk bingkai gambar berikutnya, kita akan melihat hasil yang kita harapkan.

Berkat pekerjaan seperti ini, Flutter mencapai kinerja tinggi seperti itu.

Contoh di atas menjelaskan kasus ketika struktur widget itu sendiri tidak berubah. Tetapi apa yang terjadi jika strukturnya berubah? Flutter, tentu saja, akan terus mencoba memaksimalkan penggunaan objek yang ada, seperti yang kita pahami dari deskripsi siklus hidup, tetapi elemen baru akan dibuat untuk semua widget baru, dan yang lama dan yang tidak perlu akan dihapus pada akhir frame.

Mari kita lihat beberapa contoh. Dan untuk memastikan hal di atas, kami menggunakan alat Android Studio - Flutter Inspector.

@override
Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
            child: _isFirst ? first() : second(),
        ),
        floatingActionButton: FloatingActionButton(
            child: Text("Switch"),
            onPressed: () {
                setState(() {
                    _isFirst = !_isFirst;
                });
            },
        ),
    );
}

Widget first() => Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
        Text(
            "test",
            style: TextStyle(fontSize: 25),
        ),
        SizedBox(
            width: 5,
        ),
        Icon(
            Icons.error,
        ),
    ],
);

Widget second() => Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
        Text(
            "one more test",
            style: TextStyle(fontSize: 25),
        ),
        Padding(
            padding: EdgeInsets.only(left: 5),
        ),
        Icon(
            Icons.error,
        ),
    ],
);

Dalam hal ini, dengan mengklik tombol, salah satu widget akan berubah. Mari kita lihat apa yang ditunjukkan inspektur kepada kita.

gambar

gambar

Seperti yang dapat kita lihat, Flutter menciptakan kembali render hanya untuk Padding, sisanya hanya digunakan kembali.

Pertimbangkan 1 opsi lagi di mana struktur berubah dengan cara yang lebih global - kami mengubah tingkat sarang.

Widget second() => Container(child: first(),);

gambar

gambar

Terlepas dari kenyataan bahwa pohon itu tidak berubah sama sekali secara visual, elemen dan objek pohon rendering diciptakan kembali. Ini terjadi karena Flutter membandingkan berdasarkan level (dalam hal ini, tidak masalah bahwa sebagian besar pohon tidak berubah), pengayakan bagian ini terjadi pada saat membandingkan Container dan Row. Namun, seseorang dapat keluar dari situasi ini. Ini akan membantu kami GlobalKey. Tambahkan kunci untuk Row.

var _key = GlobalKey(debugLabel: "testLabel");

Widget first() => Row(
    key: _key,
    …
);

gambar

gambar

Segera setelah kami memberi tahu Flutter bahwa bagian itu dapat digunakan kembali, dia memanfaatkan kesempatan itu.

Kesimpulan


Kami menjadi sedikit lebih akrab dengan sihir Flutter dan sekarang kami tahu bahwa itu tidak hanya di widget.

Flutter adalah mekanisme yang terkoordinasi dengan baik dipikirkan dengan hierarki sendiri, bidang tanggung jawab, dengan mana Anda dapat membuat tidak hanya indah, tetapi juga aplikasi produktif. Tentu saja, kami hanya memeriksa sebagian kecil dari perangkatnya, jadi kami akan terus menganalisis berbagai aspek kerja internal kerangka kerja di artikel mendatang.

Saya harap informasi dalam artikel ini bermanfaat dalam memahami bagaimana Flutter bekerja secara internal dan membantu Anda menemukan solusi yang elegan dan produktif selama pengembangan.

Terimakasih atas perhatiannya!

Sumber daya


Flutter
"How Flutter renders Widgets" oleh Andrew Fitz Gibbon, Matt Sullivan

All Articles