หากวิดเจ็ตที่กำหนดเองของคุณยอมรับ String
และ TextStyle
หรือรูปแบบอื่นๆ เช่น สี น้ำหนัก จัดแนวข้อความ ฯลฯ คุณกำลังทำผิด! วิดเจ็ตของคุณควรมีคุณสมบัติประเภท Widget
แทน คุณเคยสงสัยหรือไม่ว่าทำไม TextButton()
, ListTile()
และวิดเจ็ตอื่นๆ จึงยอมรับวิดเจ็ต (ไม่ใช่สตริง) และวิดเจ็ตเหล่านี้จัดรูปแบบวิดเจ็ต Text()
ที่คุณส่งเข้าไปอย่างไร — พวกเขาทำได้โดยห่อลูกด้วย DefaultTextStyle
หรือ AnimatedDefaultTextStyle
วิธีการนี้มีความยืดหยุ่นมากโดยไม่ต้องใช้พารามิเตอร์การจัดรูปแบบหลายร้อยรายการในตัวสร้างวิดเจ็ต
ในบทความนี้ ฉันจะแสดงให้คุณเห็นว่าเหตุใดโค้ดทางด้านซ้ายจึงไม่ผ่านการตรวจสอบ PR ของฉัน และโค้ดทางด้านขวาจะแก้ปัญหาอะไรได้บ้าง
หมายเหตุ:
_WidgetContainer
เพียงเพิ่มสีพื้นหลังและช่องว่างภายใน และแสดงลูกในคอลัมน์ คุณจะเห็นผลลัพธ์ในบทความต่อไป- หากคุณสงสัยว่าทำไมฉันถึงเขียน
context.textTheme
และไม่ใช่Theme.of(context).textTheme
คุณควรอ่านบทความเรื่อง "ส่วนขยายที่ขาดหายไปใน Flutter"
เมื่อแนวทาง TextStyle พัง
ขั้นแรก ลองจินตนาการถึงการวนซ้ำหลายครั้งที่วิดเจ็ตที่คล้ายกันซึ่งมีคุณสมบัติ 2 String
อาจผ่านไปได้ ดูว่าโค้ดจะเติบโตเร็วแค่ไหนและซับซ้อนแค่ไหน
1. การเพิ่มสีสัน
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, ), ), ], ); } }
เพียงเพื่อเพิ่มความสามารถในการเปลี่ยนสีข้อความ เราจำเป็นต้องเพิ่ม 2 คุณสมบัติและเปลี่ยนโค้ดใน 6 ตำแหน่ง!
2. การเพิ่มตัวหนาและการจัดตำแหน่ง
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 รายการ และการเปลี่ยนแปลงโค้ดใน 9 ตำแหน่ง!
❗️ โดยรวมแล้วมี คุณสมบัติใหม่ 5 รายการและการเปลี่ยนแปลง 15 รายการ นับตั้งแต่เวอร์ชันดั้งเดิม!
วันถัดไปคุณได้รับคำขออื่น: คำอธิบายควรรองรับการจัดตำแหน่งตัวเอียงและกึ่งกลาง ชื่อเรื่องควรสนับสนุนแสงนอกเหนือจากตัวหนา คุณควรเห็นแล้วว่ามันจะไปอยู่ที่ไหน — ไปสู่นรก "บูลีน"
3.ก้าวไปผิดทาง ❌: TextStyle
บางครั้งฉันเห็นว่าวิดเจ็ตยอมรับพารามิเตอร์ TextStyle
หลายตัวสำหรับข้อความที่แตกต่างกัน แต่น่าเสียดายที่คุณยังต้องผ่าน textAlign
แยกกัน โค้ดจะมีคุณสมบัติเพิ่มเติม 3 รายการและการเปลี่ยนแปลงใน 9 ตำแหน่ง ตั้งแต่เวอร์ชันดั้งเดิม:
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), ), ], ); } }
นอกจากนี้ การปรับแต่งยังค่อนข้าง จำกัด คุณไม่สามารถเปลี่ยน maxLines, overflow, softWrap และ textWidthBasis(ไม่รู้ด้วยซ้ำว่ามันคืออะไร 😅) โชคดีที่เรามีวิธีแก้ปัญหา!
โซลูชัน ⭐: DefaultTextStyle.merge()
ตอนนี้ฉันจะแสดงวิธีที่ดีที่สุดในการจัดการข้อความในวิดเจ็ตที่กำหนดเอง มาเปลี่ยนประเภทของคุณสมบัติ String title
และ String description
เป็น Widget
และลบการจัดตำแหน่งออก แทนที่จะรวมรูปแบบข้อความเริ่มต้นเข้ากับวิดเจ็ตที่มีให้และส่งต่อไปยังวิดเจ็ต Text()
เราจะรวมชื่อและคำอธิบายไว้ใน DefaultTextStyle.merge()
และจัดเตรียมการจัดรูปแบบไว้ที่นั่น นี่คือรหัส:
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, ), ], ); } }
สำหรับการใช้งานขั้นพื้นฐาน วิดเจ็ตนี้อาจใช้งานยากขึ้นเล็กน้อย เนื่องจากแทนที่จะใช้สตริงธรรมดา ตอนนี้คุณควรผ่าน Text(“some string”)
แต่นี่คือสถานที่ที่ทำให้เราปรับแต่งได้โดยไม่ต้องใช้แฟล็กบูลีนนับล้านล้าน 🎉
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, ), ), )
อย่างที่คุณเห็น ชื่อมีขนาด headlineMedium
แต่มีสีม่วง มีตัวหนาและอยู่ตรงกลาง คำอธิบายเป็นสีดำ (ไม่ใช่ black54) มีขนาด 16 และมีการจัดตำแหน่งตรงกลาง ทุกอย่างทำงานตามที่คาดไว้ สไตล์เริ่มต้นจะผสานกับสไตล์ที่ส่งผ่านไปยังวิดเจ็ต Text()
ไม่ใช่แค่ TextStyle เท่านั้น!
ในทำนองเดียวกัน เรายังปรับแต่งวิดเจ็ตอื่นๆ ได้ด้วย ตัวอย่างเช่น ด้วย IconTheme
เราสามารถตั้งค่าสีเริ่มต้นและแม้แต่ SIZE สำหรับไอคอนได้
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, ), ], ), ); } }
นอกจากนี้ เนื่องจากคุณสมบัติของเราเป็นประเภท Widget
เราจึงสามารถส่งผ่าน อะไรก็ได้ที่เราต้องการ แม้แต่ Row()
ด้วยข้อความและไอคอนที่หลากหลาย!
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, ), ), )
หมายเหตุ:
- แทนที่จะใช้
RichText
คุณควรใช้Text.rich()
เนื่องจากอันแรกเป็นระดับต่ำมากกว่าและไม่ใช้DefaultTextStyle
บางครั้งคุณจะต้องผ่าน String, TextStyle, Color หรือสไตล์อื่นสำหรับวิดเจ็ต แต่ฉันหวังว่าหลังจากเรียนรู้เกี่ยวกับ DefaultTextStyle
และ IconTheme
แล้ว คุณจะพบว่ามีประโยชน์สำหรับวิดเจ็ตเหล่านี้! นอกจากนี้ยังมีวิธีอื่นๆ อีกมากมายในการจัดเตรียมสไตล์เริ่มต้นสำหรับวิดเจ็ต เช่น การแทนที่ธีมสำหรับส่วนของ UI
ทิปโบนัส⚡️
หากคุณไม่ชอบที่โค้ดวิดเจ็ตสุดท้ายมีการซ้อนมากเกินไป คุณสามารถแก้ไขได้ด้วยตัวแก้ไขวิดเจ็ต (คล้ายกับ "SwiftUI") ดังนี้:
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)), ); } }
แนวคิดนี้นำมาจากทวีต “@luke_pighetti” คุณจะพบตัวอย่างเพิ่มเติมของตัวดัดแปลงวิดเจ็ตใน Flutter!
นอกจากนี้ ฉันขอเชิญคุณอ่านบทความของฉันเกี่ยวกับ Flutter Custom Theme พร้อม ThemeExtensions + Templates:
ขอขอบคุณที่อ่าน บาย บะบาย