Material是什么?Scaffold脚手架如何使用?Material风格常用组件教程

2,436 阅读7分钟

Material

Flutter集成了material的组件。

Materia(官网需要科学上网)是谷歌的一套设计风格的组件,它为设计师提供了纸墨风格的整体的设计思路。原生安卓应用也遵循这套设计风格。

以下图片是官网的几个示例。

我们可以通过material组件快速生成一个好看的App。

比如我们可以通过materiApp快速生成一个导航栏,而不用考虑导航栏某个图标的具体位置、某个字的大小。

一个简单基本的结构

废话不多说。先走一个基本的程序。

使用MaterialApp组件,创建一个导航栏和一个hello word的文本。之后详解各个部件使用。

import 'package:flutter/material.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(      //  home 属性为App的入口
        appBar: AppBar(
          title: Text("这是第一个程序"),   
        ),
        body: Center(
          child: Text("Hello world"),
        ),
      ),
    );
  }
}

如果我们不使用这种骨架,仍然使用material风格的背景颜色和字体样式。尝试手动写一个MyScaffold的骨架。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyScaffold(),
    );
  }
}

class MyScaffold extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Material(
      child: Column(
        children: <Widget>[
          MyAppBar(),
          Expanded(
            child: Center(child: Text("Hello world")),
          )
        ],
      ),
    );
  }
}

class MyAppBar extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      height: 80, 
      padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
      decoration: BoxDecoration(color: Colors.blue[500]),
      child: Column(
        children: <Widget>[
          Spacer(),
          Expanded(
            flex: 2,
            child: Row(
              children: <Widget>[
                Expanded(
                  child: Text(
                    "这是第一个程序",
                    style: Theme.of(context).primaryTextTheme.title,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

void main() => runApp(MyApp());

于是代码量变得很大。试想一下。如果从头手写整个骨架。代码量将会更大。

Scaffold脚手架

Scaffold脚手架是整个应用的骨架。我们可以将其抽象成还未被填词的旋律。我们拿到它只需要构建内容就完全可以了。当然,发现提供的旋律不是我们想要的也可以将某一部分替换成其他的,甚至整套样式都不喜欢我们也可将默认的样式都替换。

首先我们看一下scaffold的构造函数的参数。

Scaffold({Key key, PreferredSizeWidget appBar, Widget body, Widget floatingActionButton, FloatingActionButtonLocation floatingActionButtonLocation, FloatingActionButtonAnimator floatingActionButtonAnimator, List<Widget> persistentFooterButtons, Widget drawer, Widget endDrawer, Widget bottomNavigationBar, Widget bottomSheet, Color backgroundColor, bool resizeToAvoidBottomPadding, bool resizeToAvoidBottomInset, bool primary = true, DragStartBehavior drawerDragStartBehavior = DragStartBehavior.start, bool extendBody = false, Color drawerScrimColor, double drawerEdgeDragWidth})
属性 构造函数或者类型
body Widget
appbar(导航栏) Widget
bottomNavigationBar(底部状态栏) BottomAppBar
floatingActionButton(悬浮按钮) FloatingActionButton
floatingActionButtonLocation(悬浮按钮位置) FloatingActionButtonLocation
drawer(侧边栏) Widget

我们再MaterialApp的home属性中添加构造函数Scaffold

创建一个简单的脚手架应用:

import 'package:flutter/material.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(   //导航栏
          title: Text("脚手架组件的示例"),
        ),
        body: Center(child:Text("Scaffold")),   // 身体部分
        bottomNavigationBar: BottomAppBar(   //  底部导航栏
          child: Container(
            height: 70.0,//高度
          ),
        ),
        floatingActionButton: FloatingActionButton(    //  悬浮按钮
          onPressed: (){},  //  点击事件(一般是一个函数)
          child: Icon(Icons.add),  //  图标样式
          tooltip: "增加",     // 提示信息
        ),
        floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,// 悬浮按钮的位置
        drawer: Icon(    //侧边栏
          Icons.menu    //图标样式
        ),
      ),
    );
  }
}

AppBar导航栏

首先介绍Scaffold的参数appBar。

appBar属性的参数类型为PreferredSizeWidget。构造函数是AppBar

AppBar({Key key, Widget leading, bool automaticallyImplyLeading = true, Widget title, List<Widget> actions, Widget flexibleSpace, PreferredSizeWidget bottom, double elevation, ShapeBorder shape, Color backgroundColor, Brightness brightness, IconThemeData iconTheme, IconThemeData actionsIconTheme, TextTheme textTheme, bool primary = true, bool centerTitle, double titleSpacing = NavigationToolbar.kMiddleSpacing, double toolbarOpacity = 1.0, double bottomOpacity = 1.0})

在material官网上我们可以看到这样的样式。只要在AppBar的属性写就可以在相应的位置补充内容。

import 'package:flutter/material.dart';

void main() => runApp(new MaterialApp(
      title: "appBar示例",
      home: Layout(),
    ));

class Layout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: Icon(Icons.menu),
      
        title: Text("AppBar"),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.search),
            tooltip: '搜索',
            onPressed: () {},
          ),
          IconButton(
            icon: Icon(Icons.add),
            tooltip: '添加',
            onPressed: () {},
          ),
        ],
      ),
      body: Text("appbar示例"),
    );
  }
}

导航栏部件

我们看到在materai示例中,还有一个区域为bottom。

我们可以在这个区域创建一个顶部的导航栏。

我们可以使用TabBarTabBarView这两个组件

DefaultTabController创建TabBarTabBarView的控制器,关联俩组件的桥梁

DefaultTabController必须给定 length且必须和TabBar 的tabs 的长度和TabBarView的 children的长度相同(别晕~ ~请看接下来的示例。)。

import 'package:flutter/material.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget{
   final List<Tab> myTabs =<Tab>[    
      Tab(text: "1",),
      Tab(text: "2",)
    ];
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '水平选项卡视图组件',
      home: DefaultTabController(    // 控制器关联两导航栏和内容
        length: 2,       //  长度
        child: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              tabs: myTabs   //  使用上面的变量
            ),
          ),
          body: TabBarView(
            children: myTabs.map(    // 后面介绍map的功能
              (Tab tab){
                return Center(
                  child: Text(tab.text),
                );
              }
            ).toList()
          ),
        ),
      ),
    );
  }
}

补充内容

final List<Tab> myTabs =<Tab>[
      Tab(text: "1",),
      Tab(text: "2",)
    ];
myTabs.map(   // 将myTabs的每一个 tab传入
    (Tab tab){
    	return Center(child: Text(tab.text));   // 返回一个 Center
}).toList()     //  将全部返回情况的转化为一个列表

一个复杂的导航栏:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class ItemView {
  const ItemView({this.title, this.icon});
  final String title;
  final IconData icon;
}

const items = <ItemView>[
  ItemView(title: "自驾", icon: Icons.directions_car),
  ItemView(title: "自行车", icon: Icons.directions_bike),
  ItemView(title: "船", icon: Icons.directions_boat),
  ItemView(title: "公交车", icon: Icons.directions_bus),
  ItemView(title: "地铁", icon: Icons.directions_railway),
  ItemView(title: "跑", icon: Icons.directions_run),
  ItemView(title: "火车", icon: Icons.directions_subway),
  ItemView(title: "运输", icon: Icons.directions_transit),
  ItemView(title: "走", icon: Icons.directions_walk),
];

// --------------------------------
List directionsTabs = items.map((ItemView item) {
  return Tab(
    text: item.title,
    icon: Icon(item.icon),
  );
}).toList();
//---------------------------------
// 这里最好写于tabs属性里 每次热重载时都会调用build方法(详情大家查阅以下statelessWidget的热重载模式)。如果在这里只能用大写R热重载。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: "tarbar水平列表篇",
        home: DefaultTabController(
          length: items.length,
          child: Scaffold(
            appBar: AppBar(
              bottom: TabBar(isScrollable: true, tabs: directionsTabs),
            ),
            body: TabBarView(
                children: items.map((item) {
              return Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: SelectedView(item: item));
            }).toList()),
          ),
        ));
  }
}

class SelectedView extends StatelessWidget {
  SelectedView({Key key, this.item}) : super(key: key);
  final ItemView item;
  @override
  Widget build(BuildContext context) {
    final TextStyle textStyle = Theme.of(context).textTheme.display1; //提取上一级的主题
    return Card(
      child: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Icon(item.icon, size: 128.0, color: textStyle.color),
            Text(
              item.title,
              style: textStyle,
            )
          ],
        ),
      ),
    );
  }
}

drawer组件

我们先看看material官方怎么说drawer组件

Drawer组件可以添加头部效果。

UserAccountsDrawerHeader

属性名 类型 说明
decoration Decoration 头部区域的decoration设置背景颜色和样式
accountName Widget 设置当前用户名
accountEmail Widget 设置当前用户的邮箱
margin EdgeInsetGeometry 设置四周间隙
OnDetailsPressed VoidCallback 回调函数。设置当用户名或者有限被点击时。触发的回调函数。
currentAccountPicture Widget 用来设置当前用户的头像
otherAccountPicture Widget 用来设置其他账号头像

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        drawer: Drawer(
          child: ListView(
            children: <Widget>[
              UserAccountsDrawerHeader(    // Hearder的位置信息
                accountName: Text("痴痴那"),
                accountEmail: Text("1161838951@qq.com"),
                currentAccountPicture: CircleAvatar(
                  backgroundImage: AssetImage("images/1.png"),
                ),
              ),
              ListTile(
                leading: CircleAvatar(
                  backgroundImage: AssetImage("images/1.png"),
                ),
                title: Text("个性装扮"),
              ),
              ListTile(
                leading: CircleAvatar(
                  backgroundImage: AssetImage("images/1.png"),
                ),
                title: Text("个性装扮"),
              ),
              ListTile(
                leading: CircleAvatar(
                  backgroundImage: AssetImage("images/1.png"),
                ),
                title: Text("个性装扮"),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Material的路由管理

第一种方式通过

Navigator.push(context,MaterialPageRoute(builder: (context) => NewRoute())

context这里有个问题详情请看这里

juejin.cn/post/684490…

MaterialPageRoute继承自PageRoute

/// A modal route that replaces the entire screen.

替换整个屏幕的模态路由。

而MaterialPageRoute初始化了PageRoute中的一些属性(动画等)

class MaterialPageRoute<T> extends PageRoute<T> 
MaterialPageRoute({
    @required this.builder,
    RouteSettings settings,
    this.maintainState = true,
    bool fullscreenDialog = false,
  }) 

Navigator是一个路由管理的组件,它提供了打开和退出路由页方法。Navigator通过一个栈来管理活动路由集合。通常当前屏幕显示的页面就是栈顶的路由。Navigator提供了一系列方法来管理路由栈。pushpop

// 第一种使用路由的方法
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //这里的上下文对象为根部
    return MaterialApp(home: HomePage());
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //这里的上下文对象是MaterialApp
    return Scaffold(
      appBar: AppBar(
        title: Text("data"),
      ),
      body: Center(
          child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text("home page"),
          FlatButton(
            child: Text("open new route"),
            textColor: Colors.blue,
            onPressed: () {
              //导航到新路由
              Navigator.push(      //通过Navigator.push方法
                context,
                MaterialPageRoute(builder: (context) => NewRoute()),
              );
            },
          ),
        ],
      )),
    );
  }
}

class NewRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text("This is new route"),
      ),
    );
  }
}

第二种方式

设置materialApp的routes属性 Navigator.pushNamed(context, '/second');

routes: {

	'/frist':(BuildContext context)=>FristPage(),

	'/second':(BuildContext context)=>SecondPage()

}

一个使用pushName的例子:注意页面参数的传递以及钩子函数

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {
        '/': (context) => MyHomePage(),
        '/a': (context) => MyHomePageA(),
        '/b': (context) =>
            MyHomePageB(text: ModalRoute.of(context).settings.arguments)
      },

      initialRoute: '/', //   初始化之后进入的页面
      onGenerateRoute: (RouteSettings routeSettings) {
        return MaterialPageRoute(builder: (context) {
          return MyHomePageC();
        }); //  MaterialPageRoute是一个控制路由的
      },
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text("Home Page"),
            RaisedButton(
              //方式一
              child: Text("open A page"),
              onPressed: () async {
                // 异步函数
                // Navigator.pushNamed(context, '/a');   //通过注册路由表来跳转页面、
                var a = await Navigator.pushNamed(context, '/a',
                    arguments: "我是传入的数据");
                print("$a"); //    await  默认返回一个 Future 可以用then提取
                // 返回的数据
              },
            ),
            RaisedButton(
              child: Text("open B page"),
              onPressed: () {
                Navigator.of(context).pushNamed('/b', arguments: "我是传入B的参数");
              },
            ),
            RaisedButton(
              child: Text("跳转到不存在的页面"),
              onPressed: () {
                Navigator.pushNamed(context, '/c');
              },
            )
          ],
        ),
      ),
    );
  }
}

class MyHomePageA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var args = ModalRoute.of(context).settings.arguments;
    return Scaffold(
      appBar: AppBar(
          leading: IconButton(
        icon: Icon(Icons.arrow_back),
        onPressed: () {
          Navigator.pop(context, "这是传回的数据");
        },
      )),
      body: Center(
        child: Text(args),
      ),
    );
  }
}

class MyHomePageB extends StatelessWidget {
  MyHomePageB({Key key, this.text}) : super(key: key);
  final String text;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text(text),
      ),
    );
  }
}

class MyHomePageC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text("123"),
      ),
    );
  }
}

按钮和提示组件

我们用上面见到的悬浮按钮FloatingActionButton生成一个SnackBar的提示

FloatingActionButton

SnackBar

SnackBar在脚手架底部显示一个弹窗,脚手架一次最多可以显示一个SnackBar。如果在另一个SnackBar已经可见的情况下调用此功能,则当前的SnackBar将被添加到队列中并在之前的SnackBar关闭后显示。

使用Scaffold.of(context).showSnackBar(snackBar);

 const SnackBar({
    Key key,
    @required this.content,   // SnackBar的内容
    this.backgroundColor,  // 背景颜色
    this.elevation,     // 控制阴影的大小 double类型
    this.shape,         //  SnackBar的形状
    this.behavior,     	//   定义了SnackBar的位置和行为
    this.action,        // 控制SnackBar
    this.duration = _snackBarDisplayDuration,    //  SnackBar 的时间
    this.animation,     //  SnackBar 的动画
  }) 
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final snackBar = new SnackBar(
      behavior: SnackBarBehavior.floating,
      content: new Text('删除信息'),
      action: new SnackBarAction(
          label: '撤消',
          onPressed: () {
                 // 设置一些新为信息
          }),
    );
    return MaterialApp(
        title: "按钮",
        home: Scaffold(
          appBar: AppBar(
            title: Text("按钮"),
          ),
          body: Builder(builder: (BuildContext context) {
            return RaisedButton(
              child: Text("隐藏"),
              onPressed: () {
                Scaffold.of(context).hideCurrentSnackBar();
              },
            );
          }),
          floatingActionButtonLocation:
              FloatingActionButtonLocation.centerFloat,
          floatingActionButton: Builder(builder: (BuildContext context) {
            return FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: () {
                // Scaffold.of(context).removeCurrentSnackBar();   //清除当前SnackBar
                var a = Scaffold.of(context).showSnackBar(snackBar);
                //   SnackBar(
                //   content: Text("你点击了"),
                // ));
                // a.close();   //关闭
              },
            );
          }),
        ));
  }
}

扁平按钮

点击时有一个阴影效果

与悬浮按钮类似都是需要一个回调函数

FlatButton(onPressed: (){},child: Text("扁平悬浮按钮"),),

import 'package:flutter/material.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget{
@override
  Widget build(BuildContext context) {
    
    return MaterialApp(
      title: "扁平悬浮按钮",
      home: Scaffold(
        appBar: AppBar(

        ),
        body: Center(
          child: FlatButton(
            onPressed: (){
                // 执行一些操作
                
            },
            child: Text("扁平悬浮按钮"),
          ),
        ),
      ),
    );
  }
}

弹出菜单

PopupMenuButton<T>

构造函数

 PopupMenuButton({
    Key key,
    @required this.itemBuilder,    //  必须不为空
    this.initialValue,
    this.onSelected,              //  当选中某项时调用此
    this.onCanceled,              
    this.tooltip,
    this.elevation,
    this.padding = const EdgeInsets.all(8.0),
    this.child,
    this.icon,
    this.offset = Offset.zero,
    this.enabled = true,
    this.shape,
    this.color,
  }) 

弹出菜单widget使用接口与菜单项关联。

用showMenu函数显示弹出菜单,也可以使用PopupMenuButton去创建按键触发弹出菜单。

[PopupMenuEntry] 代表弹出的列表

  • [PopupMenuItem], 单个值的弹出菜单项。
  • [PopupMenuDivider], 项的分割线
  • [CheckedPopupMenuItem], 单个值的项用标记已选

实例:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());
enum ConferenceItem {
  AddMember,
  LockConference,
  ModifyLayout,
  TurnoffAll,
  TurnoffAllOF
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "弹出菜单",
      home: Scaffold(
        appBar: AppBar(
          title: Text("弹出菜单"),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
            
              FlatButton(
                onPressed: () {},
                child: PopupMenuButton<ConferenceItem>(
                  child: Text("打开弹出层"),
                  onSelected: (ConferenceItem res) {
                    print(res);    // res的值为选中项的value
                  },
                  itemBuilder: (BuildContext context) =>
                      <PopupMenuEntry<ConferenceItem>>[
                    PopupMenuDivider(),
                    PopupMenuItem(
                      value: ConferenceItem.AddMember,
                      child: Text("a"),
                    ),
                    PopupMenuDivider(),
                    PopupMenuItem(
                      value: ConferenceItem.LockConference,
                      child: Text("b"),
                    ),
                    PopupMenuDivider(),
                    PopupMenuItem(
                      value: ConferenceItem.ModifyLayout,
                      child: Text("c"),
                    ),
                    PopupMenuDivider(),
                    PopupMenuItem(
                      value: ConferenceItem.TurnoffAll,
                      child: Text("d"),
                    ),
                    PopupMenuDivider(),
                    CheckedPopupMenuItem(    //
                      value: ConferenceItem.TurnoffAllOF,
                      checked: true,
                      child: Text("e"),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

简单对话框

通常SimpleDialog(对话框)搭配SimpleDialogOption(对话框的项)一块使用

import 'package:flutter/material.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "简单对话框",
      home: Scaffold(
        appBar: AppBar(title: Text("简单对话框"),),
        body: Center(
          child: SimpleDialog(    
            title: Text("对话框"),
            children: <Widget>[
              SimpleDialogOption(
                onPressed: (){},
                child: Text("第一行信息"),
              ),
              SimpleDialogOption(
                onPressed: (){},
                child: Text("第二行信息"),
                
              )
            ],
          ),
        ),
      )
    );
  }
}

提示对话框

AlertDialog对话框。

 const AlertDialog({
    Key key,
    this.title,                   // 标头
    this.titlePadding,            // 标头 内边距
    this.titleTextStyle,          // 标头的文本样式
    this.content,                 //  内容
    this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),   // 内容的编剧
    this.contentTextStyle,       // 内容的样式
    this.actions,                //  按键
    this.backgroundColor,        // 背景颜色
    this.elevation,              //  z轴坐标
    this.semanticLabel,         
    this.shape,					// 边框的形状
  })

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "提示对话框",
      home: Scaffold(
          appBar: AppBar(
            title: Text("提示对话框"),
          ),
          body: Center(
            child: AlertDialog(
              title: Text("提示"),
              actions: <Widget>[
                FlatButton(
                  onPressed: () {},
                  child: Text('确定'),
                ),
                FlatButton(
                  onPressed: () {},
                  child: Text('取消'),
                ),
              ],
              content: SingleChildScrollView(
                child: ListBody(
                  children: <Widget>[
                    Text("是否要删除"),
                    Text("不可恢复"),
                  ],
                ),
              ),
            ),
          )),
    );
  }
}

文本框组件

TextField提供文本输入的组件

import "package:flutter/material.dart";
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget{
  
  @override
  Widget build(BuildContext context) {
  final TextEditingController controller=TextEditingController()  ;
  controller.addListener((){
    print(controller.text);
  });
    return MaterialApp(
      title: "TextField",
      home: Scaffold(
        appBar: AppBar(title: Text("data"),),
        body: Center(
          child: TextField(
            controller: controller,
            maxLength: 30,
            maxLines: 1,
            autocorrect: true,
            autofocus: true,
            textAlign: TextAlign.center,
            onChanged: (v){
              print("onchange   $v");

            },
            onSubmitted: (v){
              print("subumit $v");
            },
          ),
        ),
      ),
    );
  }
}

控制台输出

卡片组件

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var card = SizedBox(
      height: 600,
      child: Card(
        child: Column(
          children: <Widget>[
            ListTile(
              title: Text(
                "清华大学",
                style: TextStyle(fontWeight: FontWeight.w300),
              ),
              subtitle: Text("清华大学"),
              leading: Icon(Icons.school),
            ),
            Divider(),
            ListTile(
              title: Text(
                "北京大学",
                style: TextStyle(fontWeight: FontWeight.w300),
              ),
              subtitle: Text("北京大学"),
              leading: Icon(Icons.school),
            ),
            Divider(),
            ListTile(
              title: Text(
                "浙江大学",
                style: TextStyle(fontWeight: FontWeight.w300),
              ),
              subtitle: Text("浙江大学"),
              leading: Icon(Icons.school),
            ),
          ],
        ),
      ),
    );
    return MaterialApp(
      title: "card",
      home: Scaffold(
          appBar: AppBar(
            title: Text("card"),
          ),
          body: Center(child: card)),
    );
  }
}

参考