请不要再把String或Style直接传递给自定义的组件了!

简介: 经常在各大社群中看到关于“如何自定义一个组件”这样的问题讨论。在探讨的过程中,经常会涉及到“如何更好的设置自定义组件中的文本”的问题。那么这篇文章让我们一起来聊聊。

经常在各大社群中看到关于“如何自定义一个组件”这样的问题讨论。在探讨的过程中,经常会涉及到“如何更好的设置自定义组件中的文本”的问题。那么这篇文章让我们一起来聊聊。

为什么这样处理自定义组件中的文本不是一个好的方法?

让我们来看一个常见的封装组件,封装了一个标题,还有标题下的一些淡灰色的正文。

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风格下的特殊处理

那本文的意义也不是想让大家完全不去用StringTextStyle...的方式,只是希望大家能更好的了解到DefaultTextStyle,利用它实现更优美更便捷的代码!

如果启用了Material 3的风格,当你的组件需要在整个App中是特定的样式,那么你必须要通过显式的方式去设置样式,因为当你启用了Material 3的风格后,Flutter组件的实例都具有默认样式!

关于Material 3的内容可以查看这篇文章:在Material 3中使用 Themes 统一颜色和字体风格

有兴趣的朋友也可以去了解IconTheme等其他默认样式在自定义组件中的运用~

关于我

Hello,我是Taxze,如果您觉得文章对您有价值,希望您能给我的文章点个❤️,有问题需要联系我的话:[我在这里](https://github.com/taxze6)。如果您觉得文章还差了那么点东西,也请通过**关注**督促我写出更好的文章~万一哪天我进步了呢?😝

相关文章
|
6月前
|
算法 Linux C语言
7.学习STL和string类:版本、组件、构造、操作及应用
7.学习STL和string类:版本、组件、构造、操作及应用
|
3月前
|
Java 索引
java基础(13)String类
本文介绍了Java中String类的多种操作方法,包括字符串拼接、获取长度、去除空格、替换、截取、分割、比较和查找字符等。
48 0
java基础(13)String类
|
29天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
51 2
|
2月前
|
Java
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
本文深入探讨了Java中方法参数的传递机制,包括值传递和引用传递的区别,以及String类对象的不可变性。通过详细讲解和示例代码,帮助读者理解参数传递的内部原理,并掌握在实际编程中正确处理参数传递的方法。关键词:Java, 方法参数传递, 值传递, 引用传递, String不可变性。
68 1
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
|
2月前
|
安全 Java 测试技术
Java零基础-StringBuffer 类详解
【10月更文挑战第9天】Java零基础教学篇,手把手实践教学!
55 2
|
3月前
|
安全 Java
String类-知识回顾①
这篇文章回顾了Java中String类的相关知识点,包括`==`操作符和`equals()`方法的区别、String类对象的不可变性及其好处、String常量池的概念,以及String对象的加法操作。文章通过代码示例详细解释了这些概念,并探讨了使用String常量池时的一些行为。
String类-知识回顾①
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
29 1
|
2月前
|
数据可视化 Java
让星星月亮告诉你,通过反射创建类的实例对象,并通过Unsafe theUnsafe来修改实例对象的私有的String类型的成员属性的值
本文介绍了如何使用 Unsafe 类通过反射机制修改对象的私有属性值。主要包括: 1. 获取 Unsafe 的 theUnsafe 属性:通过反射获取 Unsafe类的私有静态属性theUnsafe,并放开其访问权限,以便后续操作 2. 利用反射创建 User 类的实例对象:通过反射创建User类的实例对象,并定义预期值 3. 利用反射获取实例对象的name属性并修改:通过反射获取 User类实例对象的私有属性name,使用 Unsafe`的compareAndSwapObject方法直接在内存地址上修改属性值 核心代码展示了详细的步骤和逻辑,确保了对私有属性的修改不受 JVM 访问权限的限制
64 4
|
2月前
|
存储 安全 Java
【一步一步了解Java系列】:认识String类
【一步一步了解Java系列】:认识String类
32 2