Flutter基础——主题、字体和表单

Posted by lingjye on July 18, 2019

主题

Flutter实现了一套漂亮的MD(Material Design)组件,可以定制许多样式和主题。在Main函数中使用声明的顶级widget,MaterialApp作为程序入口。(类似于iOS中需要声明在Appdelegate中设置window的rootController作为iOS应用入口)。

MaterialApp在Flutter中是一个非常便利的组件,包含了许多App通常需要的MD风格组件,它通过一个WidgetsApp添加了MD功能来实现。类似的,在WidgetApp中也提供了许多相似功能,但是没有MaterialApp强大,所以推荐使用MaterialApp组件。

在iOS上可以用Cupertino library来制作遵守Human Interface Guidelines的界面。可以查看Cupertino widgets gallery来了解这些widget的集合。

在MaterialApp中可以设置ThemeData对象,来自定义主题颜色和样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MaterialSampleApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        // 设置主题色是蓝色
        primarySwatch: Colors.blue,
        // 设置文字选中时为红色
        textSelectionColor: Colors.red,
      ),
      home: MaterialSampleAppPage(),
    );
  }
}

自定义字体

在Flutter中使用自定义字,跟iOS一样需要把字体库放到工程路径下的文件夹中,但是Flutter需要在pubspec.yaml中添加引用,可以参考图片资源的引用

1
2
3
4
5
fonts:
   - family: MyCustomFont
     fonts:
       - asset: fonts/MyCustomFont.ttf
       - style: italic

使用字体:

1
2
3
4
child: Text(
	'This is a custom font text',
	style: TextStyle(fontFamily: 'MyCustomFont'),
),

使用iconfont,一样需要pubspec.yaml文件添加引用,然后使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class MaterialSampleAppPage extends StatelessWidget {
  const MaterialSampleAppPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Theme'),
      ),
      body: Center(
        // child: Text(
        //   '自定义字体 Custom Font',
        //   style: TextStyle(fontFamily: 'your font family', fontSize: 20),
        // ),
        child: Icon(
          // 使用iconfont
          IconData(
            0xea74,
            fontFamily:'iconfont',
          ),
          color: Colors.red,
          size: 100,
        )
      ),
    );
  }
}

字体样式

除了字体意外,还可以给Text widget的样式元素设置自定义值。通过在Text的TextStyle对象来设置,可设置参数:

  • color
  • decoration
  • decorationColor
  • decorationStyle
  • fontFamily
  • fontSize
  • fontStyle
  • fontWeight
  • hashCode
  • height
  • inherit
  • letterSpacing
  • textBaseline
  • wordSpacing

表单输入

FLutter中使用的widget是不可变的,并且状态是分离的,所以使用表单需要通过特定的widget来完成表单操作。例如TextField或TextFormField,使用TextEditingController获取用户输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class _FormSampleAppPageState extends State<FormSampleAppPage> {
  // 创建表单
  final formController = TextEditingController();

  @override
  void dispose() {
    // TODO: implement dispose
    formController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
       child: Scaffold(
         appBar: AppBar(
           title: Text('表单输入'),
         ),
         body: Padding(
           padding: const EdgeInsets.all(16.0),
           child: TextField(
             controller: formController,
             style: TextStyle(
               color: Colors.red
             ),
           ),
         ),
         floatingActionButton: FloatingActionButton(
           // 用户点击时显示
           onPressed: () {
             return showDialog(
               context: context,
               builder: (context) {
                 return AlertDialog(
                   title: Text('输入内容'),
                   content: Text(formController.text),
                  );
               }
             );
           },
           tooltip: '点击',
           child: Icon(Icons.text_fields),
         ),
       ),
    );
  }
}

可以通过向Text widget的装饰构造器函数传入InputDecoration来展示提示文字(placeholder):

1
2
3
4
5
body: Center(
  child: TextField(
    decoration: InputDecoration(hintText: "This is a hint"),
  ),
)

错误信息展示

在InputDecoration对象中传入errorText,可以展示错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class _FormSampleAppPage extends StatefulWidget {
  _FormSampleAppPage({Key key}) : super(key: key);

  __FormSampleAppPageState createState() => __FormSampleAppPageState();
}

class __FormSampleAppPageState extends State<_FormSampleAppPage> {
  String _errorText;
  
  void showError(text) {
    setState(() {
      if (!isEmail(text)) {
        _errorText = 'Error, This is not email.';
      } else {
        _errorText = null;
      }
    });
  }

  bool isEmail(text) {
    // 正则匹配
    String emailRegexp = r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
    RegExp regExp = RegExp(emailRegexp);
    return regExp.hasMatch(text);
  }

  _getErrorText() {
    print(_errorText);
    return _errorText;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
       appBar: AppBar(
         title: Text('表单'),
       ),
       body: Center(
         child: TextField(
           onSubmitted: (String text) {
             showError(text);
             print('提交');
           },
           decoration: InputDecoration(
             hintText: '请输入一个email',
             errorText: _getErrorText()
           ),
         ),
       ),
    );
  }
}

本文Demo