经常在各大社群中看到关于“如何自定义一个组件”这样的问题讨论。在探讨的过程中,经常会涉及到“如何更好的设置自定义组件中的文本”的问题。那么这篇文章让我们一起来聊聊。
为什么这样处理自定义组件中的文本不是一个好的方法?
让我们来看一个常见的封装组件,封装了一个标题,还有标题下的一些淡灰色的正文。
class MyTitleText extends StatelessWidget { const MyTitleText({ Key? key, required this.titleText, required this.content, this.titleStyle = const TextStyle(fontSize: 22, fontWeight: FontWeight.bold), }) : super(key: key); final String titleText; final String content; final TextStyle titleStyle;
@override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( titleText, style: titleStyle, ), const SizedBox( height: 6, ), Text( content, style: const TextStyle(fontSize: 14, color: Color(0xFF707070)), ) ], ); }}
复制代码
代码内容非常的简单,乍一看封装的也没有什么问题。但是这样其实并不是一个非常好的方式!为什么我会这么说,让我们再来看一段封装的代码:
class MyTitleText extends StatelessWidget { const MyTitleText({ Key? key, required this.titleText, required this.content, }) : super(key: key); final Widget titleText; final Widget content;
@override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ DefaultTextStyle.merge( style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold), child: titleText, ), const SizedBox( height: 6, ), DefaultTextStyle.merge( style: const TextStyle( fontSize: 14, color: Color(0xFF707070), ), child: content, ) ], ); }}
复制代码
不知道大家有没有看出什么区别,区别主要在于,前面的自定义封装组件,接收了两个String和一个TextStyle,而这个自定义的封装组件,接受的是两个 Widget。那么为什么我会说自定义组件接收String或者TextStyle又或者是TextAlign等属性不是一个好的方式呢?让我们接着来看。
在使用封装的组件中 TextStyle 等属性是一个错误的方向
如果我们还是使用最开始封装的组件,今天 UI 妹子告诉你,要把有几个页面的标题变成正常的字体,不要粗体,让你把组件改一下,你会怎么改?遇到这样的情况,我相信有很多朋友会和我一样,加一个bool😏,组件就会变成这样:
class MyTitleText extends StatelessWidget { const MyTitleText({ Key? key, required this.titleText, required this.content, required this.isTitleBold, this.titleStyle = const TextStyle(fontSize: 22, fontWeight: FontWeight.bold), }) : super(key: key); final String titleText; final String content; final TextStyle titleStyle; final bool isTitleBold;
@override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( titleText, style: titleStyle.copyWith( fontWeight: isTitleBold ? FontWeight.bold : FontWeight.normal, ), ), const SizedBox( height: 6, ), Text( content, style: const TextStyle(fontSize: 14, color: Color(0xFF707070)), ) ], ); }}
复制代码
也许现在各位还觉得没什么,不就是加一个属性,通过copyWith,利用三元判断改一下样式,很轻松呀。
过两天,UI 妹子又来了,她说,有些页面的标题需要改变文本的对齐方式,有些页面的标题和正文的颜色也要有所区别,需要改下组件。好了,又要干活了,这次准备怎么改呢?我猜大部分朋友会这么写:
class MyTitleText extends StatelessWidget { const MyTitleText({ Key? key, required this.titleText, required this.content, required this.isTitleBold, this.titleStyle = const TextStyle(fontSize: 22, fontWeight: FontWeight.bold), this.titleTextAlign = TextAlign.start, this.titleTextColor = const Color(0xFF000000), this.contentTextColor = const Color(0xFF707070), }) : super(key: key); final String titleText; final String content; final TextStyle titleStyle; final bool isTitleBold; final TextAlign titleTextAlign; final Color titleTextColor; final Color contentTextColor;
@override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( titleText, textAlign: titleTextAlign, style: titleStyle.copyWith( fontWeight: isTitleBold ? FontWeight.bold : FontWeight.normal, color: titleTextColor), ), const SizedBox( height: 6, ), Text( content, style: const TextStyle(fontSize: 14).copyWith( color: contentTextColor, ), ) ], ); }}
复制代码
好了,到现在为止,从第一版封装的组件到到这“最后”一版,我们已经添加了 4 个属性和 11 个地方的代码修改。过两天,UI 妹子又来了...她说正文还需要能有斜体样式。我想,大家已经知道怎么修改了,没错,又是一个bool。而且,就算是又用一个bool控制了斜体效果,那如果还要控制 maxLines 呢,如果还要控制...
那么如何改善这样的问题呢?答案是:传进组件的是 Widget。为什么这样可以改善呢?探讨之前,我想说一句,大家一定听过一句话,“Flutter 中一切皆是 Widget”。让我们看看 Flutter 自带的组件,如TextButton,这些组件有一个特点,就是都是接收 Widget 作为属性的。
那大家可能也会好奇,如果接收的是一个 Widget,那么要怎么把样式传递给这个 Widget 呢。答案是DefaultTextStyle 或者是AnimatedDefaultTextStyle。
使用 DefaultTextStyle.merge 更好的处理自定义组件中的文本
去掉我们自定义组件中的 String、textAlign 和 Color 以及其他属性,把它们变成传入两个 Widget。通过DefaultTextStyle.merge()去合并我们的默认文本的样式。(DefaultTextStyle 组件会将子组件中没有显式指定样式的文本部件应用默认样式)。
class MyTitleText extends StatelessWidget { const MyTitleText({ Key? key, required this.titleText, required this.content, }) : super(key: key); final Widget titleText; final Widget content;
@override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ DefaultTextStyle.merge( style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold), child: titleText, ), const SizedBox( height: 6, ), DefaultTextStyle.merge( style: const TextStyle( fontSize: 14, color: Color(0xFF707070), ), child: content, ), ], ); }}
复制代码
组件是使用难度稍稍的增加了,因为不只是单单传一个字符串就够了,我们需要传入一个 Widget。但是这样,我们的自定义也更加的“自由”,拜托了无限使用bool的窘境。我们传入的 Widget,本身自带的样式,也会和我们自定义封装的样式合并。
选择这样的自定义方式有着更多的优势
—— 因为我们传入的是一个 Widget,有着几乎“无限”自定义的可能。
又过了两天,UI 妹子又来了,她希望能首页的标题后面加上我们 App 的 Icon,并且标题中间的某个字要斜体。如果是前面的自定义组件方式,那需要改变的地方可太多了!但是,学会现在这样自定义组件后,我们就不会那么狼狈了!我们可以传入一个 RichText,来展示斜体文本和图标。
MyTitleText( titleText: Text.rich(TextSpan(children: [ TextSpan( text: "我是", style: TextStyle( color: Colors.black, fontSize: 16, ), ), TextSpan( text: "标题", style: TextStyle( color: Colors.black, fontStyle: FontStyle.italic, fontSize: 16, ), ), WidgetSpan( child: Icon( Icons.add, )) ])), content: Text( '我是正文', style: TextStyle( color: Colors.black, fontSize: 16, ), ),)
复制代码
——这里的代码中有个小小的知识点,我们用的是 Text.rich 而不是 RichText,为什么呢?我们可以看下 RichText 中的注释:
Text.rich 相比较于 RichText 更加的底层,RichText 有 DefaultTextStyle,如果要设置 RichText 的字体样式,必须显式的设置。
在 Material 3 风格下的特殊处理
那本文的意义也不是想让大家完全不去用String、TextStyle...的方式,只是希望大家能更好的了解到DefaultTextStyle,利用它实现更优美更便捷的代码!
如果启用了 Material 3 的风格,当你的组件需要在整个 App 中是特定的样式,那么你必须要通过显式的方式去设置样式,因为当你启用了 Material 3 的风格后,Flutter 组件的实例都具有默认样式!
关于 Material 3 的内容可以查看这篇文章:在Material 3中使用 Themes 统一颜色和字体风格
有兴趣的朋友也可以去了解IconTheme等其他默认样式在自定义组件中的运用~
关于我
Hello,我是 Taxze,如果您觉得文章对您有价值,希望您能给我的文章点个❤️,有问题需要联系我的话:我在这里 。如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章~万一哪天我进步了呢?😝
评论