Jika widget khusus Anda menerima String
dan TextStyle
atau gaya lainnya: warna, berat, perataan teks, dll. Anda salah melakukannya! Sebaliknya, widget Anda seharusnya memiliki properti tipe Widget
. Pernahkah Anda bertanya-tanya mengapa TextButton()
, ListTile()
, dan lainnya menerima widget (bukan string) dan bagaimana mereka menata widget Text()
yang Anda masukkan ke dalamnya? — Mereka melakukannya dengan membungkus anaknya dengan DefaultTextStyle
atau AnimatedDefaultTextStyle
. Pendekatan ini sangat fleksibel tanpa memerlukan ratusan parameter gaya di konstruktor widget.
Dalam artikel ini, saya akan menunjukkan kepada Anda mengapa kode di sebelah kiri tidak lolos tinjauan PR saya dan masalah apa yang dipecahkan oleh kode di sebelah kanan.
Catatan:
_WidgetContainer
cukup menambahkan warna latar belakang, dan padding, serta menampilkan turunan di Kolom. Anda akan melihat hasilnya nanti di artikel.- Jika Anda bertanya-tanya mengapa saya menulis
context.textTheme
dan bukanTheme.of(context).textTheme
, Anda harus membaca artikel tentang Ekstensi yang Hilang di Flutter.
Ketika pendekatan TextStyle rusak
Pertama, bayangkan beberapa iterasi yang mungkin dilakukan oleh widget serupa dengan 2 properti String
. Lihatlah seberapa cepat kode tersebut berkembang dan betapa rumitnya kode tersebut.
1. Menambahkan warna
class CustomWidgetWithColor extends StatelessWidget { const CustomWidgetWithColor({ super.key, required this.title, // 1 this.titleColor, required this.description, // 2 this.descriptionColor, }); final String title; // 3 final Color? titleColor; final String description; // 4 final Color? descriptionColor; @override Widget build(BuildContext context) { return _WidgetContainer( children: [ Text( title, style: context.textTheme.headlineMedium?.copyWith( // 5 color: titleColor ?? Colors.black, ), ), Text( description, style: context.textTheme.bodySmall?.copyWith( // 6 color: descriptionColor ?? Colors.black54, ), ), ], ); } }
Hanya untuk menambahkan kemampuan mengubah warna teks kita perlu menambahkan 2 properti dan mengubah kode di 6 tempat!
2. Menambahkan huruf tebal dan perataan
class CustomWidgetWithEverything extends StatelessWidget { const CustomWidgetWithEverything({ super.key, required this.title, this.titleColor, // 1 this.isTitleBold = false, // 2 this.titleAlignment = TextAlign.start, required this.description, this.descriptionColor, // 3 this.isDescriptionBold = false, }); final String title; final Color? titleColor; // 4 final bool isTitleBold; // 5 final TextAlign titleAlignment; final String description; final Color? descriptionColor; // 6 final bool isDescriptionBold; @override Widget build(BuildContext context) { return _WidgetContainer( children: [ Text( title, // 7 textAlign: titleAlignment, style: context.textTheme.headlineMedium?.copyWith( color: titleColor ?? Colors.black, // 8 fontWeight: isTitleBold ? FontWeight.bold : FontWeight.normal, ), ), Text( description, style: context.textTheme.bodySmall?.copyWith( color: descriptionColor ?? Colors.black54, // 9 fontWeight: isDescriptionBold ? FontWeight.bold : FontWeight.normal, ), ), ], ); } }
3 properti lagi dan perubahan kode di 9 tempat!
❗️ Secara total, sudah ada 5 properti baru dan 15 perubahan sejak versi aslinya!
Keesokan harinya Anda mendapatkan permintaan lain: Deskripsi harus mendukung miring dan perataan tengah; Judul juga harus mendukung ringan dan tebal. Anda seharusnya sudah melihat ke mana arahnya — Sungguh “boolean”.
3. Melangkah ke arah yang salah ❌: TextStyle
Terkadang saya melihat widget menerima beberapa parameter TextStyle
untuk teks yang berbeda, namun sayangnya, Anda masih harus meneruskan textAlign
secara terpisah. Kode akan memiliki 3 properti tambahan dan perubahan di 9 tempat sejak versi aslinya:
class CustomWidgetWithTextStyle extends StatelessWidget { const CustomWidgetWithTextStyle({ super.key, required this.title, // 1 this.titleStyle, // 2 this.titleAlignment = TextAlign.start, required this.description, // 3 this.descriptionStyle, }); final String title; // 4 final TextStyle? titleStyle; // 5 final TextAlign? titleAlignment; final String description; // 6 final TextStyle? descriptionStyle; @override Widget build(BuildContext context) { return _WidgetContainer( children: [ Text( title, // 7 textAlign: titleAlignment, style: context.textTheme.headlineMedium ?.copyWith(color: Colors.black) // 8 .merge(titleStyle), ), Text( description, style: context.textTheme.bodySmall ?.copyWith(color: Colors.black54) // 9 .merge(descriptionStyle), ), ], ); } }
Selain itu, penyesuaiannya masih cukup terbatas. Anda tidak dapat mengubah maxLines, overflow, softWrap, dan textWidthBasis(bahkan tidak tahu apa itu 😅). Untungnya, kami punya solusinya!
Solusi ⭐: DefaultTextStyle.merge()
Sekarang saya akan menunjukkan cara terbaik untuk menangani teks di widget khusus. Mari kita ubah jenis properti String title
dan String description
menjadi Widget
dan hapus perataannya. Daripada menggabungkan gaya teks default dengan yang disediakan dan meneruskannya ke widget Text()
, kami akan menggabungkan Judul dan Deskripsi ke dalam DefaultTextStyle.merge()
dan memberikan gaya di sana. Ini kodenya:
class TheBestCustomWidget extends StatelessWidget { const TheBestCustomWidget({ super.key, required this.title, required this.description, }); final Widget title; final Widget description; @override Widget build(BuildContext context) { return _WidgetContainer( children: [ DefaultTextStyle.merge( style: context.textTheme.headlineMedium?.copyWith(color: Colors.black), child: title, ), DefaultTextStyle.merge( style: context.textTheme.bodySmall?.copyWith(color: Colors.black54), child: description, ), ], ); } }
Untuk penggunaan dasar, widget ini mungkin menjadi sedikit lebih sulit untuk digunakan, karena alih-alih menggunakan string sederhana, Anda sekarang harus meneruskan Text(“some string”)
. Tapi inilah tempat yang tepat yang memberi kita penyesuaian tanpa satu triliun tanda boolean 🎉
TheBestCustomWidget( title: Text( 'The Best Title', textAlign: TextAlign.center, style: TextStyle( color: Colors.purple, fontWeight: FontWeight.bold, ), ), description: Text( 'DefaultTextStyle.merge() is the best!', textAlign: TextAlign.center, style: TextStyle( color: Colors.black, fontSize: 16, ), ), )
Seperti yang Anda lihat, Judul memiliki ukuran headlineMedium
, tetapi berwarna ungu, tebal dan rata tengah. Deskripsinya berwarna hitam (bukan black54), berukuran 16, dan memiliki garis tengah. Semuanya berfungsi seperti yang diharapkan, gaya default digabungkan dengan gaya yang diteruskan ke widget Text()
.
Bukan hanya TextStyle!
Dengan cara yang sama, kita juga dapat menyesuaikan widget lainnya. Misalnya, dengan IconTheme
kita dapat mengatur warna default dan bahkan UKURAN untuk ikon.
class TheBestCustomWidgetWithIconsSupport extends StatelessWidget { const TheBestCustomWidgetWithIconsSupport({ super.key, required this.title, required this.description, }); final Widget title; final Widget description; @override Widget build(BuildContext context) { // 1. IconTheme return IconTheme.merge( // 2. IconThemeData data: const IconThemeData( color: Colors.red, size: 32, ), child: _WidgetContainer( children: [ DefaultTextStyle.merge( style: context.textTheme.headlineMedium?.copyWith(color: Colors.black), child: title, ), DefaultTextStyle.merge( style: context.textTheme.bodySmall?.copyWith(color: Colors.black54), child: description, ), ], ), ); } }
Selain itu, karena properti kita bertipe Widget
, kita dapat meneruskan apa pun yang kita perlukan. Bahkan Row()
dengan teks kaya dan ikon!
TheBestCustomWidgetWithIconsSupport( // Row title: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ // Rich text Text.rich( TextSpan( children: [ TextSpan( text: 'The', style: TextStyle(color: Colors.purple), ), TextSpan( text: ' Best ', style: TextStyle(fontStyle: FontStyle.italic), ), TextSpan(text: 'Title!'), ], style: TextStyle( color: Colors.purple, fontWeight: FontWeight.bold, ), ), textAlign: TextAlign.center, ), SizedBox(width: 5), // Icon Icon(Icons.favorite), ], ), description: Text( 'DefaultTextStyle & IconTheme are the best!', textAlign: TextAlign.center, style: TextStyle( color: Colors.black, fontWeight: FontWeight.normal, fontSize: 16, ), ), )
Catatan:
- Daripada
RichText
sebaiknya gunakanText.rich()
, karena yang pertama lebih berlevel rendah dan tidak menggunakanDefaultTextStyle
.
Terkadang Anda benar-benar perlu meneruskan String, TextStyle, Color, atau gaya lainnya untuk widget, namun saya harap setelah mempelajari tentang DefaultTextStyle
dan IconTheme
, Anda akan menemukan kegunaan yang baik untuk widget tersebut! Selain itu, ada lebih banyak cara untuk memberikan gaya default untuk widget seperti mengganti Tema untuk bagian UI.
Kiat bonus ⚡️
Jika Anda tidak menyukai kode widget terakhir yang terlalu banyak bersarang, Anda dapat memperbaikinya dengan pengubah widget (mirip dengan SwiftUI) seperti ini:
class TheBestCustomWidgetWithExtensions extends StatelessWidget { const TheBestCustomWidgetWithExtensions({ super.key, required this.title, required this.description, }); final Widget title; final Widget description; @override Widget build(BuildContext context) { return _WidgetContainer( children: [ title .textStyle(context.textTheme.headlineMedium) .foregroundColor(Colors.black), description .textStyle(context.textTheme.bodySmall) .foregroundColor(Colors.black54), ], ); } } extension on Widget { Widget textStyle(TextStyle? style, {TextAlign? align}) { return DefaultTextStyle.merge( style: style, textAlign: align, child: this, ); } Widget foregroundColor(Color color) { return IconTheme.merge( data: IconThemeData(color: color), child: textStyle(TextStyle(color: color)), ); } }
Ide ini diambil dari @luke_pighetti tweet. Di sana Anda dapat menemukan lebih banyak contoh pengubah widget di Flutter!
Saya juga mengundang Anda untuk membaca artikel saya tentang Tema Kustom Flutter dengan Ekstensi Tema + Templat:
Terima kasih telah membaca. Sampai jumpa 👋