美文网首页FlutterFlutter中文社区Flutter圈子
使用Flutter构建漂亮的UI界面(亲测 Mac OS环境操作

使用Flutter构建漂亮的UI界面(亲测 Mac OS环境操作

作者: 7752d0206c56 | 来源:发表于2019-01-14 17:49 被阅读5次

    学习Flutter过程中,整理了一下Flutter官方教程 Building Beautiful UIs with Flutter

    原文地址 https://codelabs.flutter-io.cn/codelabs/flutter/index.html#0

    1.综述

    Flutter 是一个开源 SDK用于创建高性能原生iOS与Android应用

    Flutter框架使你很方便地创建快速响应的UI界面,减少同步更新APP界面的工作量

    Flutter通过使用Material Design和 Cupertino (iOS) 组件使得创建一个漂亮的APP变得更容易

    用户会倾心于你的APP的观感和体验,因为Flutter使用了针对Android和iOS平台的滚动条,导航栏,字体等.

    通过使用设备和模拟器的热部署,开发人员会直观地感受到Flutter框架的强大和对开发效率的显著提升

    Flutter应用使用了Dart语言

    如果你已经学习过Java, JavaScript, C#或Swift, 那么Dart的语法会让你倍感亲切

    Dart在编译时使用了标准的Android和iOS工具,针对不同的移动平台进行处理

    Dart语言提供了丰富的功能包括你可能已经非常熟悉的简洁的语法,first-class functions (将函数作为类似int float的基础类型,可以赋值和作为参数传递),async/await,以及丰富的标准库可供调用

    通过本篇教程可以学习到

    如何使用Flutter编写一个原生的iOS和Android应用

    如何调试Flutter应用

    如何在模拟器和真机上运行Flutter应用

    2.搭建你的Flutter环境

    Flutter环境涉及2个部分,Flutter SDK和一个编辑器(IDE),教程中会使用Android Studio作为例子,但你也可以使用其他你习惯使用的编辑器

    实际代码运行可以选择真机(Android或iOS)连接到电脑,并开启开发者模式

    使用iOS模拟器,需要安装XCode工具

    使用Android模拟器,需要安装Android Studio

    3.创建一个新的Flutter项目

    首先根据模板创建一个简单的Flutter应用,具体可以参考

    原文地址 Getting Started with your first Flutter app.

    中文地址 编写您的第一个Flutter应用

    将项目命名为 friendlychat, 你将会使用这个应用起步来创建一个完整的app

    提示: 如果你在你的Android Studio(或其他IDE)中,没有看到“新建Flutter项目”的选项,请确保您已正确安装了Flutter和Dart插件 (具体可以参考plugins installed for Flutter and Dart.)

    本篇教程中,主要涉及修改 lib/main.dart 这个Dart源文件

    4.创建主用户界面

    通过本教程你将开始实践把一个默认的样例app修改为一个聊天应用

    目标是使用Flutter创建一个应用Friendlychat,一个简单的可扩展的聊天app,包含以下功能:

    实时展示聊天信息

    用户可以通过软键盘或者发送图标来发送一条信息

    UI界面同时适用于iOS与Android设备

    iOS 与 Android 图示如下

    image image

    着手搭建应用的主要框架

    第一个需要添加的元素是一个展示静态标题的简单标题栏

    随着教程不断深入,你会逐步添加更多具有状态的和响应的UI组件

    main.dart文件位于 Flutter 项目lib目录下, 包含main()方法作为程序的启动入口

    main()runApp()方法定义直接参考默认的项目代码. runApp()方法中的输入参数Widget组件

    是Flutter框架用于展示APP运行时的界面. 代码中使用了Material Design元素作为UI界面, 创建一个新的MaterialApp对象,并将它传入runApp()方法,这个组件也就成为了UI界面的根节点

    main.dart

    
    // Replace the code in main.dart with the following.
    
    import 'package:flutter/material.dart';
    
    void main() {
    
      runApp(
    
        new MaterialApp(
    
          title: "Friendlychat",
    
          home: new Scaffold(
    
            appBar: new AppBar(
    
              title: new Text("Friendlychat"),
    
            ),
    
          ),
    
        ),
    
      );
    
    }
    
    

    为了确认用户打开应用时的默认界面, 在MaterialApp定义中设置home参数.

    home参数引用了一个组件,该组件定义了应用的主界面 . 这里新建了一个Scaffold组件, 其中包含了一个简单的AppBar作为子控件

    如果你启动这个app,可以看到如下界面

    iOS 与 Android 图示如下

    image image

    创建聊天界面

    开始着手编写一个交互式的组件, 你需要把这个简单的应用分成两个不同的组件子类:

    一个不会变化的根节点FriendlychatApp组件

    一个子节点ChatScreen组件,用于处理消息发送和内部状态的改变

    目前两个组件都可继承 StatelessWidget (无状态组件)

    后续我们会修改ChatScreen,继承StatefulWidget(有状态组件) 并管理状态.

    main.dart github代码

    
    // Replace the code in main.dart with the following.
    
    import 'package:flutter/material.dart';
    
    void main() {
    
      runApp(new FriendlychatApp());
    
    }
    
    class FriendlychatApp extends StatelessWidget {
    
      @override
    
      Widget build(BuildContext context) {
    
        return new MaterialApp(
    
          title: "Friendlychat",
    
          home: new ChatScreen(),
    
        );
    
      }
    
    }
    
    class ChatScreen extends StatelessWidget {
    
      @override
    
      Widget build(BuildContext context) {
    
        return new Scaffold(
    
          appBar: new AppBar(title: new Text("Friendlychat")),
    
        );
    
      }
    
    }
    
    

    这一步引入了Flutter框架的几个重要的概念

    你通过组件中的build()方法来描述用户界面

    Flutter框架调用FriendlychatApp或ChatScreen中的build()方法来添加组件,并更新组件的显示层级

    @override是Dart的一个标注,用来标识一个方法重写了父类的方法

    部分组件,例如ScaffoldAppBarMaterial Design应用所特有的组件

    其他组件如Text是适用于所有app的通用组件.

    Flutter框架中的不同库的组件可在同一个app中互相兼容并使用

    点击 hot reload 热部署按钮可以迅速看到刚刚做的修改已经生效

    把UI组件分类2个类并修改根节点组件,你应该看到界面没有发生变化

    iOS 与 Android 图示如下

    image image

    5.创建一个UI界面来发送消息

    接下来将会学习如何创建一个UI界面让用户输入并发送消息

    image

    在手机上点击文本框会弹出一个软键盘,用户可以输入非空的文字并按软件盘上的Return来发送消息

    或者,用户可以通过点击输入框胖的发送按钮来发送消息

    目前输入文字的界面在聊天界面的顶部,后续我们会把它移动到底部

    添加一个交互式文本输入框

    Flutter 框架提供了一个 Material Design 文本框组件TextField. 它是一个有状态的组件,拥有可以定制化的输入框属性

    状态指的是指在组件从创建到生命周期结束时,可能发生变化的信息

    为Friendlychat创建第一个有状态的组件需要对其做一些修改

    你需要将数据封装到一个State对象,然后将它和一个StatefulWidget类关联.

    如下main.dart代码中展示了如何添加一个交互式输入框.

    首先修改ChatScreen继承StatefulWidget,而非StatelessWidget.

    TextField用于处理可变的文本内容, ChatScreen则作为文本框控制器, 你需要定义一个新的ChatScreenState类来继承State类.

    重写createState()方法来添加ChatScreenState类. 你需要使用这个新的类来创建带有状态的 TextField组件

    build()方法上方添加一行来定义 ChatScreenState类

    main.dart

    
    // Modify the ChatScreen class definition to extend StatefulWidget.
    
    class ChatScreen extends StatefulWidget {                    //modified
    
      @override                                                        //new
    
      State createState() => new ChatScreenState();                    //new
    
    }
    
    // Add the ChatScreenState class definition in main.dart.
    
    class ChatScreenState extends State<ChatScreen> {                  //new
    
      @override                                                        //new
    
      Widget build(BuildContext context) {
    
        return new Scaffold(
    
          appBar: new AppBar(title: new Text("Friendlychat")),
    
        );
    
      }
    
    }
    
    

    现在ChatScreenState的build()方法包含了之前ChatScreen的创建的所有组件

    当Flutter框架调用build()方法刷新 UI时,将会重建ChatScreenState和它的所有子控件

    提示:经常查看Flutter框架的API和源码对于了解背后的实现机制很有帮助

    可以使用IntelliJ编辑器, 选择一个类或者方法, 然后右键选择查找定义来查看Flutter源码

    TextEditingController对象可以用来读取输入框中的内容并在信息发送后将其清空,接下来我们来创建一下这个对象

    main.dart

    
    // Add the following code in the ChatScreenState class definition.**](https://github.com/flutter/friendlychat-steps/blob/master/offline_steps/step_2_composing_messages/lib/main.dart) 
    
    class ChatScreenState extends State<ChatScreen> {
    
      final TextEditingController _textController = new TextEditingController(); //new
    
    

    接下来我们定义一个私有方法_buildTextComposer()

    返回Container组件, 其中包含了TextField组件

    main.dart

    
    // Add the following code in the ChatScreenState class definition.
    
    Widget _buildTextComposer() {
    
      return new Container(
    
        margin: const EdgeInsets.symmetric(horizontal: 8.0),
    
        child: new TextField(
    
          controller: _textController,
    
          onSubmitted: _handleSubmitted,
    
          decoration: new InputDecoration.collapsed(
    
            hintText: "Send a message"),
    
        ),
    
      );
    
    }
    
    

    Container组件首先在屏幕和输入框的四周添加了一个水平边距,这里使用的单位是逻辑像素,实际会根据不同设备转换为特定数量的物理像素

    这和iOS中的points点的概念和Android中的 dip (density-independent pixels) 设备无关像素是类似的

    设置TextField组件来管理用户交互行为:

    TextField构造器包含一个TextEditingController控制器,该控制器用来读取和清空输入框中的内容

    为了在用户提交时获取通知, 使用onSubmitted参数来提供一个私有的callback回调方法 _handleSubmitted() 目前我们先实现清空输入框的功能,后续会添加实际的发送功能,代码如下

    main.dart

    
    //Add the following code in the ChatScreenState class definition.
    
    void _handleSubmitted(String text) {
    
      _textController.clear();
    
    }
    
    

    提示: 在变量前面的下划线 _ 表明该变量是在类的内部私有的. Dart编译器会强制校验变量的私有性. 具体可参考Dart官方信息 libraries and visibility

    放置文本输入框组件

    接下来需要告诉app如何展示这个文本输入框组件. ChatScreenState类的build()方法中,在body属性上添加一个私有方法 _buildTextComposer

    _buildTextComposerm返回一个组件,组件中包含了文本输入框

    main.dart

    
    // Modify the code in the ChatScreenState class definition as follows.
    
      @override
    
      Widget build(BuildContext context) {
    
        return new Scaffold(
    
          appBar: new AppBar(title: new Text("Friendlychat")),
    
          body: _buildTextComposer(), //new
    
        );
    
      }
    
    

    将app热部署后,可以看到如下界面

    iOS 与 Android

    image image

    添加带有响应的Send发送按钮

    接下来我们在文本框的右侧添加 Send发送按钮.我们使用 Row行组件作为父节点,使得文本框和发送按钮处于同一行.

    然后将TextField组件包裹在Flexible组件中,使得Row行组件中的文本框能够宽度自适应,自动填满Send按钮之外的剩余空间

    main.dart

    
    //Modify the _buildTextComposer method with the code below to arrange the
    
    // text input field and send button.
    
    Widget _buildTextComposer() {
    
      return new Container(
    
        margin: const EdgeInsets.symmetric(horizontal: 8.0),
    
        child: new Row(                                            //new
    
          children: <Widget>[                                      //new
    
            new Flexible(                                          //new
    
              child: new TextField(
    
                controller: _textController,
    
                onSubmitted: _handleSubmitted,
    
                decoration: new InputDecoration.collapsed(
    
                  hintText: "Send a message"),
    
              ),
    
            ),                                                      //new
    
          ],                                                        //new
    
        ),                                                          //new
    
      );
    
    }
    
    

    现在可以着手创建IconButton带Send图标的按钮组件

    在icon属性中,使用常量Icons.send来创建一个新的Icon图标实例. 该常量表示使用了Material Design图标素材库中的send发送图标

    提示:标准Material Design图标素材库列表可以参考Material Icons官网和Icons类中的常量

    IconButton组件嵌入Container父节点组件中,使得你可以自定义按钮的边距, 使之与旁边的输入框视觉上更为匹配. 对于onPressed点击属性, 使用一个匿名方法来调用_handleSubmitted()方法并使用 _textController控制器来传入消息内容

    main.dart

    
    // Modify the _buildTextComposer method with the code below to define the
    
    // send button.
    
    Widget _buildTextComposer() {
    
      return new Container(
    
        margin: const EdgeInsets.symmetric(horizontal: 8.0),
    
        child: new Row(
    
          children: <Widget>[
    
            new Flexible(
    
              child: new TextField(
    
                controller: _textController,
    
                onSubmitted: _handleSubmitted,
    
                decoration: new InputDecoration.collapsed(
    
                  hintText: "Send a message"),
    
              ),
    
            ),
    
            new Container(                                                //new
    
              margin: new EdgeInsets.symmetric(horizontal: 4.0),          //new
    
              child: new IconButton(                                      //new
    
                icon: new Icon(Icons.send),                                //new
    
                onPressed: () => _handleSubmitted(_textController.text)),  //new
    
            ),                                                            //new
    
          ],
    
        ),
    
      );
    
    }
    
    

    提示:Dart语法中, 箭头=> 表示的含义为 { return expression; }.

    Dart 函数相关信息, 包括匿名和嵌套函数, 可以参考Dart Language Tour.

    Material Design主题中按钮默认为黑色

    可以通过传入颜色参数在IconButton带有图标的按钮中设置颜色,或者你也可以直接选择不同的主题

    图标从IconTheme主题组件中继承获得颜色,透明度和尺寸,使用IconThemeData对象来定义这些特征. 把所有组件包裹在IconTheme组件的_buildTextComposer()方法中, 使用data属性来确定ThemeData当前主题所使用的具体值. 详见以下代码

    main.dart

    
    // Modify the _buildTextComposer method with the code below to give the
    
    // send button the current theme's accent color.
    
    Widget _buildTextComposer() {
    
      return new IconTheme(                                            //new
    
        data: new IconThemeData(color: Theme.of(context).accentColor), //new
    
        child: new Container(                                    //modified
    
          margin: const EdgeInsets.symmetric(horizontal: 8.0),
    
          child: new Row(
    
            children: <Widget>[
    
              new Flexible(
    
                child: new TextField(
    
                  controller: _textController,
    
                  onSubmitted: _handleSubmitted,
    
                  decoration: new InputDecoration.collapsed(
    
                    hintText: "Send a message"),
    
                ),
    
              ),
    
              new Container(
    
                margin: new EdgeInsets.symmetric(horizontal: 4.0),
    
                child: new IconButton(
    
                  icon: new Icon(Icons.send),
    
                  onPressed: () => _handleSubmitted(_textController.text)),
    
              ),
    
            ],
    
          ),
    
        ),                                                            //new
    
      );
    
    }
    
    

    BuildContext上下文对象是一个当前应用组件树的句柄. 每个组件都有其自己的BuildContext, 它是StatelessWidget.buildState.build方法返回的组件的父节点. 这意味着_buildTextComposer()方法可以访问State 对象内部的BuildContext对象,而不需要在方法中显示的传入上下文对象

    热部署应用,可以看到如下界面

    iOS****与Android

    image image

    使用IntelliJ来调试你的程序

    IntelliJ 集成开发环境IDE可以让你很方便地调试 Flutter应用,无论是运行在真机还是模拟器上. 你可以:

    选择一个设备或模拟器来调试应用

    查看终端的输出信息

    在代码中设置断点

    在程序运行时查看变量的值

    提示:除了 IntelliJ以外, 还有许多其他工具,命令和技巧可以用来调试Flutter应用 ,更多信息可以参考 Debugging Flutter Applications.

    IntelliJ 编辑器在程序运行时可以展示运行日志,并提供调试界面来设置断点和控制执行流程

    image

    使用断点

    打开你想要设置断点的源文件

    点击你所想设置断点的行,然后在菜单中选择Run运行 > Toggle Line Breakpoint设置行断点. 或者你也可以点击行号右侧来设置断点

    如果你还没有启动调试模式,先将程序终止,然后重新用调试模式启动

    然后IntelliJ 编辑器会启动调试界面,并在到达断点时暂停程序.然后你就可以查看你所需要排查的问题了

    你可以试着使用调试模式,在Friendlychat应用中的build()方法中设置断点,来调试程序

    你可以通过查看调用栈来观察你的app所调用过的方法

    6.创建一个UI界面来展示信息

    有了基础的app框架后,你就可以着手开发展示信息的界面了

    image

    实现消息列表

    接下来,你将创建一个组件来展示用户的聊天信息

    你将使用多个小组件来组装一个更为复杂的组件

    首先创建一个组件来展示单条消息, 然后将其嵌入一个滚动列表, 接着把滚动列表加入基础的应用框架

    先来定义展示单条消息的组件

    定义一个无状态组件StatelessWidget名为ChatMessage, 它的build()方法返回一个Row行组件,它展示了一个简单的发送信息的用户头像, 一个Column列组件包含了发送消息的人的名字以及消息内容

    main.dart

    
    // Add the following class definition to main.dart.
    
    class ChatMessage extends StatelessWidget {
    
      ChatMessage({this.text});
    
      final String text;
    
      @override
    
      Widget build(BuildContext context) {
    
        return new Container(
    
          margin: const EdgeInsets.symmetric(vertical: 10.0),
    
          child: new Row(
    
            crossAxisAlignment: CrossAxisAlignment.start,
    
            children: <Widget>[
    
              new Container(
    
                margin: const EdgeInsets.only(right: 16.0),
    
                child: new CircleAvatar(child: new Text(_name[0])),
    
              ),
    
              new Column(
    
                crossAxisAlignment: CrossAxisAlignment.start,
    
                children: <Widget>[
    
                  new Text(_name, style: Theme.of(context).textTheme.subhead),
    
                  new Container(
    
                    margin: const EdgeInsets.only(top: 5.0),
    
                    child: new Text(text),
    
                  ),
    
                ],
    
              ),
    
            ],
    
          ),
    
        );
    
      }
    
    }
    
    

    参考如上代码来定义_name变量, 我们用这个变量来表示发送者的名字. 为了简化代码,这里可以先给这个变量赋一个固定值. 更通用的做法是通过用户登录验证来获取用户名字,具体可参考Firebase for Flutter

    main.dart

    
     //Add the following code to main.dart.
    
    const String _name = "Your Name";
    
    

    为了定制CircleAvatar组件, 可以把用户名称_name的首字母赋给Text组件. 我们使用CrossAxisAlignment.start对齐方式传入Row行组件中来定位头像和消息相对于父节点组件的位置

    头像的父节点是一个Row行组件,主坐标为水平方向.CrossAxisAlignment.start坐标对齐方式表示从头开始(从最上端开始). 消息的父节点是一个Column列组件,主坐标为垂直方向,CrossAxisAlignment.start坐标对齐方式表示从头开始(从最左侧开始)

    头像的旁边是2个纵向排列的Text文本组件,上面展示发送者的名字,下面展示消息

    为了给发送者名字加上样式,使名字显示的比消息文本大一些,你需要使用Theme.of(context)来获取一个ThemeData主题数据对象. textTheme文本主题属性代表了 Material Design的一个文本样式比如subhead副标题, 使你可以避免在代码中写死字体字号等属性

    目前我们还没有为app指定一个主题,Theme.of(context)会得到一个默认的Flutter主题

    后续你可以重写这个默认主题,使得你的app在android和iOS中展示不同的样式

    实现消息列表

    接下来进一步细化UI,获取聊天消息并在界面中展示

    这个列表应是可滚动的,使得用户可以查看聊天历史记录

    排序应采用时间顺序,优先展示最新的消息

    在ChatScreenState组件中, 添加一个List列表成员变量_messages来表示消息列表. 列表中每一项都是一个ChatMessage聊天消息实例. 你需要把这个列表初始化为一个空列表

    
    // Add the following code to the ChatScreenState class definition.
    
    class ChatScreenState extends State<ChatScreen> {
    
      final List<ChatMessage> _messages = <ChatMessage>[];            // new
    
      final TextEditingController _textController = new TextEditingController();
    
    

    main.dart

    当用户发送消息时,需要把新消息加入到消息列表中

    修改_handleSubmitted()处理提交方法, 如下

    
    // Modify the code in the _handleSubmitted method definition.
    
    void _handleSubmitted(String text) {
    
      _textController.clear();
    
        ChatMessage message = new ChatMessage(                        //new
    
          text: text,                                                  //new
    
        );                                                            //new
    
        setState(() {                                                  //new
    
          _messages.insert(0, message);                                //new
    
        });                                                            //new
    
    }
    
    

    main.dart

    调用setState()来修改_messages列表,并且通知Flutter框架,组件树中的部分内容已经发生了修改,需要重建UI. 在setState()设置状态方法中只应该有同步操作,否则组件可能在操作完成前就已被重建.

    相关文章

      网友评论

        本文标题:使用Flutter构建漂亮的UI界面(亲测 Mac OS环境操作

        本文链接:https://www.haomeiwen.com/subject/sjejdqtx.html