flutter学习笔记

Flutter

文章目录

1.Flutter配置(Windows)

1.1 JDK以及SDK

1.2 Android Studio

1.3 Vscode

2. 第一个项目

  • vscode引包快捷键:

    如引入material包:fim:f->flutter i->import m->包material

  • 在dart/flutter中实例化的时候可以不写new

自定义组件

MaterialApp组件

一般作为顶层Widget使用。

常用属性:

​ home(主页) title(标题) color(颜色) theme(主题) routes(路由)

Scaffold组件

主要属性:

​ appBar(显示在页面顶部的一个AppBar)

​ body(当前页面所显示的主要内容Widget)

​ drawer(抽屉菜单控件)

图片组件
  • 一般放在一个container里,这样可以定义图片大小和分布方式等。

  • 储存图片的文件夹里要分级 2.0x 3.0x 4.0x 前两个是必须的,因为flutter运行在不同配置的手机里可以自动找到所在目录。用的时候直接用放在images下的路径即可。

  • 圆形图片的4种写法:

    • child: Container(       
              width: 300,
              height: 300,
              decoration: BoxDecoration(
                color: Colors.yellow,
                borderRadius: BorderRadius.circular(150),
                image: DecorationImage(
                  fit: BoxFit.cover
                )
              ),
            )
      
    •       child: Container(       
              width: 300,
              height: 300,
              decoration: BoxDecoration(
                color: Colors.yellow,
                 borderRadius: BorderRadius.all(
                   Radius.circular(150),
                 )
                image: DecorationImage(   image:NetworkImage("https://www.itying.com/images/201905/thumb_img/1101_thumb_G_1557845381862.jpg"),
                  fit: BoxFit.cover
                )
              ),
            )
      
    • child: Container(       
              child: ClipOval(
                  child: Image.network(              
                    'http://www.ionic.wang/statics/index/images/ionic4.png',
                    height: 100,
                    width: 100,
                    fit: BoxFit.cover,
                  ),
              ),
            )
      
    • child:CircleAvatar(
                            backgroundImage:NetworkImage(value["imageUrl"]),
      

    推荐第三四种,可以适应各种图片大小形状要求,根据图片本来尺寸自适应成椭圆也可,也可以自己设置宽高变为正圆。

    注意23方法里远程图片的两种导入方式

  • 本地图片导入的时候,还需要再pubspec.yaml文件的flutter下用assets关键字导入,并且空格等格式要注意

    flutter:

    uses-material-design: true

    assets:

    - images/logo.jpg

基础列表组件ListView
  • 列表包括水平列表和垂直列表
  • LisrView里不能再包括ListView
动态列表组件 动态循环

实现方法有两种:

  1. 循环遍历动态数据,如果是用map转换最后返回还需要加.toList()

  2. ListView.builder()

return ListView.builder(
        itemCount:this.list.length,//指定数据长度
        itemBuilder:(context,index){
          return ListTile(
            title: Text(this.list[index]),
          );
        }
    );
GridView组件(网格布局)
  1. GridView.count,静态布局,不过可以结合for循环和map实现动态传参
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('FlutterDemo')),
        body: LayoutDemo(),
      ));
  }
}
class LayoutDemo extends StatelessWidget {  

  List<Widget> _getListData() {
      List<Widget> list = new List();

      for (var i = 0; i < 20; i++) {
        list.add(Container(
          alignment: Alignment.center,
          child: Text(
            '这是第$i条数据',
            style: TextStyle(color: Colors.white, fontSize: 20),
          ),
          color: Colors.blue,        
          // height: 400,  //设置高度没有反应
        ));
      }
      return list;
  }

  @override
  Widget build(BuildContext context) {    
    return GridView.count(
        crossAxisSpacing:20.0 ,   //水平子 Widget 之间间距
        mainAxisSpacing: 20.0,    //垂直子 Widget 之间间距
        padding: EdgeInsets.all(10),
        crossAxisCount: 2,  //一行的 Widget 数量
        childAspectRatio:0.7,  //宽度和高度的比例
        children: this._getListData(),
    );
  }
}

  1. GridView.builder

Padding组件

两个属性:padding和children

Row水平布局组件
Column垂直布局组件

和ListView组件的区别就是,前者多宽就是多宽,后者会自动铺满整个屏幕

Expanded组件

自适应比例布局组件

实例1:三个组件比例是1:2:1

return Row(    
        children: <Widget>[
          Expanded(
            flex: 1,
            child: IconContainer(Icons.search,color: Colors.blue)
          ),
           Expanded(
            flex: 2,
            child: IconContainer(Icons.home,color: Colors.orange),  
          ),
           Expanded(
            flex: 1,
            child: IconContainer(Icons.select_all,color: Colors.red),  
          ),
        ],    
    );

实例2:右侧固定宽度100px,左侧自适应

return Row(    
     
        children: <Widget>[
          Expanded(
            flex: 1,
            child: IconContainer(Icons.home,color: Colors.orange),  
          ),
          IconContainer(Icons.search,color: Colors.blue)
               
        ],    
    );
    
    class IconContainer extends StatelessWidget{
  double size=32.0;
  Color color=Colors.red;
  IconData icon;
  IconContainer(this.icon,{this.color,this.size});
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      height: 100.0,
      width: 100.0,
      color: this.color,
      child: Center(
        child: Icon(this.icon,size: this.size,color: Colors.white)
      ),
    );
  }
}
Stack层叠组件

主要用于定位布局,更复杂的可以结合Align组件和Positioned组件实现定位布局

  • 属性:

    • children

    • alignment

  • Align

return Center(
      child:  Container(
            height: 400,
            width: 300,
            color: Colors.red,
            child: Stack(
              // alignment: Alignment.center,
              children: <Widget>[
                Align(
                  alignment: Alignment(1,-0.2),
                  child: Icon(Icons.home,size: 40,color: Colors.white),
                ),
                Align(
                  alignment: Alignment.center,
                  child: Icon(Icons.search,size: 30,color: Colors.white),
                ),
                Align(
                  alignment: Alignment.bottomRight,
                  child: Icon(Icons.settings_applications,size: 30,color: Colors.white),
                )
              ],
            ),
      ),
    );
  • Position
    return Center(
      child:  Container(
            height: 400,
            width: 300,
            color: Colors.red,
            child: Stack(
              // alignment: Alignment.center,
              children: <Widget>[
                Positioned(
                //  left: 10,
                  child: Icon(Icons.home,size: 40,color: Colors.white),
                ),
                Positioned(
                 bottom: 0,
                 left: 100,
                  child: Icon(Icons.search,size: 30,color: Colors.white),
                ),
                Positioned(
                  right: 0,
                  child: Icon(Icons.settings_applications,size: 30,color: Colors.white),
                )
              ],
            ),
      ),
    );
AspectRatio Card组件
  • AspectRatio 的作用是根据设置调整子元素 child 的宽高比。
    AspectRatio 首先会在布局限制条件允许的范围内尽可能的扩展,widget 的高度是由宽 度和比率决定的,类似于 BoxFit 中的 contain,按照固定比率去尽量占满区域。
    如果在满足所有限制条件过后无法找到一个可行的尺寸,AspectRatio 最终将会去优先 适应布局限制条件,而忽略所设置的比率。

  • 属性 说明

    • aspectRatio 宽高比,最终可能不会根据这个值去布局, 具体则要看综合因素,外层是否允许按照这 种比率进行布局,这只是一个参考值
    • child 子组件
    return Container(
      width: 400,
      color: Colors.blue,
      child: AspectRatio(
        aspectRatio: 2.0/1.0,
        child: Container(
          color: Colors.red,
        ),
      ),

    );
  • Card 是卡片组件块,内容可以由大多数类型的 Widget 构成,Card 具有圆角和阴影,这让它 看起来有立体感。

  • 属性 说明

    • margin 外边距
    • child 子组件
    • Shape Card 的阴影效果,默认的阴影效果为圆角的 长方形边。
return ListView(

      children: <Widget>[

          Card(
            margin: EdgeInsets.all(10),
            child: Column(
              children: <Widget>[
                
                ListTile(
                  title:Text("张三",style: TextStyle(fontSize: 28)) ,
                  subtitle: Text("高级工程师"),
                ),
                 ListTile(
                  title:Text("电话:xxxxx") ,                  
                ),
                ListTile(
                  title:Text("地址:xxxxxx") ,                  
                )
                
              ],
            ),
          ),Card(
            margin: EdgeInsets.all(10),
            child: Column(
              children: <Widget>[
                
                ListTile(
                  title:Text("李四",style: TextStyle(fontSize: 28)) ,
                  subtitle: Text("高级工程师"),
                ),
                 ListTile(
                  title:Text("电话:xxxxx") ,                  
                ),
                ListTile(
                  title:Text("地址:xxxxxx") ,                  
                )
                
              ],
            ),
          ),
          Card(
            margin: EdgeInsets.all(10),
            child: Column(
              children: <Widget>[
                
                ListTile(
                  title:Text("王五",style: TextStyle(fontSize: 28)) ,
                  subtitle: Text("高级工程师"),
                ),
                 ListTile(
                  title:Text("电话:xxxxx") ,                  
                ),
                ListTile(
                  title:Text("地址:xxxxxx") ,                  
                )
                
              ],
            ),
          )
      ],
    );
Wrap组件
  • Wrap 可以实现流布局,单行的 Wrap 跟 Row 表现几乎一致,单列的 Wrap 则跟 Row 表 现几乎一致。但 Row 与 Column 都是单行单列的,Wrap 则突破了这个限制,mainAxis 上空 间不足时,则向 crossAxis 上去扩展显示
  • 属性 说明
    • direction 主轴的方向,默认水平
    • alignment 主轴的对其方式
    • spacing 主轴方向上的间距
    • textDirection 文本方向
    • verticalDirection 定义了 children 摆放顺序,默认是 down,见 Flex 相关属性介绍。
    • runAlignment 纵轴的对齐方式。run 可以理解为新的行或者 列,如果是水平方向布局的话, run 可以理解 为新的一行
    • runSpacing run 的间距

RaisedButton组件
    return RaisedButton(
      child: Text('女装'),
      textColor: Theme.of(context).accentColor,      
      onPressed: (){        
      },
    );
FlutterStatefulWidget 有状态组件

页面上绑定数据、改变页面数据

  • 在 Flutter 中自定义组件其实就是一个类,这个类需要继承 StatelessWidget/StatefulWidget
    • StatelessWidget 是无状态组件,状态不可变的
    • widget StatefulWidget 是有状态组件,持有的状态可能在 widget 生命周期改变。
    • 通俗的讲:如果我 们想改变页面中的数据的话这个时候就需要用到 StatefulWidget
  • (1)你可能觉得有些奇怪,为什么StatefulComponentState要分开使用而不是集成在StatefulComponent内部,这是因为它们两个在程序的运行过程中有各自的生命周期,StatefulComponent仅用来表示控件的表现形式随时可能发生改变,而State的生命周期存在与两次build方法之间。
  • (2)当框架得知组件是StatefulComponent的时候回去调用createState()来获得其组件内容。
  • (3)State内部存储可变状态值,并通过实现build来构建组件。
  • (4)这里非常重要,当在State内部改变任何子控件需要的变量时,都需要使用setState,当调用了setState后,底层框架会把当前控件标记为一个‘脏’组件,接着会在必要的时刻重新调用组件的build方法来刷新其子控件,由此起到刷新的作用

FlutterBottomNavigationBar 组件
  • BottomNavigationBar 是底部导航条,可以让我们定义底部 Tab 切换
  • Tab切换需要在Scaffold组件中进行
  • BottomNavigationBar 是 Scaffold 组件的参数。
  • BottomNavigationBar 常见的属性
    • 属性名 说明
    • items List 底部导航条按钮集合
    • iconSize icon
    • currentIndex 默认选中第几个
    • onTap 选中变化回调函数
    • fixedColor 选中的颜色
    • type BottomNavigationBarType.fixed

​ BottomNavigationBarType.shifting

Flutter 中的普通路由、普通路由传值、
  • Flutter 中的路由通俗的讲就是页面跳转。
  • 在 Flutter 中通过 Navigator 组件管理路由导航。
  • 提供了管理堆栈的方法。如:Navigator.push跳转到一个页面 和 Navigator.pop返回上级页面
  • Flutter 中给我们提供了两种配置路由跳转的方式:1、基本路由 2、命名路由
普通路由跳转

实例:现在想从 HomePage 组件跳转到 SearchPage 组件

1、需要在 HomPage 中引入 SearchPage.dart

import'../SearchPage.dart';

2、在 HomePage 中通过下面方法跳转

RaisedButton(
child:Text("跳转到搜索页面"), 
    onPressed:(){ 
        Navigator.of(context).push( 
        MaterialPageRoute( 
            builder:(BuildContextcontext){ 
                returnSearchPage(); } ) );
        //或者
        //Navigator.push(context,
        //       MaterialPageRoute(builder: (context) => SearchPage(),
        //        )
        //      );
}, 
    color:Theme.of(context).accentColor, 
    textTheme:ButtonTextTheme.primary
)
普通路由传值

传值有上传下和下传上。

上级路由传值给下级路由只要在下级路由类中添加变量并且改写构造方法,如下例

分类页面要把标题’我是跳转传值’传给下级路由Formpage()的title参数变量

          //分类页面,点击按钮跳转Formepage路由
          onPressed: () async {
            var result = await Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (context){
                    return FormPage(title:'我是跳转传值'
                    );
                    },
                ),
            );
            print("路由返回:$result");
          },
//Formpage页面,
class FormPage extends StatelessWidget {
//设置title变量
  String title;
  FormPage({this.title="表单"});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: Text('返回'),
        onPressed: (){
          Navigator.pop(context,"我是返回结果");
        },
      ),

同理,下级路由返回值给上级路由需要使用异步 如上面的async和await关键字

把返回结果储存在result中打印即可

Flutter 中的命名路由、命名路由传值

命名路由可以对路由进行统一管理,适合比较大的项目

要想使用命名路由,我们必须先提供并注册一个路由表(routing table),这样应用程序才知道哪个名字与哪个路由组件相对应。其实注册路由表就是给路由起名字,路由表的定义如下:

Map<String, WidgetBuilder> routes;

它是一个Map,key为路由的名字,是个字符串;value是个builder回调函数,用于生成相应的路由widget。我们在通过路由名字打开新路由时,应用会根据路由名字在路由表中查找到对应的WidgetBuilder回调函数,然后调用该回调函数生成路由widget并返回。

命名路由跳转

1.首先引入组件并在根组件materialApp中定义配置路由

import 'pages/Tabs.dart';
import 'pages/Form.dart';
import 'pages/Search.dart';

//......
return MaterialApp(
      home:Tabs(),
      routes: {
        '/form':(context)=>FormPage(),
        '/search':(context)=>SearchPage(),
      }
    );

2.要通过路由名称来打开新路由,可以使用NavigatorpushNamed方法:

Future pushNamed(BuildContext context, String routeName,{Object arguments})

在该用的地方使用 Navigator.pushNamed(context, “路由器组件名”);

//在home页中
onPressed: () {
              //路由跳转dart
              Navigator.pushNamed(context, '/search');
            },
命名路由无状态组件传值

上传下和下传上和普通路由是一样的

import '../pages/Tabs.dart';
import '../pages/Form.dart';
import '../pages/Search.dart';
import '../pages/Product.dart';
import '../pages/ProductInfo.dart';

//配置路由表
final routes={
      '/':(context)=>Tabs(),
      '/form':(context)=>FormPage(),
      '/product':(context)=>ProductPage(),
      '/productinfo':(context,{arguments})=>ProductInfoPage(arguments:arguments),
      '/search':(context,{arguments})=>SearchPage(arguments:arguments),
};

//固定写法
var onGenerateRoute=(RouteSettings settings) {
      // 统一处理,获取路由名称,如要跳转到./form,获取的就是form,settings.name就是点击按钮的时候pushNamed函数第二个参数传过来的名称
      final String name = settings.name; 
    //routes[name] 见下文,把settings.name这个名称就放在this.routes里,this.routes是一个map对象,就把名称对应的内容((context)=>FormPage())传给左边函数
      final Function pageContentBuilder = routes[name];
      if (pageContentBuilder != null) {
        if (settings.arguments != null) {
          final Route route = MaterialPageRoute(
              builder: (context) =>
                  pageContentBuilder(context, arguments: settings.arguments));
          return route;
        }else{
            final Route route = MaterialPageRoute(
              builder: (context) =>
                  pageContentBuilder(context));
            return route;
        }
      }
};
  • routes[name]:

    打印结果如下:

routes[name]: { /: Closure: (dynamic) => Tabs, 
				/fordartm: Closure: (dynamic) => FormPage, 
				/product: Closure: (dynamic) => ProductPage, 
				/productinfo: Closure: (dynamic, {dynamic arguments}) => ProductInfoPage, 				  /search: Closure: (dynamic, {dynamic arguments}) => SearchPage}[name]
  • 要传参的话,在配置路由的时候,就要多加一个参数,如./search路由的argument参数,并且根据有无arguments采用不同的pageContentBuilder方法参数

  • 同时在要传参的路由页面如searchPage类里final一个arguments变量,因为是命名的所以在构造函数里要给该变量赋值。

class SearchPage extends StatelessWidget {
  
  final arguments;

  SearchPage({this.arguments});
   @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar:AppBar(
        title: Text("搜索页面"),
      ) ,
      body: Text("搜索页面内容区域${arguments != null ? arguments['id'] : '0'}"),
    );
  }
}
  • 然后再跳转主页面(home)中就可以配置参数,arguments相当于一个储存变量的字典
            onPressed: () {
              //路由跳转
              Navigator.pushNamed(context, '/search',arguments: {
                "id":123
              });

在main.dart中配置路由钩子

import 'package:flutter/material.dart';
import 'routes/Routes.dart';

void main() => runApp(MyApp());
class MyApp extends StatelessWidget {  
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // home:Tabs(),   
      initialRoute: '/',     //初始化的时候加载的路由
      onGenerateRoute: onGenerateRoute
    );
  }
}
推荐阅读:路由管理
Flutter路由生成钩子

假设我们要开发一个电商APP,当用户没有登录时可以看店铺、商品等信息,但交易记录、购物车、用户个人信息等页面需要登录后才能看。为了实现上述功能,我们需要在打开每一个路由页前判断用户登录状态!如果每次打开路由前我们都需要去判断一下将会非常麻烦,那有什么更好的办法吗?答案是有!

MaterialApp有一个onGenerateRoute属性,它在打开命名路由时可能会被调用,之所以说可能,是因为当调用Navigator.pushNamed(...)打开命名路由时,如果指定的路由名在路由表中已注册,则会调用路由表中的builder函数来生成路由组件;如果路由表中没有注册,才会调用onGenerateRoute来生成路由。onGenerateRoute回调签名如下:

Route<dynamic> Function(RouteSettings settings)

有了onGenerateRoute回调,要实现上面控制页面权限的功能就非常容易:我们放弃使用路由表,取而代之的是提供一个onGenerateRoute回调,然后在该回调中进行统一的权限控制

MaterialApp(
  ... //省略无关代码
  onGenerateRoute:(RouteSettings settings){
      return MaterialPageRoute(builder: (context){
           String routeName = settings.name;
       // 如果访问的路由页需要登录,但当前未登录,则直接返回登录页路由,
       // 引导用户登录;其它情况则正常打开路由。
     }
   );
  }
);
命名路由有状态组件传值

例子:Product.dart传参给ProductInfo界面一个pid,这两个都是有状态组件

通过arguments传递,是一个map类型

//Product.dart关键代码
          RaisedButton(
              child: Text("跳转到商品详情"),
              onPressed: () {
                Navigator.pushNamed(context, '/productinfo',arguments: {
                  "pid":456
                });
              }
          ), 
//Productinfo.dart代码
import 'package:flutter/material.dart';

class ProductInfoPage extends StatefulWidget {
    //设置变量来接收
  final Map arguments;
  ProductInfoPage({Key key,this.arguments}) : super(key: key);

  _ProductInfoPageState createState() => _ProductInfoPageState();
}

class _ProductInfoPageState extends State<ProductInfoPage> {
  //Map arguments;
  //_ProductInfoPageState({this.arguments});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('商品详情'),
      ),
      body: Container(
          //通过接收父组件的变量即可
        child: Text("pid=${widget.arguments["pid"]}"),
      ),
    );
  }
}
Flutter路由替换

​ 比如我们从用户中心页面跳转到了 registerFirst 页面,然后从 registerFirst 页面通过 pushReplacementNamed 跳转到了 registerSecond 页面。这个时候当我们点击 registerSecond 的返回按钮的时候它会直接返回到用户中心而不是之前的registerFirst页面,相当于用registerSecond页面替换了registerFirst。

//registerFirst
Navigator.pushReplacementNamed(context, '/registerSecond');
Flutter返回到根路由

1.用替换路由实现,一路一直使用替换路由,一直用下一个页面替换旧页面,最后用一个pop即可。

2.pushAndRemoveUntil方法

Navigator.of(context).pushAndRemoveUntil( 
    newMaterialPageRoute(
        builder:(context)=>newTabs(index:1)), //返回指定路由
    (route)=>route==null );//把前面的路由置为空

这时候就可以指定返回的是bottomNavigatorbar里的那一个,之前的Tabs页面也要做一些改进:

//原Tabs页面,通过 _currentIndex 变量取值来改变状态改变底部tab页面

import 'package:flutter/material.dart';
import 'tabs/Home.dart';
import 'tabs/Category.dart';
import 'tabs/Setting.dart';

class Tabs extends StatefulWidget {
  Tabs({Key key}) : super(key: key);

  _TabsState createState() => _TabsState();
}

class _TabsState extends State<Tabs> {

  int _currentIndex=0;
  List _pageList=[
    HomePage(),
    CategoryPage(),
    SettingPage(),
  ];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Flutter Demo"),
        ),
        body: this._pageList[this._currentIndex],
        bottomNavigationBar: BottomNavigationBar(
          currentIndex: this._currentIndex,   //配置对应的索引值选中
          onTap: (int index){
              setState(() {  //改变状态
                  this._currentIndex=index;
              });
          },
          iconSize:36.0,      //icon的大小
          fixedColor:Colors.red,  //选中的颜色  
          type:BottomNavigationBarType.fixed,   //配置底部tabs可以有多个按钮
          items: [
            BottomNavigationBarItem(
              icon: Icon(Icons.home),
              title: Text("首页")
            ),
             BottomNavigationBarItem(
              icon: Icon(Icons.category),
              title: Text("分类")
            ),
             BottomNavigationBarItem(
              icon: Icon(Icons.settings),
              title: Text("设置")
            )
          ],
        ),
      );
  }
}
//改进后,取消 _currentIndex变量,直接使用返回的index变量

import 'package:flutter/material.dart';
import 'tabs/Home.dart';
import 'tabs/Category.dart';
import 'tabs/Setting.dart';

class Tabs extends StatefulWidget {
    //定义index变量并且构造函数,可选变量,默认是0
  var index;
  Tabs({Key key,this.index=0}) : super(key: key);

  _TabsState createState() => _TabsState();
}

class _TabsState extends State<Tabs> {


  List _pageList=[
    HomePage(),
    CategoryPage(),
    SettingPage(),
  ];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Flutter Demo"),
        ),
        body: this._pageList[widget.index],
        bottomNavigationBar: BottomNavigationBar(
            //直接用父组件的index变量来选中
          currentIndex: widget.index,   //配置对应的索引值选中
          onTap: (int index){
              setState(() {  //改变状态
                  //直接赋给父组件的index
                  widget.index = index;
              });
          },
          iconSize:36.0,      //icon的大小
          fixedColor:Colors.red,  //选中的颜色  
          type:BottomNavigationBarType.fixed,   //配置底部tabs可以有多个按钮
          items: [
            BottomNavigationBarItem(
              icon: Icon(Icons.home),
              title: Text("首页")
            ),
             BottomNavigationBarItem(
              icon: Icon(Icons.category),
              title: Text("分类")
            ),
            
             BottomNavigationBarItem(
              icon: Icon(Icons.settings),
              title: Text("设置")
            )
          ],
        ),
      );
  }
}

在RegisterThird页面返回部分:

                Navigator.of(context).pushAndRemoveUntil(
                    //要引入Tab.dart,并且传过去index变量
                  new MaterialPageRoute(builder: (context) => new Tabs(index:2)),         
                  (route) => route == null
                );
Flutter AppBar
Flutter AppBar 自定义顶部按钮图标 颜色

属性 描述

leading 在标题前面显示的一个控件,在首页通常显示应用 的 logo;在其他界面通常显示为返回按钮

title 标题,通常显示为当前界面的标题文字,可以放组 件

actions 通常使用 IconButton 来表示,可以放按钮组

bottom 通常放 tabBar,标题下面显示一个 Tab 导航栏

backgroundColor 导航背景颜色

iconTheme 图标样式

textTheme 文字样式

centerTitle 标题是否居中显示

      appBar: AppBar(
        title:Text("AppBarDemoPage"), 
        // backgroundColor: Colors.red, 
        centerTitle:true,
        leading: IconButton(
          icon: Icon(Icons.menu),
          onPressed: (){
            print('menu');
          },
        ), 
        actions: <Widget>[
            //按钮图标,可以触发事件
          IconButton(
            icon: Icon(Icons.search),
            onPressed: (){
              print('search');
            },
          ),
          IconButton(
            icon: Icon(Icons.settings),
            onPressed: (){
              print('settings');
            },
          )
        ],
      ),
AppBar中自定义TabBar 实现顶部Tab切换
  1. 首先为Scaffold组件添加父组件DefaultTabController,设置length属性,有几个切换页面就设置为几。
return DefaultTabController(
      length:2 ,
      child: Scaffold(
          appBar: AppBar(
            title:Text("AppBarDemoPage"),  
            centerTitle:true,
  1. 在AppBar组件里设置bottom属性,bottom属性里设置TabBar类,这个类有个Tabs参数,tabs就是存放Tab选项的数组了。
          appBar: AppBar(
            title:Text("AppBarDemoPage"),  
            centerTitle:true,
            bottom: TabBar(
              tabs: <Widget>[
                Tab(text: "热门"),
                Tab(text: "推荐")
              ],
            ),
          ),
  1. 在body里写组件TabBarView,chilren组件里写内容即可
body: TabBarView(
            children: <Widget>[
              ListView(
                children: <Widget>[
                  ListTile(
                    title:Text("第一个tab")
                  ),
                  ListTile(
                    title:Text("第一个tab")
                  ),
                  ListTile(
                    title:Text("第一个tab")
                  )
                ],
              ),
                //......
                          ],
          ),
  • 之前的DefaultTabController能添加为Scaffold父组件是因为 在App首页 点击按钮后跳转到AppBarDemo页面,那么可以在AppBarDemo页面直接return DefaultTabController组件,但是如果想要在App的初始页面或者其他底部Tab页面里实现呢?

    那就直接在要添加顶部Tab的底部Tab页里renturn DefaultTabController组件,比如category页面,写法和上面一致。

    但是我们要注意,底部导航页面是统一挂载在Tabs.dart的Scaffold组件的body中的(忘了可以看看tabs.dart的底部导航组件切换),这相当于Scaffold里嵌套DefaultTabController又套Scaffold组件,哪有不就会有两个AppBar导航了吗?所以就需要再次改进:

    把之前的TabBar类内容 放到AppBar类中:

    //Category类
    return DefaultTabController(
          length: 4,
          child: Scaffold(
            appBar: AppBar(
    
              backgroundColor: Colors.black26,
              title: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Expanded(
                      child:TabBar(
                        indicatorColor:Colors.blue,
                        labelColor:Colors.blue,
                        unselectedLabelColor: Colors.white,
                        indicatorSize:TabBarIndicatorSize.label ,
                        
                        tabs: <Widget>[
                          Tab(text: "热销"),
                          Tab(text: "推荐"),
                          Tab(text: "推荐"),
                          Tab(text: "推荐")
                        ],
                   ) ,
                  )
                ],
              ),
              
            ),
            body:TabBarView(
            //......
    
    TabBar 常见属性

    属性 描述

    tabs 显示的标签内容,一般使用 Tab 对象,也可以是其他 的 Widget

    controller TabController 对象

    isScrollable 导航栏是否可滚动(有多个的时候)

    indicatorColor 指示器颜色

    indicatorWeight 指示器高度

    indicatorPadding 底部指示器的 Padding

    indicator 指示器 decoration,例如边框等 indicatorSize 指示器大 小计算方式,TabBarIndicatorSize.label 跟文 字等宽,TabBarIndicatorSize.tab 跟每个 tab 等宽

    labelColor 选中 label 颜色

    labelStyle 选中 label 的 Style

    labelPadding 每个 label 的 padding 值

    unselectedLabelColor 未选中 label 颜色

    unselectedLabelStyle 未选中 label 的 Style

    Flutter AppBar 中自定义 TabBar 实 现 Tabs 的另一种方法:TabController

    为什么要用这种方式:如果有数据请求 上拉加载更多 左右滑动等功能 上一种方法就比较麻烦

    TabController实现的话,组件必须是动态组件,并且要实现一个类SingleTickerProviderStateMixin

    直接新建dart文件 配置相关路由;

    //TabController类
    import 'package:flutter/material.dart';
    
    class TabBarControllerPage extends StatefulWidget {
      TabBarControllerPage({Key key}) : super(key: key);
    
      _TabBarControllerPageState createState() => _TabBarControllerPageState();
    }
    
    class _TabBarControllerPageState extends State<TabBarControllerPage> with SingleTickerProviderStateMixin {
    
      TabController _tabController;
    
      @override
      void dispose() {   //生命周期函数
        // TODO: implement dispose
        super.dispose();
        _tabController.dispose();
      }
    
      @override//1.重写实例化tabController类初始化函数,这个方法会在加载的时候触发
      void initState() {   //生命周期函数
        // TODO: implement initState
        super.initState();
          //2.实例化TabController,
          //第一个参数固定,第二个是指定Tabs的数量
        _tabController=new TabController(
          vsync: this,
          length: 2
        );
         _tabController.addListener((){
    
           print(_tabController.index);     
         });
      }  
    
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("TabBarControllerPage"),
              //3.定义TabBar,基本和前面的一样,只不过
              //要加上一个controller属性,内容就是前面定义实例化的变量_tabController
            bottom: TabBar(
              controller: this._tabController,  //注意
              tabs: <Widget>[
                Tab(text:"热销"),
                Tab(text:"推荐"),
              ],
            ),
          ),
            //4.在TabBarView里也要添加controller属性
          body: TabBarView(
            controller: this._tabController,  //注意
            children: <Widget>[
              Center(child: Text("热销")),
              Center(child: Text("推荐"))
              
            ],
          ),
        );
      }
    }
    

    那么,这样写为什么就比上面那种(Defaultcontroller)好呢?

    因为我们可以在_tabController里调用一些方法,如上面注释里的addListener添加对事件的监听,一旦点击(状态改变)就打印_tabController的序号,表示是第几个tab。

    FlutterDrawer 侧边栏 侧边栏头部Drawerheader
  • 在 Scaffold 组件里面传入 drawer 参数可以定义左侧边栏,传入 endDrawer 可以定义右侧边 栏。侧边栏默认是隐藏的,我们可以通过手指滑动显示侧边栏,也可以通过点击按钮显示侧 边栏。

  • 这个组件根据放的组件不同而定,放在Tab切换组件里可以实现无论在哪个Tab里都可以出现侧边栏

     //在tabs.dart的Scaffold组件 BottomNavigationBar组件下面写  
drawer: 
//1.左侧边栏
Drawer(
          child: Column(
            children: <Widget>[
              Row(
                children: <Widget>[
                  Expanded(
                      //2.这个是侧边栏头部,有许多属性可以用
                    child: DrawerHeader(
                      child: Text("你好flutter"),
                      decoration:BoxDecoration(
                        color: Colors.yellow,
                        image: DecorationImage(
                          image: 			 NetworkImage("https://www.itying.com/images/flutter/2.png"),
                          fit:BoxFit.cover,
                        )
                      )
                    )
                  )
                ],
              ),
              ListTile(
                leading: CircleAvatar(
                  child: Icon(Icons.home)
                ),
                title: Text("我的空间"),
              ),
                //3.这个Divider函数是类似于组件之间分割线的东西
                Divider(),
               ListTile(
                leading: CircleAvatar(
                  child: Icon(Icons.people)
                ),
                title: Text("用户中心"),
              ),
              Divider(),
              ListTile(
                leading: CircleAvatar(
                  child: Icon(Icons.settings)
                ),
                title: Text("设置中心"),
              ),
                Divider(),
            ],
          ),
        ),
//4.右侧边栏
        endDrawer: Drawer(
          child: Text('右侧侧边栏'),
        ),
  • DrawerHeader属性

    属性 描述

    decoration 设置顶部背景颜色

    child 配置子元素

    padding 内边距

    margin 外边距

Flutter UserAccountsDrawerHeader

该组件用来替换Drawerheader,提供了一些样式来实现Drawer头部组件

属性 描述

decoration 设置顶部背景颜色

accountName 账户名称

accountEmail 账户邮箱

currentAccountPicture 用户头像

otherAccountsPictures 用来设置当前账户其他账户头像

margin

drawer: Drawer(
          child: Column(
            children: <Widget>[

              Row(
                children: <Widget>[
                  Expanded(
                    child: UserAccountsDrawerHeader(
                      accountName:Text("大地老师"),
                      accountEmail: Text("dadi@itying.com"),
                      currentAccountPicture: CircleAvatar(
                        backgroundImage: NetworkImage("https://www.itying.com/images/flutter/3.png"),                        
                      ),
                      decoration:BoxDecoration(                        
                        image: DecorationImage(
                          image: NetworkImage("https://www.itying.com/images/flutter/2.png"),
                          fit:BoxFit.cover,
                        )
                        
                      ),
                     otherAccountsPictures: <Widget>[
                       Image.network("https://www.itying.com/images/flutter/4.png"),
                       Image.network("https://www.itying.com/images/flutter/5.png"),
                     ],
                    )
                  )
                ],
              ),
Flutter 侧边栏路由跳转

比如划出来侧边栏,点击里面的“用户中心”,侧边栏自动收起并跳转到用户中心路由页面?

解决:在对应侧边栏具体组件里用onTap方法即可

              ListTile(
                leading: CircleAvatar(
                  child: Icon(Icons.people)
                ),
                title: Text("用户中心"),
                onTap: (){
                  Navigator.of(context).pop();  //隐藏侧边栏
                  Navigator.pushNamed(context, '/user');
                },
              ),
Flutter 中的常见的按钮组件

Flutter 里有很多的 Button 组件很多,常见的按钮组件有:RaisedButton、FlatButton、 IconButton、OutlineButton、ButtonBar、FloatingActionButton 等。

RaisedButton :凸起的按钮,其实就是 Material Design 风格的 Button

FlatButton :扁平化的按钮

OutlineButton:线框按钮

IconButton :图标按钮

ButtonBar:按钮组

FloatingActionButton:浮动按钮

Flutter 按钮组件部分属性

属性名称 值类型 属性值

onPressed VoidCallback ,一般接收一个 方法 必填参数,按下按钮时触发的回调,接收一个 方法,传 null 表示按钮禁用,会显示禁用相关 样式

child Widget 文本控件
textColor Color 文本颜色

color Color 按钮的颜色

disabledColor Color 按钮禁用时的颜色

disabledTextColor Color 按钮禁用时的文本颜色

splashColor Color 点击按钮时水波纹的颜色

highlightColor Color 点击(长按)按钮后按钮的颜色

elevation double 阴影的范围,值越大阴影范围越大

padding 内边距

shape

设置按钮宽高

可以在按钮外边包裹一个container

自适应按钮

在外层加一个Expanded组件即可

给按钮加icon
RaisedButton.icon(
                    icon: Icon(Icons.search),
                    label: Text('图标按钮'),
                    color: Colors.blue,
                    textColor: Colors.white,
                    // onPressed: null,
                    onPressed: () {
                      print("图标按钮");
                    })
圆角按钮
RaisedButton(
                    child: Text('圆角按钮'),
                    color: Colors.blue,
                    textColor: Colors.white,
                    elevation: 20,
                    shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(10)),
                    onPressed: () {
                      print("圆角按钮");
                    }),
圆形按钮
RaisedButton(
                      child: Text('圆形按钮'),
                      color: Colors.blue,
                      textColor: Colors.white,
                      elevation: 20,
                      splashColor: Colors.red,//水波纹
                      shape:
                          CircleBorder(side: BorderSide(color: Colors.white)),
                      onPressed: () {
                        print("圆形按钮");
                      }),
ButtonBar按钮组

结合下面自定义button组件来看

Flutter自定义按钮组件
class MyButton extends StatelessWidget {
  final text;
  final pressed;
  final width;
  final height;
  const MyButton({this.text='',this.pressed=null,this.width=80,this.height=30}) ;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: this.height,
      width: this.width,
      child: RaisedButton(
        child: Text(this.text),
        onPressed:this.pressed ,
      ),
    );
  }
}

ButtonBar(
                  children: <Widget>[

                    RaisedButton(
                      child: Text('登录'),
                      color: Colors.blue,
                      textColor: Colors.white,
                      elevation: 20,
                      onPressed: () {
                        print("宽度高度");
                      },
                    ),
                    RaisedButton(
                      child: Text('注册'),
                      color: Colors.blue,
                      textColor: Colors.white,
                      elevation: 20,
                      onPressed: () {
                        print("宽度高度");
                      },
                    ),
                    MyButton(text: "自定义按钮",height: 60.0,width: 100.0,pressed: (){
                      print('自定义按钮');
                    })
                    
                  ],
                )
Flutter FloatingActionButton

FloatingActionButton 简称 FAB ,可以实现浮动按钮,也可以实现类似闲鱼 app 的地步凸 起导航。

属性名称 属性值

child 子视图,一般为 Icon,不推荐使用文字

tooltip FAB 被长按时显示,也是无障碍功能

backgroundColor 背景颜色

elevation 未点击的时候的阴影

hignlightElevation 点击时阴影值,默认 12.0

onPressed 点击事件回调

shape 可以定义 FAB 的形状等

mini 是否是 mini 类型默认 false

FloatingActionButton 实现闲鱼 app 底部凸起按钮

import 'package:flutter/material.dart';
import 'tabs/Home.dart';
import 'tabs/Category.dart';
import 'tabs/Setting.dart';

class Tabs extends StatefulWidget {
  final index;
  Tabs({Key key,this.index=0}) : super(key: key);

  _TabsState createState() => _TabsState(this.index);
}

class _TabsState extends State<Tabs> {

  int _currentIndex;
  _TabsState(index){
    this._currentIndex=index;
  }

  List _pageList=[
    HomePage(),
    CategoryPage(),
    SettingPage(),
  ];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Flutter App"),
        ),
        floatingActionButton: Container(
          height: 80,
          width: 80,
          padding: EdgeInsets.all(8),
          margin: EdgeInsets.only(top: 10),
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(40),
            color: Colors.white,
          ),
          
          child: FloatingActionButton(
            child: Icon(Icons.add),
            onPressed: (){
               setState(() {  //改变状态
                    this._currentIndex=1;
                });

            },
            backgroundColor: this._currentIndex==1?Colors.red:Colors.yellow,
          ),
        ),
        floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
        body: this._pageList[this._currentIndex],
        bottomNavigationBar: BottomNavigationBar(
          currentIndex: this._currentIndex,   //配置对应的索引值选中
          onTap: (int index){
              setState(() {  //改变状态
                  this._currentIndex=index;
              });
          },
          iconSize:36.0,      //icon的大小
          fixedColor:Colors.red,  //选中的颜色  
          type:BottomNavigationBarType.fixed,   //配置底部tabs可以有多个按钮
          items: [
            BottomNavigationBarItem(
              icon: Icon(Icons.home),
              title: Text("首页")
            ),
             BottomNavigationBarItem(
              icon: Icon(Icons.category),
              title: Text("分类")
            ),
            
             BottomNavigationBarItem(
              icon: Icon(Icons.settings),
              title: Text("设置")
            )
          ],
        ),

        drawer: Drawer(
          child: Column(
            children: <Widget>[

              Row(
                children: <Widget>[
                  Expanded(
                    child: UserAccountsDrawerHeader(
                      accountName:Text("大地老师"),
                      accountEmail: Text("dadi@itying.com"),
                      currentAccountPicture: CircleAvatar(
                        backgroundImage: NetworkImage("https://www.itying.com/images/flutter/3.png"),                        
                      ),
                      decoration:BoxDecoration(                        
                        image: DecorationImage(
                          image: NetworkImage("https://www.itying.com/images/flutter/2.png"),
                          fit:BoxFit.cover,
                        )
                        
                      ),
                     otherAccountsPictures: <Widget>[
                       Image.network("https://www.itying.com/images/flutter/4.png"),
                       Image.network("https://www.itying.com/images/flutter/5.png"),
                     ],
                    )
                  )
                ],
              ),
              ListTile(
                leading: CircleAvatar(
                  child: Icon(Icons.home)
                ),
                title: Text("我的空间"),
              
              ),
                Divider(),
               ListTile(
                leading: CircleAvatar(
                  child: Icon(Icons.people)
                ),
                title: Text("用户中心"),
                onTap: (){
                  Navigator.of(context).pop();  //隐藏侧边栏
                  Navigator.pushNamed(context, '/user');
                },
              ),
              Divider(),
              ListTile(
                leading: CircleAvatar(
                  child: Icon(Icons.settings)
                ),
                title: Text("设置中心"),
              ),
                Divider(),
            ],
          ),


        ),
        endDrawer: Drawer(
          child: Text('右侧侧边栏'),
        ),
      );
  }
}
Flutter 中的表单

​ Flutter 中常见的表单有 TextField 单行文本框, TextField 多行文本框、 CheckBox、 Radio、 Switch CheckboxListTile、RadioListTile、SwitchListTile、Slide。

TextField 文本框组件
TextField表单常见属性

属性 描述

maxLines 设置此参数可以把文本框改为多行文本框

onChanged 文本框改变的时候触发的事件

decoration hintText 类似 html 中的 placeholder

​ border 配置文本框边框 OutlineInputBorder 配合使用

​ labelText lable 的名称 labelStyle 配置 lable 的样式
​ obscureText 把文本框框改为密码框

controller controller 结合 TextEditingController()可以配置表单默认显示的内容

import 'package:flutter/material.dart';

class TextFieldDemoPage extends StatefulWidget {
  TextFieldDemoPage({Key key}) : super(key: key);

  _TextFieldDemoPageState createState() => _TextFieldDemoPageState();
}

class _TextFieldDemoPageState extends State<TextFieldDemoPage> {

  var _username=new TextEditingController();   //初始化的时候给表单赋值
  var _password;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _username.text='初始值';
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('表单演示页面'),
      ),
      body: Padding(
        padding: EdgeInsets.all(20),
        // child:TextDemo() ,    
        child:Column(
          children: <Widget>[
            TextField(
              decoration: InputDecoration(
                hintText: "请输入用户名"
              ),
              controller: _username,
              onChanged: (value){
                setState(() {
                    _username.text=value; 
                });
              },
              
            ),
            SizedBox(height: 10),
            TextField(
              obscureText: true,
              decoration: InputDecoration(
                hintText: "密码"
              ),             
              onChanged: (value){
                setState(() {
                    this._password=value; 
                });
              },
              
            ),
            SizedBox(height: 40),
            Container(
              width: double.infinity,
              height: 40,
              child: RaisedButton(
                child: Text("登录"),
                onPressed: (){
                  print(this._username.text);
                  print(this._password);
                },
                color: Colors.blue,
                textColor: Colors.white,
              ),
            )
          ],
        ) ,     
      )
    );
  }
}


class TextDemo extends StatelessWidget {
  const TextDemo({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
          children: <Widget>[
            TextField(),
            SizedBox(height: 20),
            TextField(
              decoration:InputDecoration(
                hintText:"请输入搜索的内容",
                border: OutlineInputBorder()
              ) ,
            ),
             SizedBox(height: 20),
             TextField(
              maxLines: 4,
              decoration:InputDecoration(
                hintText:"多行文本框",
                border: OutlineInputBorder()
              ) ,
            ),
             SizedBox(height: 20),
            TextField(
              obscureText: true,
              decoration:InputDecoration(
                hintText:"密码框",
                border: OutlineInputBorder()
              ) ,
            ),
             SizedBox(height: 20),

            TextField(             
              decoration:InputDecoration(               
                border: OutlineInputBorder(),
                labelText: "用户名"
              ) 
              
            ),
             SizedBox(height: 20),
            TextField(    
              obscureText: true,         
              decoration:InputDecoration(               
                border: OutlineInputBorder(),
                labelText: "密码",
                // labelStyle: TextStyle()
              ) 
              
            ),
             SizedBox(height: 20),
             TextField(                   
              decoration:InputDecoration(               
               icon: Icon(Icons.people),
               hintText: "请输入用户名"
              )               
            ),
          ],
      ),
    );
    
  }
}
Checkbox&CheckboxListTile 多选框组件
Checkbox 常见属性

属性 描述

value true 或者 false

onChanged 改变的时候触发的事件

activeColor 选中的颜色、背景颜色

checkColor 选中的颜色、Checkbox 里面对号的颜色

CheckboxListTile 常见属性

属性 描述

value true 或者 false

onChanged 改变的时候触发的事件

activeColor 选中的颜色、背景颜色

title 标题

subtitle 二级标题

secondary 配置图标或者图片

selected 选中的时候文字颜色是否跟着改变

import 'package:flutter/material.dart';

class CheckBoxDemo extends StatefulWidget {
  CheckBoxDemo({Key key}) : super(key: key);

  _CheckBoxDemoState createState() => _CheckBoxDemoState();
}

class _CheckBoxDemoState extends State<CheckBoxDemo> {

  var flag=true;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("checkbox"),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[

            Row(children: <Widget>[
              Checkbox(
                value: this.flag,
                onChanged: (v){
                  setState(() {
                     this.flag=v;
                  });
                },
                activeColor: Colors.red,
              )
            ]),
            Row(
              children: <Widget>[
                Text(this.flag?"选中":"未选中")
              ],
            ),
            SizedBox(height: 40),

            // CheckboxListTile(
            //    value: this.flag,
            //     onChanged: (v){
            //       setState(() {
            //          this.flag=v;
            //       });
            //     },
            //     title: Text("标题"),
            //     subtitle:Text("这是二级标题") ,
            // ),
            Divider(),
            CheckboxListTile(
               value: this.flag,
                onChanged: (v){
                  setState(() {
                     this.flag=v;
                  });
                },
                title: Text("标题"),
                subtitle:Text("这是二级标题") ,
                secondary:Icon(Icons.help)
            )

          ],
        ),
      
    );
  }
}

Radio&RadioListTile 单选按钮组件
Radio 常用属性

属性 描述

value 单选的值

onChanged 改变时触发

activeColor 选中的颜色、背景颜色

groupValue 选择组的值

RadioListTile 常用属性

属性 描述

value true 或者 false

onChanged 改变的时候触发的事件

activeColor 选中的颜色、背景颜色

title 标题

subtitle 二级标题

secondary 配置图标或者图片

groupValue 选择组的值

import 'package:flutter/material.dart';

class RadioDemo extends StatefulWidget {
  RadioDemo({Key key}) : super(key: key);
  _RadioDemoState createState() => _RadioDemoState();
}

class _RadioDemoState extends State<RadioDemo> {

  int sex=1;

  bool flag=true;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Radio"),
      ),
      body: Padding(
        padding: EdgeInsets.all(20),
        child: Column(
          children: <Widget>[

            // Row(
            //   children: <Widget>[
            //     Text("男:"),
            //     Radio(
            //       value:1,
            //       onChanged: (v){
            //         setState(() {
            //            this.sex=v;
            //         });
            //       },
            //       groupValue:this.sex ,
            //     ),
            //     SizedBox(width: 20),
            //     Text("女:"),
            //     Radio(
            //       value:2,
            //       onChanged: (v){
            //         setState(() {
            //            this.sex=v;
            //         });
            //       },
            //       groupValue:this.sex ,
            //     )
            //   ],
            // ),
            // Row(
            //   children: <Widget>[
            //     Text("${this.sex}"),
            //     Text(this.sex==1?"男":"女")
            //   ],
            // ),
            SizedBox(height: 40),
            RadioListTile(
                value:1,
                onChanged: (v){
                  setState(() {
                      this.sex=v;
                  });
                },
                groupValue:this.sex ,
                 title: Text("标题"),
                subtitle:Text("这是二级标题") ,
                secondary:Icon(Icons.help),
                selected: this.sex==1,
            ),
            RadioListTile(
                value:2,
                onChanged: (v){
                  setState(() {
                      this.sex=v;
                  });
                },
                groupValue:this.sex ,
                 title: Text("标题"),
                subtitle:Text("这是二级标题") ,
                secondary:Image.network('https://www.itying.com/images/flutter/1.png'),
                selected: this.sex==2,
            ),
            SizedBox(height: 40),

            Switch(
              value: this.flag,
              onChanged: (v){
                setState(() {
                  print(v);
                  this.flag=v;
                });
              },
            )
          ],
        ),
      ),
    );
  }
}

Demo:学员登记表单
import 'package:flutter/material.dart';

class FormDemoPage extends StatefulWidget {
  FormDemoPage({Key key}) : super(key: key);

  _FormDemoPageState createState() => _FormDemoPageState();
}

class _FormDemoPageState extends State<FormDemoPage> {

  String username;
  int sex=1;
  String info='';

  List hobby=[
    {
      "checked":true,
      "title":"吃饭"
    },
     {
      "checked":false,
      "title":"睡觉"
    },
     {
      "checked":true,
      "title":"写代码"
    }
  ];

  List<Widget> _getHobby(){

    List<Widget> tempList=[];

    for(var i=0;i<this.hobby.length;i++){

        tempList.add(
          Row(
            children: <Widget>[
              Text(this.hobby[i]["title"]+":"),
              Checkbox(
                value: this.hobby[i]["checked"],
                onChanged: (value){
                  setState(() {
                    this.hobby[i]["checked"]=value; 
                  });
                }
              )
            ],
          )
        );

    }
    return tempList;

  }

  void _sexChanged(value){
    setState(() {
      this.sex=value; 
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("学员信息登记系统"),
      ),
      body: Padding(
        padding: EdgeInsets.all(20),
        child: Column(
          children: <Widget>[

             TextField(
               decoration: InputDecoration(
                 hintText: "输入用户信息"
               ),
               onChanged: (value){
                 setState(() {
                    this.username=value;
                 });
               },
             ),
              SizedBox(height:10),
             Row(
               children: <Widget>[
                 Text("男"),
                 Radio(
                   value: 1,
                   onChanged: this._sexChanged,
                   groupValue: this.sex
                 ),
                 SizedBox(width: 20),
                 Text("女"),
                 Radio(
                   value: 2,
                   onChanged:this._sexChanged,
                   groupValue: this.sex
                 )
               ],
             ),

            //爱好
            SizedBox(height:40),
            Column(
              children: this._getHobby(),
            ),

            TextField(
              maxLines: 4,
               decoration: InputDecoration(
                 hintText: "描述信息",
                 border: OutlineInputBorder()
               ),
               onChanged: (value){
                setState(() {
                  this.info=value;
                });
               },
             ),


           SizedBox(height:40),
             Container(
              width: double.infinity,
              height: 40,
              child: RaisedButton(
                child: Text("提交信息"),
                onPressed: (){
                  print(this.sex);
                  print(this.username);
                  print(this.hobby);
                },
                color: Colors.blue,
                textColor: Colors.white,
              ),
            )
          ],
        ),
      ),
    );
  }
}
Flutter文件操作
参考文档

文件操作

文件操作类官网API

App目录

Android和iOS的应用存储目录不同,PathProvider 插件提供了一种平台透明的方式来访问设备文件系统上的常用位置。该类当前支持访问两个文件系统位置:

  • 临时目录: 可以使用 getTemporaryDirectory() 来获取临时目录; 系统可随时清除的临时目录(缓存)。在iOS上,这对应于NSTemporaryDirectory() 返回的值。在Android上,这是getCacheDir()返回的值。
  • 文档目录: 可以使用getApplicationDocumentsDirectory()来获取应用程序的文档目录,该目录用于存储只有自己可以访问的文件。只有当应用程序被卸载时,系统才会清除该目录。在iOS上,这对应于NSDocumentDirectory。在Android上,这是AppData目录。
  • 外部存储目录:可以使用getExternalStorageDirectory()来获取外部存储目录,如SD卡;由于iOS不支持外部目录,所以在iOS下调用该方法会抛出UnsupportedError异常,而在Android下结果是android SDK中getExternalStorageDirectory的返回值。

一旦你的Flutter应用程序有一个文件位置的引用,你可以使用dart:ioAPI来执行对文件系统的读/写操作。

Flutter HTTP请求
参考文档

Flutter中文网

Flutter实战

CSDN博客

dio

处理异步

HTTP API 在返回值中使用了Dart Futures。 建议使用async/await语法来调用API。

网络调用通常遵循如下步骤:

  1. 创建 client.

    HttpClient httpClient = new HttpClient();
    
  2. 构造 Uri.

    这一步可以使用任意Http Method,如httpClient.post(...)httpClient.delete(...)等。如果包含

    Query参数,可以在构建uri时添加,如:

    Uri uri=Uri(scheme: "https", host: "flutterchina.club", queryParameters: {
        "xx":"xx",
        "yy":"dd"
      });
    
  3. 发起请求, 等待请求,同时也可以配置请求headers、 body。

    通过HttpClientRequest可以设置请求header,如:

    request.headers.add("user-agent", "test");
    

    如果是post或put等可以携带请求体方法,可以通过HttpClientRequest对象发送request body,如:

    String payload="...";
    request.add(utf8.encode(payload)); 
    //request.addStream(_inputStream); //可以直接添加输入流
    
  4. 关闭请求, 等待响应.

    HttpClientResponse response = await request.close();
    

    这一步完成后,请求信息就已经发送给服务器了,返回一个HttpClientResponse对象,它包含响应头(header)和响应流(响应体的Stream),接下来就可以通过读取响应流来获取响应内容。

  5. 解码响应的内容.

    String responseBody = await response.transform(utf8.decoder).join();
    

    我们通过读取响应流来获取服务器返回的数据,在读取时我们可以设置编码格式,这里是utf8。

  6. 请求结束,关闭HttpClient

    httpClient.close();
    

    关闭client后,通过该client发起的所有请求都会中止。

其中的几个步骤使用基于Future的API。

get() async {
    //1.创建 client.
  var httpClient = new HttpClient();
    //2.创建uri
  var uri = new Uri.http(
      'example.com', '/path1/path2', {'param1': '42', 'param2': 'foo'});
    //3.打开Http连接,设置请求头
  var request = await httpClient.getUrl(uri);
    //4.关闭请求,等待响应
  var response = await request.close();
    //5.解码相应内容
  var responseBody = await response.transform(UTF8.decoder).join();
}
JSON解码与编码

使用dart:convert库可以简单解码和编码JSON

​ 解码简单的JSON字符串并将响应解析为Map:

Map data = JSON.decode(responseBody);
// Assume the response body is something like: ['foo', { 'bar': 499 }]
int barValue = data[1]['bar']; // barValue is set to 499

​ 要对简单的JSON进行编码,请将简单值(字符串,布尔值或数字字面量)或包含简单值的Map,list等传给encode方法:

String encodedString = JSON.encode([1, 2, { 'a': null }]);
实例1
import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';

void main() {
  runApp(new MyApp());
}

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

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var _ipAddress = 'Unknown';

  _getIPAddress() async {
    var url = 'https://httpbin.org/ip';
    var httpClient = new HttpClient();

    String result;
    try {
      var request = await httpClient.getUrl(Uri.parse(url));
      var response = await request.close();
      if (response.statusCode == HttpStatus.ok) {
        var json = await response.transform(utf8.decoder).join();
        var data = jsonDecode(json);
        print(data.toString());
        result = data['origin'];
      } else {
        result =
            'Error getting IP address:\nHttp status ${response.statusCode}';
      }
    } catch (exception) {
      result = 'Failed getting IP address';
    }

    // If the widget was removed from the tree while the message was in flight,
    // we want to discard the reply rather than calling setState to update our
    // non-existent appearance.
    if (!mounted) return;

    setState(() {
      _ipAddress = result;
    });
  }

  @override
  Widget build(BuildContext context) {
    var spacer = new SizedBox(height: 32.0);

    return new Scaffold(
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text('Your current IP address is:'),
            new Text('$_ipAddress.'),
            spacer,
            new RaisedButton(
              onPressed: _getIPAddress,
              child: new Text('Get IP address'),
            ),
          ],
        ),
      ),
    );
  }
}
实例2
import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';

class HttpTestRoute extends StatefulWidget {
  @override
  _HttpTestRouteState createState() => new _HttpTestRouteState();
}

class _HttpTestRouteState extends State<HttpTestRoute> {
  bool _loading = false;
  String _text = "";

  @override
  Widget build(BuildContext context) {
    return ConstrainedBox(
      constraints: BoxConstraints.expand(),
      child: SingleChildScrollView(
        child: Column(
          children: <Widget>[
            RaisedButton(
                child: Text("获取百度首页"),
                onPressed: _loading ? null : () async {
                  setState(() {
                    _loading = true;
                    _text = "正在请求...";
                  });
                  try {
                    //创建一个HttpClient
                    HttpClient httpClient = new HttpClient();
                    //打开Http连接
                    HttpClientRequest request = await httpClient.getUrl(
                        Uri.parse("https://www.baidu.com"));
                    request.headers.add(
                 "user-agent", "Mozilla/5.0  Version/10.0 Mobile/14E304 Safari/602.1");
                    //等待连接服务器(会将请求信息发送给服务器)
                    HttpClientResponse response = await request.close();
                    //读取响应内容
                    _text = await response.transform(utf8.decoder).join();
                    //输出响应头
                    print(response.headers);

                    //关闭client后,通过该client发起的所有请求都会中止。
                    httpClient.close();

                  } catch (e) {
                    _text = "请求失败:$e";
                  } finally {
                    setState(() {
                      _loading = false;
                    });
                  }
                }
            ),
            Container(
                width: MediaQuery.of(context).size.width-50.0,
                child: Text(_text.replaceAll(new RegExp(r"\s"), ""))
            )
          ],
        ),
      ),
    );
  }
}
FlutterHttp请求 Dio

​ 直接使用HttpClient发起网络请求是比较麻烦的,很多事情得手动处理,如果再涉及到文件上传/下载、Cookie管理等就会非常繁琐。

参考文档

github

引入

引入dio:

dependencies:
  dio: ^x.x.x #请使用pub上的最新版本

导入并创建dio实例:

import 'package:dio/dio.dart';
Dio dio =  Dio();

接下来就可以通过 dio实例来发起网络请求了,注意,一个dio实例可以发起多个http请求,一般来说,APP只有一个http数据源时,dio应该使用单例模式。

import 'package:dio/dio.dart';
void getHttp() async {
  try {
    Response response = await Dio().get("http://www.baidu.com");
    print(response);
  } catch (e) {
    print(e);
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值