分享

Flutter基础笔记

 大前端之旅 2022-03-10

目录

List里面常用的属性和方法:

/*
List里面常用的属性和方法:

   常用属性:
       length          长度
       reversed        翻转
       isEmpty         是否为空
       isNotEmpty      是否不为空
   常用方法:  
       add         增加
       addAll      拼接数组
       indexOf     查找  传入具体值
       remove      删除  传入具体值
       removeAt    删除  传入索引值
       fillRange   修改   
       insert(index,value);            指定位置插入    
       insertAll(index,list)           指定位置插入List
       toList()    其他类型转换成List  
       join()      List转换成字符串
       split()     字符串转化成List
       forEach   
       map
       where
       any
       every

*/


void main(){

 // List myList=['香蕉','苹果','西瓜'];
 // print(myList[1]);


 // var list=new List();
 // list.add('111');
 // list.add('222');
 // print(list);


//List里面的属性:
   // List myList=['香蕉','苹果','西瓜'];
   // print(myList.length);
   // print(myList.isEmpty);
   // print(myList.isNotEmpty);
   // print(myList.reversed);  //对列表倒序排序
   // var newMyList=myList.reversed.toList();
   // print(newMyList);

//List里面的方法:


   // List myList=['香蕉','苹果','西瓜'];
   //myList.add('桃子');   //增加数据  增加一个

   // myList.addAll(['桃子','葡萄']);  //拼接数组

   // print(myList);

   //print(myList.indexOf('苹x果'));    //indexOf查找数据 查找不到返回-1  查找到返回索引值


   // myList.remove('西瓜');

   // myList.removeAt(1);

   // print(myList);
 



   // List myList=['香蕉','苹果','西瓜'];

   // myList.fillRange(1, 2,'aaa');  //修改

   //  myList.fillRange(1, 3,'aaa');  


   // myList.insert(1,'aaa');      //插入  一个

   // myList.insertAll(1, ['aaa','bbb']);  //插入 多个

   // print(myList);




   // List myList=['香蕉','苹果','西瓜'];

   // var str=myList.join('-');   //list转换成字符串

   // print(str);

   // print(str is String);  //true


   var str='香蕉-苹果-西瓜';

   var list=str.split('-');

   print(list);

   print(list is List);

}

Set

//Set 

//用它最主要的功能就是去除数组重复内容

//Set是没有顺序且不能重复的集合,所以不能通过索引去获取值

void main(){

  
  // var s=new Set();
  // s.add('香蕉');
  // s.add('苹果');
  // s.add('苹果');

  // print(s);   //{香蕉, 苹果}

  // print(s.toList()); 


  List myList=['香蕉','苹果','西瓜','香蕉','苹果','香蕉','苹果'];

  var s=new Set();

  s.addAll(myList);

  print(s);

  print(s.toList());


  
}

Map

/*
  映射(Maps)是无序的键值对:

    常用属性:
        keys            获取所有的key值
        values          获取所有的value值
        isEmpty         是否为空
        isNotEmpty      是否不为空
    常用方法:
        remove(key)     删除指定key的数据
        addAll({...})   合并映射  给映射内增加属性
        containsValue   查看映射内的值  返回true/false
        forEach   
        map
        where
        any
        every


*/

void main(){

 
  // Map person={
  //   "name":"张三",
  //   "age":20
  // };


  // var m=new Map();
  // m["name"]="李四";
  
  // print(person);
  // print(m);

//常用属性:

    // Map person={
    //   "name":"张三",
    //   "age":20,
    //   "sex":"男"
    // };

    // print(person.keys.toList());
    // print(person.values.toList());
    // print(person.isEmpty);
    // print(person.isNotEmpty);


//常用方法:
    Map person={
      "name":"张三",
      "age":20,
      "sex":"男"
    };

    // person.addAll({
    //   "work":['敲代码','送外卖'],
    //   "height":160
    // });

    // print(person);



    // person.remove("sex");
    // print(person);



    print(person.containsValue('张三'));
}

forEach,map, where,any,every

/*
        forEach     
        map         
        where       
        any
        every
*/
void main(){


      //  List myList=['香蕉','苹果','西瓜'];

      // for(var i=0;i<myList.length;i++){
      //   print(myList[i]);
      // }


      // for(var item in myList){
      //   print(item);
      // }


      // myList.forEach((value){
      //     print("$value");
      // });





      // List myList=[1,3,4];

      // List newList=new List();

      // for(var i=0;i<myList.length;i++){

      //   newList.add(myList[i]*2);
      // }
      // print(newList);





      // List myList=[1,3,4];      
      // var newList=myList.map((value){
      //     return value*2;
      // });
      //  print(newList.toList());





      // List myList=[1,3,4,5,7,8,9];

      // var newList=myList.where((value){
      //     return value>5;
      // });
      // print(newList.toList());



      // List myList=[1,3,4,5,7,8,9];

      // var f=myList.any((value){   //只要集合里面有满足条件的就返回true

      //     return value>5;
      // });
      // print(f);



      // List myList=[1,3,4,5,7,8,9];

      // var f=myList.every((value){   //每一个都满足条件返回true  否则返回false

      //     return value>5;
      // });
      // print(f);






      // set

      // var s=new Set();

      // s.addAll([1,222,333]);

      // s.forEach((value)=>print(value));



      //map

       Map person={
          "name":"张三",
          "age":20
        };

        person.forEach((key,value){            
            print("$key---$value");
        });

}

extends抽象类 和 implements

/*
Dart中抽象类: Dart抽象类主要用于定义标准,子类可以继承抽象类,也可以实现抽象类接口。


  1、抽象类通过abstract 关键字来定义

  2、Dart中的抽象方法不能用abstract声明,Dart中没有方法体的方法我们称为抽象方法。

  3、如果子类继承抽象类必须得实现里面的抽象方法

  4、如果把抽象类当做接口实现的话必须得实现抽象类里面定义的所有属性和方法。

  5、抽象类不能被实例化,只有继承它的子类可以

extends抽象类 和 implements的区别:

  1、如果要复用抽象类里面的方法,并且要用抽象方法约束自类的话我们就用extends继承抽象类

  2、如果只是把抽象类当做标准的话我们就用implements实现抽象类

*/

Flutter环境搭建

安装最新的 Xcode

下载androidstudio

https://developer.android.google.cn/studio

下载 Flutter SDK

https://v/docs/development/tools/sdk/releases?tab=macos

把下载好的 Flutter SDK 随便减压到你想安装 Sdk 的目录如

/Users/cc/flutter

Flutter 安装目录的 bin 目录配置到环境变量,然后把 Flutter 国内镜像也配置到环境 变量里面

 vim ~/.zshrc
export PATH=/Users/cc/flutter/bin:$PATH
export ANDROID_HOME="/Users/cc/Library/Android/sdk"
export PATH=${PATH}:${ANDROID_HOME}/tools
export PATH=${PATH}:${ANDROID_HOME}/platform-tools
export PUB_HOSTED_URL=https://pub.
export FLUTTER_STORAGE_BASE_URL=https://storage.
source ~/.zshrc

flutter -h 如果能出来一些命令说明 flutter sdk 配置成功。

注意如果配置完成后输入 flutter -h 告诉你 flutter 不是内置命令之类的错误的话,可能 sdk 没有配置成功,也可能 sdk 下载的时候没有下载全

运行 flutter doctor 命令检测环境

入口文件、入口方法

每一个 flutter 项目的 lib 目录里面都有一个 main.dart 这个文件就是 flutter 的入口文件

main.dart 里面的

void main() {
  runApp(MyApp());
}
//也可以简写
void main() => runApp(MyApp());

其中的 main 方法是 dart 的入口方法。runApp 方法是 flutter 的入口方法。 MyApp 是自定义的一个组件

第一个 Demo Center 组件的 使用

import 'package:flutter/material.dart';

void main() {
  runApp(Center(
    child: Text(
      "我是一个文本内容",
      textDirection: TextDirection.ltr,
    ),
  ));
}

把内容单独抽离成一个组件

在 Flutter 中自定义组件其实就是一个类,这个类需要继承 StatelessWidget/StatefulWidget

前期我们都继承 StatelessWidget。后期给大家讲 StatefulWidget 的使用。

StatelessWidget 是无状态组件,状态不可变的 widget
StatefulWidget 是有状态组件,持有的状态可能在 widget 生命周期改变

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        "我是一个文本内容",
        textDirection: TextDirection.ltr,
      ),
    );
  }
}

给 Text 组件增加一些装饰

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        "我是一个文本内容",
        textDirection: TextDirection.ltr,
        style: TextStyle(
            fontSize: 40.0,
            fontWeight: FontWeight.bold,
            // color: Colors.yellow
          color: Color.fromRGBO(255, 222, 222, 0.5)
        ),
      ),
    );
  }
}

用MaterialApp 和 Scaffold两个组件装饰 App

1MaterialApp

MaterialApp 是一个方便的 Widget,它封装了应用程序实现 Material Design 所需要的 一些 Widget。一般作为顶层 widget 使用。

常用的属性:

home(主页)

title(标题)

color(颜色)

theme(主题)

routes(路由)

2Scaffold

Scaffold 是 Material Design 布局结构的基本实现。此类提供了用于显示 drawer、snackbar 和底部 sheet 的 API。

Scaffold 有下面几个主要属性:

appBar - 显示在界面顶部的一个 AppBar。

body - 当前界面所显示的主要内容 Widget。

drawer - 抽屉菜单控件。

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("Hello Flutter"),
          elevation: 30.0, 设置标题阴影  不需要的话值设置成 0.0
        ),
        body: HomeContent(),
      ),
      theme: ThemeData(
          //设置主题颜色
          primarySwatch: Colors.yellow),
    );
  }
}

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        "我是一个文本内容",
        textDirection: TextDirection.ltr,
        style: TextStyle(
            fontSize: 40.0,
            fontWeight: FontWeight.bold,
            // color: Colors.yellow
            color: Color.fromRGBO(255, 222, 222, 0.5)
        ),
      ),
    );
  }
}

Text 组件

名称功能
textAlign文本对齐方式(center 居中,left 左 对齐,right 右对齐,justfy 两端对齐)
textDirection文本方向(ltr 从左至右,rtl 从右至 左)
overflow文字超出屏幕之后的处理方式(clip 裁剪,fade 渐隐,ellipsis 省略号)
textScaleFactor字体显示倍率
maxLines文字显示最大行数
style字体的样式设置

下面是 TextStyle 的参数 :

名称功能
decoration文字装饰线(none 没有线,lineThrough 删 除线,overline 上划线,underline 下划线)
decorationColor文字装饰线颜色
decorationStyle文字装饰线风格([dashed,dotted]虚线, double 两根线,solid 一根实线,wavy 波浪 线)
wordSpacing单词间隙(如果是负值,会让单词变得更紧 凑
letterSpacing字母间隙(如果是负值,会让字母变得更紧 凑)
fontStyle文字样式(italic 斜体,normal 正常体)
fontSize文字大小
color文字颜色
fontWeight字体粗细(bold 粗体,normal 正常体)

更多参数:https://docs./flutter/painting/TextStyle-class.html

Container 组件

名称功能
alignmenttopCenter:顶部居中对齐
topLeft:顶部左对齐
topRight:顶部右对齐
center:水平垂直居中对齐
centerLeft:垂直居中水平居左对齐
centerRight:垂直居中水平居右对齐
bottomCenter 底部居中对齐
bottomLeft:底部居左对齐
bottomRight:底部居右对齐
decorationdecoration: BoxDecoration(
color: Colors.blue,
border: Border.all(
color: Colors.red,
width: 2.0,
),
borderRadius:
BorderRadius.all(
Radius.circular(8.0)
)
)
marginmargin 属性是表示 Container 与外部其他 组件的距离。
EdgeInsets.all(20.0),
paddingpadding 就是 Container 的内边距,指 Container 边缘与 Child 之间的距离
padding: EdgeInsets.all(10.0)
transform让 Container 容易进行一些旋转之类的
transform: Matrix4.rotationZ(0.2)
height容器高度
width容器宽度
child容器子元素

更多参数:https://api.v/flutter/widgets/Container-class.html

图片组件

图片组件是显示图像的组件,Image 组件有很多构造函数,这里我们只给大家讲两个

Image.asset 本地图片

Image.network 远程图片

Image 组件的常用属性:

名称类型说明
alignmentalignment图片的对齐方式
color 和 colorBlendMode设置图片的背景颜色,通常和 colorBlendMode 配合一起使用,这样可以是图片颜色和背景色混合。上面的图片就是进行了颜色的混合,绿色背景和图片红色的混合
fitBoxFitfit 属性用来控制图片的拉伸和挤压,这都是根据父容器来的。
BoxFit.fill:全图显示,图片会被拉伸,并充满父容器。
BoxFit.contain:全图显示,显示原比例,可能会有空隙。
BoxFit.cover:显示可能拉伸,可能裁切,充满(图片要充满整个容器,还不变形)。
BoxFit.fitWidth:宽度充满(横向充满),显示可能拉伸,可能裁切。
BoxFit.fitHeight :高度充满(竖向充满),显示可能拉伸,可能裁切。
BoxFit.scaleDown:效果和 contain 差不多,但是此属性不允许显示超过源图片大小,可小不可大。
repeat平铺ImageRepeat.repeat : 横向和纵向都进行重复,直到铺满整个画布。
ImageRepeat.repeatX: 横向重复,纵向不重复。
ImageRepeat.repeatY:纵向重复,横向不重复。
width宽度 一般结合 ClipOval 才能看到效果
height高度 一般结合 ClipOval 才能看到效果

更多属性参考:https://api.v/flutter/widgets/Image-class.html

return Center(
      child: Container(
        child: Image.network(
          "http://pic.baike.soso.com/p/20130828/20130828161137-1346445960.jpg",
          alignment: Alignment.topLeft,
          color: Colors.red,
          colorBlendMode: BlendMode.colorDodge,
          // repeat: ImageRepeat.repeatX,
          fit: BoxFit.cover,
        ),
        width: 300.0,
        height: 400.0,
        decoration: BoxDecoration(
            color: Colors.yellow
        ),
      ),
    );

引入本地图片

emmm…不记了

裁剪布局之 ClipRect、ClipRRect、ClipOval、ClipPath、CustomClipper

widget 作用
ClipRect 将 child 剪裁为给定的矩形大小
ClipRRect 将 child 剪裁为圆角矩形
ClipOval 如果 child 为正方形时剪裁之后是圆形,如果 child 为矩形时,剪裁之后为椭圆形
ClipPath 将 child 按照给定的路径进行裁剪
CustomClipper 并不是一个widget,但是使用CustomClipper可以绘制出任何我们想要的形状

实现圆角以及实现圆形图片

实现圆角图片

return Center(
      child: Container(
        width: 300.0,
        height: 300.0,
        decoration: BoxDecoration(
            color: Colors.yellow,
            borderRadius: BorderRadius.circular(150),
            image: DecorationImage(
                image: NetworkImage(
                  "http://pic.baike.soso.com/p/20130828/20130828161137-1346445960.jpg",
                ),
                fit: BoxFit.cover
            )
        ),
      ),
    );

实现圆形图片

return Center(
      child: Container(
        child: ClipOval(
          child: Image.network(
            "https://www./images/201905/thumb_img/1101_thumb_G_1557845381862.jpg",
            width: 150.0,
            height: 150.0,
          ),
        ),
      ),
    );

圆形头像

ClipOval

new ClipOval(
    child: new Image.asset(Utils.getImgPath('ali_connors')),
  )

② CircleAvatar

new CircleAvatar(
    radius: 36.0,
    backgroundImage: AssetImage(
      Utils.getImgPath('ali_connors'),
    ),
  )

③ BoxDecoration BoxShape.circle

 new Container(
    width: 72.0,
    height: 72.0,
    decoration: BoxDecoration(
      shape: BoxShape.circle,
      image: DecorationImage(
        image: AssetImage(
          Utils.getImgPath('ali_connors'),
        ),
      ),
    ),
  )

圆角头像

① ClipRRect

new ClipRRect(
    borderRadius: BorderRadius.circular(6.0),
    child: new Image.asset(Utils.getImgPath('ali_connors')),
  )

② BoxDecoration BoxShape.rectangle

new Container(
    width: 88.0,
    height: 88.0,
    decoration: BoxDecoration(
      shape: BoxShape.rectangle,
      borderRadius: BorderRadius.circular(6.0),
      image: DecorationImage(
        image: AssetImage(
          Utils.getImgPath('ali_connors'),
        ),
      ),
    ),

列表组件概述

列表布局是我们项目开发中最常用的一种布局方式。Flutter 中我们可以通过 ListView 来定义 列表项,支持垂直和水平方向展示。通过一个属性就可以控制列表的显示方向。列表有一下 分类:

**1、垂直列表(宽度自动扩展,设置宽度无效)**可以在外层包Container控制

2、垂直图文列表

**3、水平列表(高度自动扩展,设置高度无效)**可以在外层包Container控制

4、动态列表

5、矩阵式列表(网格布局)

列表参数

名称类型说明
scrollDirectionAxisAxis.horizontal 水平列表
Axis.vertical 垂直列表
paddingEdgeInsetsGeometry内边距
resolvebool组件反向排序
childrenList列表元素

基本列表

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: ListView(
        children: <Widget>[
          ListTile(
            leading: Icon(Icons.phone),
            title: Text("this is list", style: TextStyle(fontSize: 28.0)),
            subtitle: Text('this is list this is list'),
          ),
          ListTile(
            title: Text("this is list"),
            subtitle: Text('this is list this is list'),
            trailing: Icon(Icons.phone),
          ),
          ListTile(
            title: Text("this is list"),
            subtitle: Text('this is list this is list'),
          ),
          ListTile(
            title: Text("this is list"),
            subtitle: Text('this is list this is list'),
          ),
          ListTile(
            title: Text("this is list"),
            subtitle: Text('this is list this is list'),
          )
        ],
      ),
    );
  }
}

水平列表

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      height: 200.0,
      margin: EdgeInsets.all(5),
      child: ListView(
        scrollDirection: Axis.horizontal,
        children: <Widget>[
          Container(
            width: 180.0,
            color: Colors.lightBlue,
          ),
          Container(
            width: 180,
            color: Colors.amber,
            child: ListView(
              children: <Widget>[
                Image.network(
                    "https://resources./images/childhood-in-a-picture.jpg"),
                SizedBox(height: 16.0),
                Text("这是一个文本信息",
                    textAlign: TextAlign.center,
                    style: TextStyle(fontSize: 16.0)),
              ],
            ),
          )
        ],
      ),
    );
  }
}

动态列表(动态循环数据)

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("Hello Flutter")),
        body: HomeContent(),
      ),
    );
  }
}

class HomeContent extends StatelessWidget {
  List list = List();

  HomeContent() {
    for (int i = 0; i < 20; i++) {
      list.add("这是第$i条数据");
    }
    print(list);
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
        itemCount: this.list.length,
        itemBuilder: (context, index) {
          // print(context);
          return ListTile(
            leading: Icon(Icons.phone),
            title: Text("${list[index]}"),
          );
        });
  }
}

GridView 组件的常用参数

当数据量很大的时候用矩阵方式排列比较清晰。此时我们可以用网格列表组件 GridView 实现布局。

GridView 创建网格列表有多种方式,下面我们主要介绍两种。

1、可以通过 GridView.count 实现网格布局

2、通过 GridView.builder 实现网格布局

常用属性:

名称类型说明
scrollDirectionAxis滚动方法
paddingEdgeInsetsGeometry内边距
resolvebool组件反向排序
crossAxisSpacingdouble水平子 Widget 之间间距
mainAxisSpacingdouble垂直子 Widget 之间间距
crossAxisCountint一行的 Widget 数量
childAspectRatiodouble子 Widget 宽高比例
children[ ]
gridDelegateSliverGridDelegateWithFix
edCrossAxisCount(常用)
SliverGridDelegateWithMax
CrossAxisExtent
控制布局主要用在GridView.builder 里面

GridView.count 实现网格布局

import 'package:cc/res/listData.dart';
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("Hello Flutter")),
        body: LayoutContent(),
      ),
    );
  }
}

class LayoutContent extends StatelessWidget {
  List<Widget> _getListData() {
    var tempList = listData.map((value) {
      return Container(
        child: Column(
          children: <Widget>[
            Image.network(value["imageUrl"]),
            SizedBox(height: 12),
            Text(value["title"],
                textAlign: TextAlign.center, style: TextStyle(fontSize: 20))
          ],
        ),
        decoration: BoxDecoration(
            border: Border.all(
                color: Color.fromRGBO(230, 230, 230, 0.9), width: 1.0)),
      );
    });
    // ('124124','124214')
    return tempList.toList();
  }

  @override
  Widget build(BuildContext context) {
    return GridView.count(
      crossAxisCount: 2,
      crossAxisSpacing: 20,
      mainAxisSpacing: 20,
//       childAspectRatio:0.7,
      children: this._getListData(),
    );
  }
}

GridView.builder 实现网格布局

import 'package:cc/res/listData.dart';
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("Hello Flutter")),
        body: LayoutContent(),
      ),
    );
  }
}

class LayoutContent extends StatelessWidget {
  Widget _getListData(context, index) {
    return Container(
      child: Column(
        children: <Widget>[
          Image.network(listData[index]["imageUrl"]),
          SizedBox(height: 12),
          Text(listData[index]["title"],
              textAlign: TextAlign.center, style: TextStyle(fontSize: 20))
        ],
      ),
      decoration: BoxDecoration(
          border: Border.all(
              color: Color.fromRGBO(230, 230, 230, 0.9), width: 1.0)),
    );
  }

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      itemCount: listData.length,
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          //横轴元素个数
          crossAxisCount: 2,
          //纵轴间距
          mainAxisSpacing: 20.0,
          //横轴间距
          crossAxisSpacing: 10.0,
          //子组件宽高长度比例
          childAspectRatio: 1.0),
      itemBuilder: this._getListData,
    );
  }
}

Paddiing 组件

在 html 中常见的布局标签都有 padding 属性,但是 Flutter 中很多 Widget 是没有 padding 属性。这个时候我们可以用 Padding 组件处理容器与子元素直接的间距。

属性说明
paddingpadding 值, EdgeInsetss 设置填充的值
child子组件
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.fromLTRB(0, 0, 10, 0),
      child: GridView.count(
        crossAxisCount: 2,
        childAspectRatio: 1.5,
        children: <Widget>[
          Padding(
            padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
            child: Image.network("https://www./images/flutter/1.png",
                fit: BoxFit.cover),
          ),
          Padding(
            padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
            child: Image.network("https://www./images/flutter/2.png",
                fit: BoxFit.cover),
          ),
          Padding(
            padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
            child: Image.network("https://www./images/flutter/3.png",
                fit: BoxFit.cover),
          ),
          Padding(
            padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
            child: Image.network("https://www./images/flutter/4.png",
                fit: BoxFit.cover),
          ),
          Padding(
            padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
            child: Image.network("https://www./images/flutter/5.png",
                fit: BoxFit.cover),
          ),
          Padding(
            padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
            child: Image.network("https://www./images/flutter/6.png",
                fit: BoxFit.cover),
          ),
        ],
      ),
    );
  }
}

Row 水平布局组件

属性说明
mainAxisAlignment主轴的排序方式
crossAxisAlignment次轴的排序方式
children组件子元素
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("Hello Flutter")),
        body: LayoutDemo(),
      ),
    );
  }
}

class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      height: 700,
      width: 500,
      color: Colors.black26,
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
//        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          IconContainer(Icons.home, color: Colors.red),
          IconContainer(Icons.home, color: Colors.blue),
          IconContainer(Icons.home, color: Colors.orange),
        ],
      ),
    );
  }
}

class IconContainer extends StatelessWidget {
  double size;
  IconData icon;
  Color color;

  IconContainer(this.icon, {this.size, this.color = Colors.blue}) {
    this.size = 32.0;
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: this.size + 60,
      height: this.size + 60,
      color: this.color,
      child: Center(
          child: Icon(
        this.icon,
        color: Colors.white,
        size: this.size,
      )),
    );
  }
}

Column 垂直布局组件

属性说明
mainAxisAlignment主轴的排序方式
crossAxisAlignment次轴的排序方式
children组件子元素
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("Hello Flutter")),
        body: LayoutDemo(),
      ),
    );
  }
}

class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      height: 700,
      width: 500,
      color: Colors.black26,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
//        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          IconContainer(Icons.home, color: Colors.red),
          IconContainer(Icons.home, color: Colors.blue),
          IconContainer(Icons.home, color: Colors.orange),
        ],
      ),
    );
  }
}

class IconContainer extends StatelessWidget {
  double size;
  IconData icon;
  Color color;

  IconContainer(this.icon, {this.size, this.color = Colors.blue}) {
    this.size = 32.0;
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: this.size + 60,
      height: this.size + 60,
      color: this.color,
      child: Center(
          child: Icon(
        this.icon,
        color: Colors.white,
        size: this.size,
      )),
    );
  }
}

Expanded 类似 Web 中的 Flex 布局

Expanded 可以用在 Row 和 Column 布局中

属性说明
flex元素站整个父 Row /Column 的比例
child子元素
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("Hello Flutter")),
        body: LayoutDemo(),
      ),
    );
  }
}

class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(10),
      child: Row(
        // crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisAlignment: MainAxisAlignment.center,
        // crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          Expanded(flex: 2, child: IconContainer(Icons.home)),
          SizedBox(width: 10),
          Expanded(flex: 3, child: IconContainer(Icons.search)),
          // SizedBox(width: 10),
          // Expanded(child: IconContainer(Icons.send))
        ],
      ),
    );
  }
}

class IconContainer extends StatelessWidget {
  double size;
  IconData icon;

  IconContainer(this.icon, {this.size}) {
    this.size = 32.0;
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100.0,
      height: 100.0,
      color: Colors.blue,
      child:
          Center(child: Icon(this.icon, color: Colors.white, size: this.size)),
    );
  }
}

Stack 组件

Stack 表示堆的意思,我们可以用 Stack 或者 Stack 结合 Align 或者 Stack 结合 Positiond 来实现页面的定位布局

属性说明
alignment配置所有子元素的显示位置
children子组件
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Center(
      child: Stack(
        alignment: Alignment.topLeft,
        children: <Widget>[              
          Container(
            height: 400,
            width: 300,
            color: Colors.red,
          ),
          Text('我是一个文本',style: TextStyle(
            fontSize: 40,
            color: Colors.white
          ))           
        ],
      ),
    );
  }
}
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Center(
      child: Stack(
        alignment: Alignment(1,0.3),
        children: <Widget>[              
          Container(
            height: 400,
            width: 300,
            color: Colors.red,
          ),
          Text('我是一个文本',style: TextStyle(
            fontSize: 20,
            color: Colors.white
          ))           
        ],
      ),
    );
  }
}

Stack Align

Stack 组件中结合 Align 组件可以控制每个子元素的显示位置

属性说明
alignment配置所有子元素的显示位置
child子组件
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    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),
                )
              ],
            ),
      ),
    );
  }
}

Stack Positioned

Stack 组件中结合 Positioned 组件也可以控制每个子元素的显示位置

属性说明
top子元素距离顶部的距离
bottom子元素距离底部的距离
left子元素距离左侧距离
right子元素距离右侧距离
child子组件
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    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 组件

AspectRatio 的作用是根据设置调整子元素 child 的宽高比。

AspectRatio 首先会在布局限制条件允许的范围内尽可能的扩展,widget 的高度是由宽度和比率决定的,类似于 BoxFit 中的 contain,按照固定比率去尽量占满区域。

如果在满足所有限制条件过后无法找到一个可行的尺寸,AspectRatio 最终将会去优先适应布局限制条件,而忽略所设置的比率。

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

Card 组件

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

属性说明
margin外边距
child子组件
ShapeCard 的阴影效果,默认的阴影效果为圆角的长方形边。
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView(
      children: <Widget>[
        Card(
          margin: EdgeInsets.all(10),
          child: Column(
            children: <Widget>[
              ListTile(
                title: Text(
                  "张三",
                  style: TextStyle(fontSize: 28),
                ),
                subtitle: Text("高级软件工程师"),
              ),
              Divider(),
              ListTile(
                title: Text("电话:123123123"),
              ),
              ListTile(
                title: Text("地址:北京市海淀区"),
              )
            ],
          ),
        ),
        Card(
          margin: EdgeInsets.all(10),
          child: Column(
            children: <Widget>[
              ListTile(
                title: Text(
                  "李四",
                  style: TextStyle(fontSize: 28),
                ),
                subtitle: Text("高级软件工程师"),
              ),
              Divider(),
              ListTile(
                title: Text("电话:123123123"),
              ),
              ListTile(
                title: Text("地址:北京市海淀区"),
              )
            ],
          ),
        ),
      ],
    );
  }
}

Card 组件实现一个图文列表布局

class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView(
        children: listData.map((value) {
      return Card(
        margin: EdgeInsets.all(10),
        child: Column(
          children: <Widget>[
            AspectRatio(
              aspectRatio: 16 / 9,
              child: Image.network(value["imageUrl"], fit: BoxFit.cover),
            ),
            ListTile(
              leading: CircleAvatar(
                backgroundImage: NetworkImage(value["imageUrl"]),
              ),
              title: Text(value["description"]),
              subtitle: Text(
                value["description"],
                overflow: TextOverflow.ellipsis,
              ),
            )
          ],
        ),
      );
    }).toList());
  }
}
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: listData.length,
      itemBuilder: (context,index){
        return Card(
          margin: EdgeInsets.all(10),
          child: Column(
            children: <Widget>[
              AspectRatio(
                aspectRatio: 16 / 9,
                child: Image.network(listData[index]["imageUrl"], fit: BoxFit.cover),
              ),
              ListTile(
                leading: CircleAvatar(
                  backgroundImage: NetworkImage(listData[index]["imageUrl"]),
                ),
                title: Text(listData[index]["description"]),
                subtitle: Text(
                  listData[index]["description"],
                  overflow: TextOverflow.ellipsis,
                ),
              )
            ],
          ),
        );
      },
    );
  }
}

RaisedButton 定义一个按钮

Flutter 中通过 RaisedButton 定义一个按钮。RaisedButton 里面有很多的参数,这一讲我们只是简单的进行使用。

return RaisedButton(
        child: Text("Flutter"),
        textColor: Theme.of(context).accentColor,
    onPressed: (){
          
    });

Wrap 组件

Wrap 可以实现流布局,单行的 Wrap 跟 Row 表现几乎一致,单列的 Wrap 则跟 Row 表 现几乎一致。但 Row 与 Column 都是单行单列的,Wrap 则突破了这个限制,mainAxis 上空 间不足时,则向 crossAxis 上去扩展显示。

属性说明
direction主轴的方向,默认水平
alignment主轴的对其方式
spacing主轴方向上的间距
textDirection文本方向
verticalDirection定义了 children 摆放顺序,默认是 down,见Flex 相关属性介绍。
runAlignmentrun 的对齐方式。run 可以理解为新的行或者列,如果是水平方向布局的话,run 可以理解为新的一行
runSpacingrun 的间距
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("Hello Flutter")),
        body: LayoutDemo(),
      ),
    );
  }
}

class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Wrap(
      spacing: 10,
      runSpacing: 10,
      alignment: WrapAlignment.spaceEvenly,
      children: <Widget>[
        MyButton("第 1 集"),
        MyButton("第 2 集"),
        MyButton("第 3 集"),
        MyButton("第 4 集"),
        MyButton("第 5 集"),
        MyButton("第 6 集第 6 集"),
        MyButton("第 7 集"),
        MyButton("第 8 集第 6 集"),
        MyButton("第 9 集"),
        MyButton("第 10 集"),
        MyButton("第 11 集"),
      ],
    );
  }
}

class MyButton extends StatelessWidget {
  final String text;

  const MyButton(this.text, {Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return RaisedButton(
        child: Text(this.text),
        textColor: Theme.of(context).accentColor,
        onPressed: () {});
  }
}

自定义有状态组件

在 Flutter 中自定义组件其实就是一个类,这个类需要继承 StatelessWidget/StatefulWidget。

StatelessWidget 是无状态组件,状态不可变的 widget
StatefulWidget 是有状态组件,持有的状态可能在 widget 生命周期改变。通俗的讲:如果我们想改变页面中的数据的话这个时候就需要用到 StatefulWidget

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("Hello Flutter")),
        body: HomePage(),
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: <Widget>[
          Chip(label: Text("${this.count}")),
          RaisedButton(
              child: Text("增加"),
              onPressed: () {
//                print(this.count);
                setState(() {
                  this.count++;
                });
              })
        ],
      ),
    );
  }
}

BottomNavigationBar 组件

BottomNavigationBar 是底部导航条,可以让我们定义底部 Tab 切换,bottomNavigationBar是 Scaffold 组件的参数。

BottomNavigationBar 常见的属性

属性名说明
itemsList 底 部 导 航条按钮集合
iconSizeicon
currentIndex默认选中第几个
fixedColor选中的颜色
typeBottomNavigationBarType.fixed
BottomNavigationBarType.shifting
(上面解决4个底部导航显示出错)
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("设置")
            )
          ],
        ),
      );
  }
}

路由

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: (context) => SerachPage()
            )
        );
      },
      color: Theme.of(context).accentColor,
      textTheme: ButtonTextTheme.primary,
    )

基本路由跳转传值

比如我们现在想从 HomePage 组件跳转到 SearchPage 组件传值。

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

import '../SearchPage.dart';

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

RaisedButton(
      child: Text("跳转到搜索页面"),
      onPressed: () {
        Navigator.of(context).push(
            MaterialPageRoute(
            builder: (context) => SerachPage(title:"表单") //传值 SerachPage加构造函数并传参数
            )
        );
      },
      color: Theme.of(context).accentColor,
      textTheme: ButtonTextTheme.primary,
    )

命名路由

1、配置路由

return MaterialApp(
      // home:Tabs(),
      initialRoute: '/', //初始化的时候加载的路由
      routes: {
        '/':(contxt)=>Tabs(),
        '/search':(contxt) =>SearchPage(),
        '/form': (context) => FormPage(),
      },
    );

2、路由跳转

RaisedButton(
            child: Text("跳转到搜索页面"),
            onPressed: () {
              Navigator.pushNamed(context, '/search');
            },
            color: Theme.of(context).accentColor,
            textTheme: ButtonTextTheme.primary
        )d

命名路由跳转传值

花里胡哨

命名路由单独抽离到一个文件

抽个鸡儿

返回到上一级页面

Navigator.of(context).pop();

替换路由

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

Navigator.of(context).pushReplacementNamed('/registerSecond'); //命名路由替换
// 普通路由替换
Navigator.of(context).pushReplacement(
            MaterialPageRoute(builder: (context)=>Second())
          );

返回到根路由

比如我们从用户中心跳转到 registerFirst 页面,然后从 registerFirst 页面跳转到 registerSecond页面,然后从 registerSecond 跳转到了 registerThird 页面。这个时候我们想的是 registerThird注册成功后返回到用户中心。 这个时候就用到了返回到根路由的方法。

Navigator.of(context).pushAndRemoveUntil(
new MaterialPageRoute(builder: (context) => new Tabs(index:1)),
(route) => route == null
);

AppBar 自定义顶部按钮图标、颜色

属性描述
leading在标题前面显示的一个控件,在首页通常显示应用的 logo;在其他界面通常显示为返回按钮
title标题,通常显示为当前界面的标题文字,可以放组件
actions在标题后面显示的一个控件,通常使用 IconButton 来表示,可以放按钮组
bottom通常放 tabBar,标题下面显示一个 Tab 导航栏
backgroundColor导航背景颜色
iconTheme图标样式
textTheme文字样式
centerTitle标题是否居中显示
class AppBardemoPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.red,
        leading: IconButton(
            icon: Icon(Icons.menu),
            tooltip: "Search",
            onPressed: () {
              print('Menu Pressed');
            }),
        title: Text('FlutterDemo'),
        actions: <Widget>[
          IconButton(
              icon: Icon(Icons.search),
              tooltip: "Search",
              onPressed: () {
                print('Search Pressed');
              }),
          IconButton(
              icon: Icon(Icons.more_horiz),
              tooltip: "more_horiz",
              onPressed: () {
                print('more_horiz Pressed');
              })
        ],
      ),
      body: Text('这是Appbar'),
    );
  }
}

AppBar 中自定义 TabBar 实现顶部 Tab 切换

TabBar 常见属性:

属性描述
tabs显示的标签内容,一般使用 Tab 对象,也可以是其他的 Widget
controllerTabController 对象
isScrollable是否可滚动(是指有很多个appbar时滚动appbar,左右滚动appbar。不是滚动内容)
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
import 'package:flutter/material.dart';

class AppBardemoPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController( //DefaultTabController在MaterialApp之后,Scaffold之前
        length: 2,                //如果页面通过路由挂载,直接return DefaultTabController
        child: Scaffold(
          appBar: AppBar(
            title: TabBar(
              tabs: <Widget>[
                Tab(text: '热门'),
                Tab(text: "123"),
              ],
            ),
          ),
          body: TabBarView(
            children: <Widget>[
              ListView(
                children: <Widget>[
                  ListTile(title: Text("这是第一个 tab")),
                  ListTile(title: Text("这是第一个 tab")),
                  ListTile(title: Text("这是第一个 tab"))
                ],
              ),
              ListView(
                children: <Widget>[
                  ListTile(title: Text("这是第一个 tab")),
                  ListTile(title: Text("这是第一个 tab")),
                  ListTile(title: Text("这是第一个 tab"))
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

把 TabBar 放在导航最顶部

把TabBar放在titile里面

import 'package:flutter/material.dart';

class AppBardemoPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(
        length: 2,
        child: Scaffold(
          appBar: AppBar(
            isScrollable: true, //如果多个按钮的话可以滑动
//            backgroundColor: Colors.red,
            leading: IconButton(
                icon: Icon(Icons.arrow_back),
                tooltip: "Search",
                onPressed: () {
                  Navigator.of(context).pop();
                }),
            title: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Expanded(
                    flex: 1,
                    child: TabBar(
                      tabs: <Widget>[Tab(text: "热门"), Tab(text: "推荐")],
                    ))
              ],
            ),
          ),
          body: TabBarView(
            children: <Widget>[
              ListView(
                children: <Widget>[
                  ListTile(title: Text("这是第一个 tab")),
                  ListTile(title: Text("这是第一个 tab")),
                  ListTile(title: Text("这是第一个 tab"))
                ],
              ),
              ListView(
                children: <Widget>[
                  ListTile(title: Text("这是第一个 tab")),
                  ListTile(title: Text("这是第一个 tab")),
                  ListTile(title: Text("这是第一个 tab"))
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

AppBar 中自定义 TabBar 实现 Tabs 的另一种方法。

TabController需要继承有状态组件

import 'package:flutter/material.dart';

class AppBardemoPage extends StatefulWidget {
  @override
  _AppBardemoPageState createState() => _AppBardemoPageState();
}

class _AppBardemoPageState extends State<AppBardemoPage>
    with SingleTickerProviderStateMixin {
  TabController _tabController;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _tabController = new TabController(
        vsync: this,
        length: 2
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AppBardemoPage"),
        bottom: TabBar(
          controller: this._tabController, //注意
          tabs: <Widget>[
            Tab(text: "热销"), Tab(text: "推荐"),
          ],
        ),
      ),
      body: TabBarView(
        controller: this._tabController, //注意
        children: <Widget>[
          Center(child: Text("热销")),
          Center(child: Text("推荐"))
        ],
      ),
    );
  }
}

Flutter Drawer 侧边栏

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

return Scaffold(
      appBar: AppBar(
        title: Text("Hello Flutter"),
      ),
      drawer: Drawer(
        child: Text("左侧边栏"),
      ),
      endDrawer: Drawer(
        child: Text("右侧边栏"),
      ),
    );

DrawerHeader

常见属性:

属性描述
decoration设置顶部背景颜色
child配置子元素
padding内边距
margin外边距
drawer: Drawer(
        child: Column(
          children: <Widget>[
            DrawerHeader(
              decoration: BoxDecoration(
                  color: Colors.yellow,
                  image: DecorationImage(
                      image: NetworkImage(
                          "https://www./images/flutter/2.png"),
                      fit: BoxFit.cover)),
              child: ListView(
                children: <Widget>[Text("我是一个头部")],
              ),
            ),
            ListTile(
              title: Text("个人中心"),
              leading: CircleAvatar(
                child: Icon(Icons.people),
              ),
            ),
            Divider(),
            ListTile(
              title: Text("系统设置"),
              leading: CircleAvatar(
                child: Icon(Icons.settings),
              ),
            ),
            Divider(),
          ],
        ),
      ),

UserAccountsDrawerHeader

属性描述
decoration设置顶部背景颜色
accountName账户名称
accountEmail账户邮箱
currentAccountPicture用户头像
otherAccountsPictures用来设置当前账户其他账户头像
margin
drawer: Drawer(
        child: Column(
          children: <Widget>[
            UserAccountsDrawerHeader(
              accountName: Text("喵喵喵?"),
              accountEmail: Text("xxx@xxx.com"),
              currentAccountPicture: CircleAvatar(// 自动处理成圆形,不需要再设置图片fit
                backgroundImage:
                    NetworkImage("https://www./images/flutter/3.png"),
              ),
              decoration: BoxDecoration(
                  color: Colors.yellow,
                  image: DecorationImage(
                      image: NetworkImage(
                          "https://www./images/flutter/2.png"),
                      fit: BoxFit.cover)),
              otherAccountsPictures: <Widget>[
                Image.network("https://www./images/flutter/4.png"),
                Image.network("https://www./images/flutter/5.png"),
                Image.network("https://www./images/flutter/6.png"),
              ],
            ),
            ListTile(
              title: Text("个人中心"),
              leading: CircleAvatar(
                child: Icon(Icons.people),
              ),
            ),
            Divider(),
            ListTile(
              title: Text("系统设置"),
              leading: CircleAvatar(
                child: Icon(Icons.settings),
              ),
            ),
            Divider(),
          ],
        ),
      ),

侧边栏路由跳转

onTap: () {
                Navigator.of(context).pop();
                Navigator.pushNamed(context, "/tabBarController");
              },

按钮组件介绍

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

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

FlatButton :扁平化的按钮

OutlineButton:线框按钮

IconButton :图标按钮

ButtonBar:按钮组

FloatingActionButton:浮动按钮

按钮组件中的一些属性

属性名称值类型属性值
onPressedVoidCallback,一般接收一个方法必填参数,按下按钮时触发的回调,接收一个方法,传 null 表示按钮禁用,会显示禁用相关样式
childWidget文本控件
textColorColor文本颜色
colorColor按钮的颜色
disabledColorColor按钮禁用时的颜色
disabledTextColorColor按钮禁用时的文本颜色
splashColorColor点击按钮时水波纹的颜色
highlightColorColor点击(长按)按钮后按钮的颜色
elevationdouble阴影的范围,值越大阴影范围越大
padding内边距
shape设置按钮的形状
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(10),
) //圆角按钮
shape: CircleBorder(
side: BorderSide(
color: Colors.white,
)
) //圆形按钮
class ButtonDemoPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("按钮演示页面"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                RaisedButton(
                  child: Text('普通按钮'),
                  onPressed: () {
                    print('点击了');
                  },
                ),
                SizedBox(width: 20),
                RaisedButton(
                  child: Text('有颜色的按钮'),
                  textColor: Colors.white,
                  color: Colors.blue,
                  onPressed: () {
                    print('点击了');
                  },
                ),
                SizedBox(width: 20),
                RaisedButton(
                  child: Text('阴影按钮'),
                  textColor: Colors.white,
                  color: Colors.blue,
                  elevation: 10,
                  onPressed: () {
                    print('点击了');
                  },
                )
              ],
            ),
            SizedBox(height: 40),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Container(
                    height: 60,
                    width: 200,
                    child: RaisedButton(
                      child: Text('有宽高的按钮'),
                      textColor: Colors.white,
                      color: Colors.blue,
                      elevation: 10,
                      onPressed: () {
                        print('点击了');
                      },
                    ))
              ],
            ),
            SizedBox(height: 40),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Expanded(
                    child: Container(
                  height: 60,
                  margin: EdgeInsets.all(20),
                  child: RaisedButton(
                    child: Text('全屏按钮'),
                    textColor: Colors.white,
                    color: Colors.blue,
                    elevation: 10,
                    onPressed: () {
                      print('点击了');
                    },
                  ),
                ))
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Expanded(
                    child: Container(
                  height: 60,
                  margin: EdgeInsets.all(20),
                  child: RaisedButton(
                    child: Text('带圆角的按钮'),
                    textColor: Colors.white,
                    color: Colors.blue,
                    elevation: 10,
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(10),
                    ),
                    onPressed: () {
                      print('点击了');
                    },
                  ),
                ))
              ],
            )
          ],
        ),
      ),
    );
  }
}

FloatingActionButton 介绍

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

属性名称属性值
child子视图,一般为 Icon,不推荐使用文字
tooltipFAB 被长按时显示,也是无障碍功能
backgroundColor背景颜色
elevation未点击的时候的阴影
hignlightElevation点击时阴影值,默认 12.0
onPressed点击事件回调
shape可以定义 FAB 的形状等
mini是否是 mini 类型默认 false

常用表单介绍

Flutter 中常见的表单有 TextField 单行文本框,TextField 多行文本框、CheckBox、Radio、SwitchCheckboxListTile、RadioListTile、SwitchListTile、Slide.

TextField 文本框组件

TextField 表单常见属性:

属性描述
maxLines设置此参数可以把文本框改为多行文本框
onChanged文本框改变的时候触发的事件
decorationhintText 类似 html 中的 placeholder
border 配置文本框边框 OutlineInputBorder 配合使用
labelText lable 的名称
labelStyle 配置 lable 的样式
obscureTextobscureText
controllercontroller 结合 TextEditingController()可以配置表单默认显示的内容
TextField(
        maxLines: 10,
//      obscureText: true,
        decoration:
        InputDecoration(
            hintText: "密码框",
            border: OutlineInputBorder()
        ),
      )d
  var _username = TextEditingController();

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _username.text = '这是文本框初始值';
  }
TextField(
          controller: _username,
          onChanged: (value) {
//            print(value);
            setState(() {
              this._username.text = value;
            });
          },
          decoration: InputDecoration(
            hintText: "请输入你的内容",
          ),
        )

Checkbox、CheckboxListTile 多选框组件

Checkbox 常见属性:

属性描述
valuetrue 或者 false
onChanged改变的时候触发的事件
activeColor选中的颜色、背景颜色
checkColor选中的颜色、Checkbox 里面对号的颜色

CheckboxListTile 常见属性:

属性描述
valuetrue 或者 false
onChanged改变的时候触发的事件
activeColor选中的颜色、背景颜色
title标题
subtitle二级标题
secondary配置图标或者图片
selected选中的时候文字颜色是否跟着改变
Checkbox(
          value: _isSelected,
          onChanged: (v) {
            print(v);
            setState(() {
              this._isSelected = v;
            });
          },
          activeColor: Colors.red,
          checkColor: Colors.blue,
        )d
CheckboxListTile(
          value: _isSelected,
          title: Text("这是一个标题"),
          subtitle: Text("这是二级标题"),
          onChanged: (v) {
            setState(() {
              this._isSelected = v;
            });
          },
          activeColor: Colors.red,
          secondary:
              Image.network("https://www./images/flutter/1.png"),
          selected: _isSelected,
        )

Radio、RadioListTile 单选按钮组件

Radio 常用属性:

属性描述
value单选的值
onChanged改变时触发
activeColor选中的颜色、背景颜色
groupValue选择组的值

RadioListTile 常用属性:

属性描述
valuetrue 或者 false
onChanged改变的时候触发的事件
activeColor选中的颜色、背景颜色
title标题
subtitle二级标题
secondary配置图标或者图片
groupValue选择组的值
int _groupValue=1;
Radio(
              value: 0,
              onChanged: (v) {
                setState(() {
                  this._groupValue = v;
                });
              },
              activeColor: Colors.red,
              groupValue: _groupValue,
            ),
Radio(
              value: 1,
              onChanged: (v) {
                setState(() {
                  this._groupValue = v;
                });
              },
              activeColor: Colors.red,
              groupValue: _groupValue,
            )
int _groupValue = 1;

  _handelChange(v) {
    setState(() {
      _groupValue = v;
    });
  }

RadioListTile(
              value: 1,
              title: Text("nodejs 视频教程"),
              subtitle: Text("egg.js 视频教程"),
              secondary:
                  Image.network("https://www./images/flutter/1.png"),
              groupValue: _groupValue,
              onChanged: _handelChange,
            ),
            Divider(),
            RadioListTile(
              value: 0,
              title: Container(
                height: 60,
                child: Text("这是文本"),
                color: Colors.red,
              ),
              subtitle: Text("egg.js 视频教程"),
              secondary:
                  Image.network("https://www./images/flutter/1.png"),
              groupValue: _groupValue,
              onChanged: _handelChange,
            )

开关 Switch

属性描述
value单选的值
onChanged改变时触发
activeColor选中的颜色、背景颜色

日期和时间戳

日期转化成时间戳:

 var now = new DateTime.now();
 print(now.millisecondsSinceEpoch);//单位毫秒,13 位时间戳

时间戳转化成日期:

var now = new DateTime.now();
var a=now.millisecondsSinceEpoch; //时间戳

print(DateTime.fromMillisecondsSinceEpoch(a));

第三方库 date_format 的使用

文档:https://v/packages/date_format

调用自带日期组件和时间组件

日期组件:

var _datetime = DateTime.now();

  _showDatePicker() async {
    var date = await showDatePicker(
        context: context,
        initialDate: _datetime,
        firstDate: DateTime(1900),
        lastDate: DateTime(2050));
    if (date == null) return;
    print(date);
    setState(() {
      _datetime = date;
    });
  }

时间组件:

var _time = TimeOfDay(hour: 9, minute: 20);

  _showTimePicker() async {
    var time = await showTimePicker(context: context, initialTime: _time);
    if (time == null) return;
    print(time);
    setState(() {
      this._time = time;
    });
  }
Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              InkWell(
                child: Row(
                  children: <Widget>[
                    Text("${formatDate(_datetime, [yyyy, '-', mm, '-', dd])}"),
                    Icon(Icons.arrow_drop_down)
                  ],
                ),
                onTap: _showDatePicker,
              ),
              InkWell(
                child: Row(
                  children: <Widget>[
                    Text("${this._time.format(context)}"),
                    Icon(Icons.arrow_drop_down)
                  ],
                ),
                onTap: _showTimePicker,
              )
            ],
          )
        ],
      )

调用自带日期组件和时间组件改为中文

http://bbs./topic/5cfb2a12f322340b2c90e764

调用第三方时间组件

https://v/packages/flutter_cupertino_date_picker

懒得记了。。。

轮播图组件

地址:https://v/packages/flutter_swiper

import 'package:flutter/material.dart';
import 'package:flutter_swiper/flutter_swiper.dart';

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

  _SwiperPageState createState() => _SwiperPageState();
}

class _SwiperPageState extends State<SwiperPage> {
  List<Map> list = [
    {"url": "https://www./images/flutter/1.png"},
    {"url": "https://www./images/flutter/2.png"},
    {"url": "https://www./images/flutter/3.png"}
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('轮播图组件演示'),
      ),
      body: Column(
        children: <Widget>[
          Container(
            width: double.infinity,
            child: AspectRatio(
              aspectRatio: 16 / 9,
              child: new Swiper(
                itemBuilder: (BuildContext context, int index) {
                  return new Image.network(
                    this.list[index]["url"],
                    fit: BoxFit.fill,
                  );
                },
                itemCount: list.length,
                pagination: new SwiperPagination(),
                autoplay: true,
// control: new SwiperControl(),
              ),
            ),
          )
        ],
      ),
    );
  }
}

一、AlertDialog

var alertRel = await showDialog(
        context: context,
        builder: (context) {
          return AlertDialog(
            title: Text("提示!"),
            content: Text("确定要删除吗"),
            actions: <Widget>[
              FlatButton(
                child: Text("取消"),
                onPressed: () {
                  Navigator.pop(context, 'Cancle');
                },
              ),
              FlatButton(
                child: Text("确定"),
                onPressed: () {
                  Navigator.pop(context, 'Ok');
                },
              )
            ],
          );
        });

二、SimpleDialog

var simpleRel = await showDialog(
        context: context,
        builder: (BuildContext context) {
          return SimpleDialog(
            title: Text("select 单选按钮框"),
            children: <Widget>[
              SimpleDialogOption(
                child: Text("Option A"),
                onPressed: () {
                  Navigator.pop(context, 'Option A');
                },
              ),
              Divider(),
              SimpleDialogOption(
                child: Text("Option B"),
                onPressed: () {
                  Navigator.pop(context, 'Option B');
                },
              ),
              Divider(),
              SimpleDialogOption(
                child: Text("Option C"),
                onPressed: () {
                  Navigator.pop(context, 'Option C');
                },
              )
            ],
          );
        });

三、showModalBottomSheet

var actionSheet = await showModalBottomSheet(
        context: context,
        builder: (builder) {
          return Container(
            height: 200, //高度不设置显示一半
            child: Column(
              children: <Widget>[
                ListTile(
                  title: Text("分享 A"),
                  onTap: () {
                    Navigator.pop(context, 'A');
                  },
                ),
                ListTile(
                  title: Text("分享 B"),
                  onTap: () {
                    Navigator.pop(context, 'B');
                  },
                ),
                ListTile(
                  title: Text("分享 C"),
                  onTap: () {
                    Navigator.pop(context, 'C');
                  },
                )
              ],
            ),
          );
        });

四、showToast

https://v/packages/fluttertoast

Fluttertoast.showToast(
        msg: "This is Short Toast",
        toastLength: Toast.LENGTH_SHORT,
        timeInSecForIos: 1);

自定义Dialog

自定义 Dialog 对象,需要继承 Dialog 类,尽管 Dialog 提供了 child 参数可以用来写视图界面,但是往往会达不到我们想要的效果,因为默认的 Dialog 背景框是满屏的。如果我们想完全定义界面,就需要重写 build 函数。

import 'package:flutter/material.dart';

class LoadingDialog extends Dialog {
  final String text;

  LoadingDialog(this.text);

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new Material(
//创建透明层
      type: MaterialType.transparency, //透明类型 child: new Center(
      child: Container(
        width: 300,
        height: 200,
        color: Colors.white,
        child: Column(
          children: <Widget>[
            Padding(
              padding: EdgeInsets.all(10),
              child: Stack(
                children: <Widget>[
                  Align(
                    alignment: Alignment.center,
                    child: Text("关于我们"),
                  ),
                  Align(
                    alignment: Alignment.centerRight,
                    child: InkWell(
                      child: Icon(Icons.close),
                      onTap: () {
                        Navigator.pop(context);
                      },
                    ),
                  )
                ],
              ),
            ),
            Divider(),
            Column(
              children: <Widget>[
                Container(
                  height: 40,
                  child: Text(this.text),
                )
              ],
            )
          ],
        ),
      ),
    );
  }
}

定时器

import 'dart:async';

_showTimer(context) {
    var timer;
    timer = Timer.periodic(Duration(milliseconds: 1500), (t) {
      print('执行');
      Navigator.pop(context);
      t.cancel();
    });
  }

定时器结合 Dialog

import 'dart:async';

import 'package:flutter/material.dart';

class LoadingDialog extends Dialog {
  final String text;

  LoadingDialog(this.text);

  _showTimer(context) {
    var timer;
    timer = Timer.periodic(Duration(milliseconds: 1500), (t) {
      print('执行');
      Navigator.pop(context);
      t.cancel();
    });
  }

  @override
  Widget build(BuildContext context) {
    _showTimer(context);
    return Material(
      //创建透明层
      type: MaterialType.transparency, //透明类型
      child: Center(
        child: Container(
          width: 300,
          height: 200,
          color: Colors.white,
          child: Column(
            children: <Widget>[
              Padding(
                padding: EdgeInsets.all(10),
                child: Stack(
                  children: <Widget>[
                    Align(
                      alignment: Alignment.center,
                      child: Text("关于我们"),
                    ),
                    Align(
                      alignment: Alignment.centerRight,
                      child: InkWell(
                        child: Icon(Icons.close),
                        onTap: () {
                          Navigator.pop(context);
                        },
                      ),
                    )
                  ],
                ),
              ),
              Divider(),
              Column(
                children: <Widget>[
                  Container(
                    height: 40,
                    child: Text(this.text),
                  )
                ],
              )
            ],
          ),
        ),
      ),
    );
  }
}

JSON 字符串和 Map 类型的转换(小项目)

(小项目直接转,大项目用模型类,笔记在下面)

import 'dart:convert';

var mapData = {"name": "张三", "age": "20"};
var strData = '{"name":"张三","age":"20"}';
print(json.encode(mapData)); //Map转换成Json字符串
print(json.decode(strData)); //Json 字符串转化成 Map 类型

使用 http 库进行网络请求

请参考官方文档:https://v/packages/http

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

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

  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String _news = '';

  //请求数据
  _getData() async {
    var apiUrl = "http://127.0.0.1:8080/";

    var result = await http.get(apiUrl);
    if (result.statusCode == 200) {
      // print(json.decode(result.body));
      setState(() {
        this._news = json.decode(result.body)["msg"];
      });
    } else {
      print(result.statusCode);
    }
  }

  //提交数据
  _postData() async {
    var apiUrl = "http://127.0.0.1:8080/x";

    var result = await http.post(apiUrl, body: {'username': '张三', 'age': '20'});
    if (result.statusCode == 200) {
      print(json.decode(result.body));
    } else {
      print(result.statusCode);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(this._news),
          RaisedButton(
            child: Text('Get请求数据'),
            onPressed: _getData,
          ),
          SizedBox(height: 20),
          RaisedButton(
            child: Text('Post提交数据'),
            onPressed: _postData,
          ),
          SizedBox(height: 20),
          RaisedButton(
            child: Text('Get请求数据、渲染数据演示demo'),
            onPressed: () {
              Navigator.pushNamed(context, '/http');
            },
          ),
          SizedBox(height: 20),
        ],
      ),
    );
  }
}

map遍历,外层套ListView组件

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

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

  _HttpDemoState createState() => _HttpDemoState();
}

class _HttpDemoState extends State<HttpDemo> {
  List _list = [];

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    this._getData();
  }

  _getData() async {
    var apiUrl = "http://a./api/productlist";
    var result = await http.get(apiUrl);
    if (result.statusCode == 200) {
      print(result.body);
      setState(() {
        this._list = json.decode(result.body)["result"];

        /*
      {
        "result": [{
            "_id": "5ac0896ca880f20358495508",
            "title": "精选热菜",
            "pid": "0",
          }, {
            "_id": "5ac089e4a880f20358495509",
            "title": "特色菜",
            "pid": "0",
            
          }
        ]
      }

      */
      });
    } else {
      print("失败${result.statusCode}");
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("请求数据Demo"),
        ),
        body: this._list.length > 0
            ? ListView(
                children: this._list.map((value) { //map遍历,外层套ListView组件
                  return ListTile(
                    title: Text(value["title"]),
                  );
                }).toList(),
              )
            : Text("加载中..."));
  }
}

ListView.builder

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

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

  _HttpDemoState createState() => _HttpDemoState();
}

class _HttpDemoState extends State<HttpDemo> {
  List _list = [];

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    this._getData();
  }

  _getData() async {
    var apiUrl = "http://a./api/productlist";
    var result = await http.get(apiUrl);
    if (result.statusCode == 200) {
      print(result.body);
      setState(() {
        this._list = json.decode(result.body)["result"];

        /*
      {
        "result": [{
            "_id": "5ac0896ca880f20358495508",
            "title": "精选热菜",
            "pid": "0",
          }, {
            "_id": "5ac089e4a880f20358495509",
            "title": "特色菜",
            "pid": "0",
            
          }
        ]
      }

      */
      });
    } else {
      print("失败${result.statusCode}");
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("请求数据Demo"),
        ),
        body: this._list.length > 0
            ? ListView.builder(
                itemCount: this._lidst.length,
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text("${this._list[index]["title"]}"),
                  );
                },
              )
            : Text("加载中..."));
  }
}

Dio 库

dio 是一个强大的 Dart Http 请求库,支持 Restful API、FormData、拦截器、请求取消、Cookie 管理、文件上传/下载、超时、自定义适配器等…

https://v/packages/dio

https://github.com/flutterchina/dio/blob/master/README-ZH.md

不做栗子,依官方最新文档栗子为准

下拉刷新和上拉分页

在 Flutter 官方 sdk 中给我们提供了下拉刷新的组件 RefreshIndicator。但是没有提供上拉分页加载更多的组件。但是在 Flutter ListView 中有一个ScrollController 属性,它就是专门来控制 ListView 滑动事件,在这里我们可以根据 ListView 的位置来判断是否滑动到了底部来做加载更多的处理。

Api 接口:http://www./appapi.php?a=getPortalList&catid=20&page=1

下拉刷新

@override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("请求数据 Dio Demo"),
        ),
        body: this._list.length > 0
            ? RefreshIndicator(
                onRefresh: _onRefresh,
                child: ListView.builder(
                    itemCount: this._list.length,
                    itemBuilder: (context, index) {
                      return ListTile(title: Text(this._list[index]["title"]));
                    }))
            : Text("加载中..."));
  }

  Future<void> _onRefresh() async {
    print('执行刷新');
  }

上拉分页加载更多

_scrollController.position.pixels 滚动的距离

_scrollController.position.maxScrollExtent 总距离

核心代码

ScrollController _scrollController = ScrollController(); //listview 的控制器
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    this._getData();
    
    //监听滚动条事件
    _scrollController.addListener(() {
      if (_scrollController.position.pixels >
          _scrollController.position.maxScrollExtent - 20) {
        print("滚动到了最底部");
        _getData();
      }
    });
  }

@override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    _scrollController.dispose(); //不用了砍掉,提高性能
  }

ListView.builder(
                  itemCount: this._list.length,
                  controller: _scrollController, //注意
                  itemBuilder: (context, index) {
                    
                  }
                )

完整代码

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'dart:convert';

class NewsPage extends StatefulWidget {
  @override
  _NewsPageState createState() => _NewsPageState();
}

class _NewsPageState extends State<NewsPage> {
  ScrollController _scrollController = ScrollController(); //listview 的控制器

  List _list = [];
  int _page = 1;
  bool isLoading = true; //是否正在加载数据
  
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    this._getData();

    //监听滚动条事件
    _scrollController.addListener(() {
      if (_scrollController.position.pixels >
          _scrollController.position.maxScrollExtent - 20) {
        print("滑动到了最底部");
        _getData();
      }
    });
  }
  
  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    _scrollController.dispose(); //不用了砍掉,提高性能
  }

  _getData() async {
    String apiUrl =
        "http://www./appapi.php?a=getPortalList&catid=20&page=${this._page}";
    Response result = await Dio().get(apiUrl);
    var res = json.decode(result.data)["result"];

//    print(json.decode(result.data)["result"]);
    setState(() {
      this._list.addAll(res);
      this._page++;
    });
    //判断是否是最后一页
    if (res.length < 20) {
      setState(() {
        this.isLoading = false;
      });
    }
  }

  Widget _getMoreWidget() {
    if (isLoading) {
      return Center(
        child: Padding(
          padding: EdgeInsets.all(10.0),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              Text(
                '加载中...',
                style: TextStyle(fontSize: 16.0),
              ),
              CircularProgressIndicator(
                strokeWidth: 1.0,
              )
            ],
          ),
        ),
      );
    } else {
      return Center(
        child: Text("--我是有底线的--"),
      );
    }
  }

  //下拉刷新
  Future<void> _onRefresh() async {
    print("执行刷新");
    this._getData();
    await Future.delayed(Duration(seconds: 3), () {
      print("refresh");
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("请求数据 Dio Demo"),
      ),
      body: this._list.length > 0
          ? RefreshIndicator(
              onRefresh: _onRefresh,
              child: ListView.builder(
                controller: _scrollController,
                itemCount: this._list.length,
                itemBuilder: (context, index) {
                  if (index == this._list.length - 1) {
                    return Column(
                      children: <Widget>[
                        ListTile(
                          title: Text(this._list[index]["title"], maxLines: 1),
                          onTap: () {
                            Navigator.pushNamed(context, '/newsContent');
                          },
                        ),
                        Divider(),
                        _getMoreWidget()
                      ],
                    );
                  } else {
                    return Column(
                      children: <Widget>[
                        ListTile(
                          title: Text(this._list[index]["title"], maxLines: 1),
                          onTap: () {
                            Navigator.pushNamed(context, '/newsContent');
                          },
                        ),
                        Divider()
                      ],
                    );
                  }
                },
              ),
            )
          : _getMoreWidget(),
    );
  }
}

解决请求重复问题

//解决重复请求的问题
  bool flag=true;
  
  @override
  void initState() {
    super.initState();
    _getData();

    //监听滚动条滚动事件
    _scrollController.addListener((){
        //_scrollController.position.pixels //获取滚动条滚动的高度
        //_scrollController.position.maxScrollExtent  //获取页面高度 
        if(_scrollController.position.pixels>_scrollController.position.maxScrollExtent-20){
          if(this.flag && this._hasMore){ //如果已经请求就不再获取数据
            _getData();
          }
        }
    });
  }


@override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    _scrollController.dispose(); //不用了砍掉,提高性能
  }

//获取数据
  _getData() async {
    // 请求数据之前置为false
    setState(() {
     this.flag=false; 
    });

    var api ='url';
    var result = await Dio().get(api);
    。。。
      // 数据请求完成之后置为true
       setState(() {
        。。。
        this.flag=true; 
      });   
  }

滚动条回到顶部

//回到顶部
_scrollController.jumpTo(0);

参考代码

  //导航改变的时候触发
  _subHeaderChange(id) {
    if (id == 4) {
      _scaffoldKey.currentState.openEndDrawer();
      setState(() {
        this._selectHeaderId = id;
      });
    } else {
      setState(() {
        this._selectHeaderId = id;
        this._sort ="${this._subHeaderList[id - 1]["fileds"]}_${this._subHeaderList[id - 1]["sort"]}";

        //重置分页
        this._page = 1;
        //重置数据
        this._productList = [];
        //改变sort排序
        this._subHeaderList[id - 1]['sort'] =
            this._subHeaderList[id - 1]['sort'] * -1;
        //回到顶部
        _scrollController.jumpTo(0);
        //重置_hasMore
        this._hasMore = true;
        //重新请求
        this._getProductListData();
      });
    }
  }

实现一个简单的新闻 APP

涉及的 api 接口:

新闻列表: http://www./appapi.php?a=getPortalList&catid=20&page=1

新闻详情:http://www./appapi.php?a=getPortalArticle&aid=20

列表页

import 'package:cc/pages/NewsContent.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'dart:convert';

class NewsPage extends StatefulWidget {
  @override
  _NewsPageState createState() => _NewsPageState();
}

class _NewsPageState extends State<NewsPage> {
  ScrollController _scrollController = ScrollController(); //listview 的控制器

  List _list = [];
  int _page = 1;
  bool isLoading = true; //是否正在加载数据

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    this._getData();

    //监听滚动条事件
    _scrollController.addListener(() {
      if (_scrollController.position.pixels >
          _scrollController.position.maxScrollExtent - 20) {
        print("滑动到了最底部");
        _getData();
      }
    });
  }

  _getData() async {
    String apiUrl =
        "http://www./appapi.php?a=getPortalList&catid=20&page=${this._page}";
    Response result = await Dio().get(apiUrl);
    var res = json.decode(result.data)["result"];

//    print(json.decode(result.data)["result"]);
    setState(() {
      this._list.addAll(res);
      this._page++;
    });
    //判断是否是最后一页
    if (res.length < 20) {
      setState(() {
        this.isLoading = false;
      });
    }
  }

  Widget _getMoreWidget() {
    if (isLoading) {
      return Center(
        child: Padding(
          padding: EdgeInsets.all(10.0),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              Text(
                '加载中...',
                style: TextStyle(fontSize: 16.0),
              ),
              CircularProgressIndicator(
                strokeWidth: 1.0,
              )
            ],
          ),
        ),
      );
    } else {
      return Center(
        child: Text("--我是有底线的--"),
      );
    }
  }

  //下拉刷新
  Future<void> _onRefresh() async {
    print("执行刷新");
    this._getData();
    await Future.delayed(Duration(seconds: 3), () {
      print("refresh");
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("请求数据 Dio Demo"),
      ),
      body: this._list.length > 0
          ? RefreshIndicator(
              onRefresh: _onRefresh,
              child: ListView.builder(
                controller: _scrollController,
                itemCount: this._list.length,
                itemBuilder: (context, index) {
                  if (index == this._list.length - 1) {
                    return Column(
                      children: <Widget>[
                        ListTile(
                          title: Text(this._list[index]["title"], maxLines: 1),
                          onTap: () {
                            Navigator.pushNamed(context, '/newsContent');
                          },
                        ),
                        Divider(),
                        _getMoreWidget()
                      ],
                    );
                  } else {
                    return Column(
                      children: <Widget>[
                        ListTile(
                          title: Text(this._list[index]["title"], maxLines: 1),
                          onTap: () {
                            Navigator.of(context).push(MaterialPageRoute(
                                builder: (context) =>
                                    NewsContent(this._list[index]["aid"])));
                          },
                        ),
                        Divider()
                      ],
                    );
                  }
                },
              ),
            )
          : _getMoreWidget(),
    );
  }
}

详情页

import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:dio/dio.dart';

class NewsContent extends StatefulWidget {
  var aid;

  NewsContent(this.aid);

  createState() => _NewsContentState();
}

class _NewsContentState extends State<NewsContent> {
  List list = [];

  _getData() async {
    String apiUrl =
        "http://www./appapi.php?a=getPortalArticle&aid=${this.widget.aid}";
    Response response = await Dio().get(apiUrl);
    setState(() {
      this.list = json.decode(response.data)["result"];
    });
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    this._getData();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Scaffold(
        appBar: AppBar(
          title: Text(this.list.length > 0 ? this.list[0]["title"] : ""),
        ),
        body: ListView(
          children: <Widget>[
            Text(this.list.length > 0 ? this.list[0]["content"] : "")
          ],
        ),
      ),
    );
  }
}

解析 html

https://v/packages/flutter_html

鸡肋。。。只能解析部分HTML标签

WebView 组件 inappbrowser的使用

建议使用WebView_flutter官方库,inappbrowser翘辫子了

https://v/packages/flutter_inappbrowser

ios模拟器测试失败。。。

获取设备信息

https://v/packages/device_info

使用高德定位准备工作获取 key

1、申请成为开发者

2、创建应用配置获取 Key (参考教程演示)

https://lbs.amap.com/api/android-sdk/guide/create-project/get-key

实现用高德定位

https://v/packages/amap_location

image_picker 实现相机拍照和相册选择

https://v/packages/image_picker

/*拍照*/
  _takePhoto() async {
    var image = await ImagePicker.pickImage(source: ImageSource.camera);
    setState(() {
      _imgPath = image;
    });
  }

/*相册*/
  _openGallery() async {
    var image = await ImagePicker.pickImage(source: ImageSource.gallery);
    setState(() {
      _imgPath = image;
    });
  }

上传图片到服务器

https://v/packages/dio

Dio2.x和3.x代码不同,依Dio官方文档为准

//上传图片
  _uploadImage(File _imageDir) async {
    //注意:dio3.x版本为了兼容web做了一些修改,上传图片的时候需要把File类型转换成String类型,具体代码如下

    var fileDir = _imageDir.path;

    FormData formData = FormData.fromMap({
      "name": "zhangsna 6666666666",
      "age": 20,
      "sex": "男",
      "file": await MultipartFile.fromFile(fileDir, filename: "xxx.jpg")
    });
    var response =
        await Dio().post("http://jd./imgupload", data: formData);
    print(response);
  }

视频播放

video_player 官方库,支持flutter web

在 Flutter 里官方提供了一个 video_player 插件可以播放视频。但是 video_player 有一些局限性。没法控制底部播放进度等。 所以我们主要给大家讲解一个第三方的视频播放库chewie。chewie 是一个非官方的第三方视频播放组件,看起来好像是基于 HTML5 播放的组件。chewie 相对 video_player 来说,有控制栏和全屏的功能。Chewie 使用 video_player 引擎并将其包裹在友好的 Material 或 Cupertino UI 中!

https://v/packages/video_player

https://v/packages/chewie

iOS警告

chewie使用的视频播放器插件在iOS模拟器上不起作用。开发/测试期间必须使用iOS设备。

coffee 21:44:59
安利一下 flutter_ijkplayer 视频播放器,这两天试了很多个,还是这个解决了目前的问题

coffee 22:31:46
flutter_tencentplayer 腾讯云的ios无法播放rtmp协议的直播流
coffee 22:33:13
官方提供的video_player 没有ui,需要自己实现
coffee 22:34:47
chewie 包装了一层video_player提供了ui,我视频回放用的这个,费了好大力气才搞定使用原视频尺寸播放,今儿发现 flutter_ijkplayer 很好用,不过回放功能暂时不打算改了,好不容易调完

chewie 视频播放完整 demo

import 'package:flutter/material.dart';
import 'package:chewie/chewie.dart';
import 'package:video_player/video_player.dart';

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

  _ChewieVideoDemoState createState() => _ChewieVideoDemoState();
}

class _ChewieVideoDemoState extends State<ChewieVideoDemo> {
  VideoPlayerController videoPlayerController;
  ChewieController chewieController;

  @override
  void initState() {
// TODO: implement initState super.initState();
    videoPlayerController = VideoPlayerController.network(
        'http://vfx./Video/2019/02/04/mp4/190204084208765161.mp4');
    chewieController = ChewieController(
      videoPlayerController: videoPlayerController,
      aspectRatio: 3 / 2,
      autoPlay: true,
      looping: true,
    );
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('标题'),
      ),
      body: Center(
        child: Chewie(
          controller: chewieController,
        ),
      ),
    );
  }
}

检测网络

https://v/packages/connectivity

检测网络完整 demo

import 'package:flutter/material.dart';
import 'package:connectivity/connectivity.dart';

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

  _NetworkPageState createState() => _NetworkPageState();
}

class _NetworkPageState extends State<NetworkPage> {
  String _state;
  var _subscription;

  @override
  initState() {
    super.initState();
    _subscription = Connectivity()
        .onConnectivityChanged
        .listen((ConnectivityResult result) {
      // Got a new connectivity status!
      if (result == ConnectivityResult.mobile) {
        setState(() {
          _state = "手机网络";
        });
// I am connected to a mobile network. } else if (result == ConnectivityResult.wifi) {
        setState(() {
          _state = "Wifi 网络";
        });
// I am connected to a wifi network. }else{
        setState(() {
          _state = "没有网络";
        });
      }
    });
  }

  @override
  dispose() {
    super.dispose();
    _subscription.cancel();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("检测网络变化"),
      ),
      body: Text("${_state}"),
    );
  }
}

本地存储

https://v/packages/shared_preferences

注意:

如果SharedPreferences prefs = await SharedPreferences.getInstance();写在runapp()的外层,

要加上

本地存储里面常用的一些方法

1、设置值

SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(key, value);
prefs.setBool(key, value)
prefs.setDouble(key, value)
prefs.setInt(key, value)
prefs.setStringList(key, value)

2、获取值

SharedPreferences prefs = await SharedPreferences.getInstance();
var data=prefs.getString("name");

3、删除值

SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.remove(key); //删除指定键
prefs.clear();//清空键值对

本地存储完整 demo

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

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

  _StoragePageState createState() => _StoragePageState();
}

class _StoragePageState extends State<StoragePage> {
  _saveData() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setString("name", "张三");
// prefs.setBool(key, value)
// prefs.setDouble(key, value)
    // prefs.setInt(key, value)
// prefs.setStringList(key, value)
  }

  _getData() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    var data = prefs.getString("name");
    print(data);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("本地存储"),
      ),
      body: Center(
        child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
          RaisedButton(
            child: Text('保存数据'),
            onPressed: _saveData,
          ),
          SizedBox(height: 10),
          RaisedButton(
            child: Text('获取数据'),
            onPressed: _getData,
          )
        ]),
      ),
    );
  }
}

扫描二维码条形码插件

https://v/packages/barcode_scan

错误多多,修改大大,不做记录。。。用到再说

检测应用版本号、服务器下载文件以及实现 App 自动升级、安装

1、Android App 升级执行流程

1、获取本地版本号

2、请求服务器获取服务器版本号

3、本地版本和服务器版本不一致提示升级,弹窗提示用户是否更新

4、用户确定升级,调用文件传输方法下载 apk 文件

5、监听下载进度

6、下载完成打开 Apk 进行安装

注意:在 Ios 中没法直接下载安装,如果版本不一致直接跳转到 Ios 应用对应的应用市场就可以了。

配置版本号: (Flutter应用获取的不是这里的版本号,在pubspec.yaml文件)

<manifest android:hardwareAccelerated="true" android:versionCode="1" android:versionName="0.0.1" package="io.jdshop.demo" xmlns:android="http://schemas./apk/res/android">

2、升级 app 之前的准备工作配置权限

配置 AndroidMenifest.xml 文件

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

3、Android 升级 app 涉及的 API 库

插件名称描述插件地址
package_info检测版本号https://v/packages/package_info
path_provider获取文件存储路径https://v/packages/path_provider
flutter_downloaderflutter_downloaderhttps://v/packages/flutter_downloader
open_file打开文件插件https://v/packages/open_file

4、获取版本信息

https://v/packages/package_info

PackageInfo packageInfo = await PackageInfo.fromPlatform();
String appName = packageInfo.appName;
String packageName = packageInfo.packageName;
String version = packageInfo.version;
String buildNumber = packageInfo.buildNumber;
print("appName:${appName}");
print("packageName:${packageName}");
print("version:${version}");
print("buildNumber:${buildNumber}");

5、获取文件存储路径

https://v/packages/path_provider

Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;

Directory appDocDir = await getApplicationDocumentsDirectory();
String appDocPath = appDocDir.path;

var directory = await getExternalStorageDirectory();

String storageDirectory=directory.path;

print("tempPath:${tempPath}");

print("appDocDir:${appDocPath}");

print("StorageDirectory:${storageDirectory}");

6、下载文件

https://v/packages/flutter_downloader

final directory = await getExternalStorageDirectory();
    String _localPath = directory.path;

    final taskId = await FlutterDownloader.enqueue(
      url: "http://www./jdshop.apk",
      savedDir: _localPath,
      showNotification:
          true, // show download progress in status bar (for Android)
      openFileFromNotification:
          true, // click on notification to open downloaded file (for Android)

7、打开文件

https://v/packages/open_file

OpenFile.open("${_localPath}/jdshop.apk");

8、注意事项

1、服务器的 App 版本必须大于本地 App 版本

2、本地 App 和服务器 App 的包名称 签名必须一致,这样的话服务器的包才可以替换本地的包。

完整代码

import 'package:flutter/material.dart';

import 'package:package_info/package_info.dart';

import 'package:path_provider/path_provider.dart';
import 'dart:io';

import 'package:open_file/open_file.dart';

import 'package:flutter_downloader/flutter_downloader.dart';

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

  _AppVersionPageState createState() => _AppVersionPageState();
}

class _AppVersionPageState extends State<AppVersionPage> {
  @override
  void initState() {
    // TODO: implement initState

    super.initState();

    this._getPackageInfo();

    this._getAppPath();
  }

  //弹出Dialog
  _showDialog() async {
    var alertRel = await showDialog(
        context: context,
        builder: (context) {
          return AlertDialog(
            title: Text("更新APP提示!"),
            content: Text("发现新的版本,新版本修复了如下bug 是否更新!"),
            actions: <Widget>[
              FlatButton(
                child: Text("否"),
                onPressed: () {
                  Navigator.pop(context, 'Cancle');
                },
              ),
              FlatButton(
                child: Text("是"),
                onPressed: () {
                  Navigator.pop(context, 'Ok');
                },
              )
            ],
          );
        });
  }

  //获取版本号
  _getPackageInfo() async {
    PackageInfo packageInfo = await PackageInfo.fromPlatform();
    String appName = packageInfo.appName;
    String packageName = packageInfo.packageName;
    String version = packageInfo.version;
    String buildNumber = packageInfo.buildNumber;

    print("appName:${appName}");

    print("packageName:${packageName}");

    print("version:${version}");

    print("buildNumber:${buildNumber}");
  }

//获取路径
  _getAppPath() async {
    Directory tempDir = await getTemporaryDirectory();
    String tempPath = tempDir.path;

    Directory appDocDir = await getApplicationDocumentsDirectory();
    String appDocPath = appDocDir.path;

    var directory = await getExternalStorageDirectory();

    String storageDirectory = directory.path;

    print("tempPath:${tempPath}");

    print("appDocDir:${appDocPath}");

    print("StorageDirectory:${storageDirectory}");
  }

  //下载打开文件
  _downLoad() async {
    final directory = await getExternalStorageDirectory();
    String _localPath = directory.path;

    final taskId = await FlutterDownloader.enqueue(
      url: "http://www./jdshop.apk",
      savedDir: _localPath,
      showNotification:
          true, // show download progress in status bar (for Android)
      openFileFromNotification:
          true, // click on notification to open downloaded file (for Android)
    );

    FlutterDownloader.registerCallback((id, status, progress) {
      print(status);
      // code to update your UI
      print('1111111');
      print(progress);
    });

    //打开文件
    OpenFile.open("${_localPath}/jdshop.apk");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.arrow_downward),
        onPressed: _downLoad,
      ),
      appBar: AppBar(
        title: Text("app升级演示"),
      ),
      body: Text("app升级演示"),
    );
  }
}

调用 url_launcher 模块打开外部浏览器 打开外部应用 拨打电话 发送短信

1、Flutter url_launcher 模块

Flutter url_launcher 模块可以让我们实现打开外部浏览器、打开外部应用、发送短信、拨打电话等功能。

https://v/packages/url_launcher

2、Flutter url_launcher 模块的使用

import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';

class _UrlLauncherState extends State<UrlLauncher> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('UrlLauncher'),
      ),
      body: Center(
        child: Padding(
          padding: EdgeInsets.all(20),
          child: ListView(
            children: <Widget>[
              RaisedButton(
                child: Text('打开外部浏览器'),
                onPressed: () async {
                  const url = 'https://';
                  if (await canLaunch(url)) {
                    await launch(url);
                  } else {
                    throw 'Could not launch $url';
                  }
                },
              ),
              SizedBox(
                height: 10,
              ),
              RaisedButton(
                child: Text('拨打电话'),
                onPressed: () async {
                  const tel = 'tel:10086';
                  if (await canLaunch(tel)) {
                    await launch(tel);
                  } else {
                    throw 'Could not launch $tel';
                  }
                },
              ),
              SizedBox(height: 10),
              RaisedButton(
                child: Text('发送短信'),
                onPressed: () async {
                  const tel = 'sms:10086';
                  if (await canLaunch(tel)) {
                    await launch(tel);
                  } else {
                    throw 'Could not launch $tel';
                  }
                },
              ),
              SizedBox(height: 10),
              RaisedButton(
                child: Text('打开外部应用'),
                onPressed: () async {
                  // weixin://
                  const app = 'alipays://';
                  if (await canLaunch(app)) {
                    await launch(app);
                  } else {
                    throw 'Could not launch $app';
                  }
                },
              )
            ],
          ),
        ),
      ),
    );
  }
}

3、关于打开其他 app 请参考这个帖子

https://www./topic/5d0853733b57e317a4d0af01

Android 修改应用名称、应用图标、应用启动画面

1、Android 修改应用名称

Android 是在 android ▸ app ▸ src ▸ main▸ AndroidManifest.xml 中修改 android:label=“XXX”;

2、Android 修改应用图标

Android 在 android ▸ app ▸ src ▸ res ▸ mipmap 下面对应的文件夹中替换相应图片

3、Android 修改应用启动画面

Android 添加启动界面

打开文件 android/app/src/main/res/drawable/launch_background.xml

修改内容,打开注释了的代码 launch_image 那段。

<!-- <item>
        <bitmap
            android:gravity="center"
            android:src="@mipmap/launch_image" />
    </item> -->

里面的ic_launch.png是图标,启动画面添加launch_image.png,格式要求png

注意@mipmap/launch_image 就是你要设置的启动界面的图片资源名字,你要放置到对应的文件夹里面

密度代表分辨率
ldpi240 x 320
mdpi320 x 480
hdpi480 x 800
xhdpi720 x 1280
xxhdpi1080 x 1920
xxxhdpi3840×2160

竖向 ListView 嵌套横向 ListView ,以及ListView 嵌套 GridView

1、竖向 ListView 嵌套横向 ListView 注意事项:

在竖向 ListView 中嵌套横向 ListView 的时候要注意给横向 ListView 外层加一个容器,然后外层这个容器要设置高度,外层这个容器可以是 SizedBox ,也可以是 Container。

2、ListView 嵌套 GridView 注意事项:

由于 GridView 和 ListView 都是可以滚动的组件,所以嵌套的时候要注意把里面的组件改为不可滚动组件。

重要属性:

shrinkWrap: true, //解决无限高度问题

physics:NeverScrollableScrollPhysics(), //禁用滑动事件

不同终端屏幕适配问题

我写的代码还用适配???

JSON 序列化反序列化(模型类)

1、使用 dart:convert 手动序列化 JSON

2、模型类中序列化 JSON

小项目中使用 dart:convert 手动序列化 JSON 非常好,也非常快速。但是随着项目的增大,dart:convert 手动序列化 JSON 的话失去了大部分静态类型语言特性:类型安全、自动补全和最重要的编译时异常。这样一来,我们的代码可能会变得非常容易出错。

当我们访问 name 或 email 字段时,我们输入的很快,导致字段名打错了。但由于这个 JSON 在 map 结构中,所以编译器不知道这个错误的字段名。

为了解决上面的问题在大型项目中使用的更多的是在模型类中序列化 JSON。

JSON字符串和Map类型的转换 dart:convert手动序列化 JSON

import 'dart:convert';

    var mapData = {"name": "张三", "age": "20"};
    var strData = '{"name":"张三","age":"20"}';
    print(json.encode(mapData)); //Map转换成Json字符串
    print(json.decode(strData)); //Json 字符串转化成 Map 类型

在模型类中序列化 JSON

class FocusModel {
  String sId;
  String title;
  String status;
  String pic;
  String url;

  FocusModel({this.sId, this.title, this.status, this.pic, this.url});

  FocusModel.fromJson(Map<String, dynamic> json) {
    sId = json['_id'];
    title = json['title'];
    status = json['status'];
    pic = json['pic'];
    url = json['url'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['_id'] = this.sId;
    data['title'] = this.title;
    data['status'] = this.status;
    data['pic'] = this.pic;
    data['url'] = this.url;
    return data;
  }
}
var strData='{"_id":"59f6ef443ce1fb0fb02c7a43","title":"笔记本电脑","status":"1","pic":"public\\upload\\UObZahqPYzFvx_C9CQjU8KiX.png","url":"12"}';

var data=FocusModel.fromJson(strData);

可参考:https:///json/

json_to_dart 自动生成模型类

https://javiercbk./json_to_dart/

IndexedStack 保持页面状态

IndexedStack 和 Stack 一样,都是层布局控件, 可以在一个控件上面放置另一个控件,但唯一不同的是 IndexedStack 在同一时刻只能显示子控件中的一个控件,通过 Index 属性来设置显示的控件。

IndexedStack 来保持页面状态的优点就是配置简单。IndexedStack 保持页面状态的缺点就是不方便单独控制每个页面的状态。

body: IndexedStack(
        index: this._currentIndex,
        children: <Widget>[],
      ),

AutomaticKeepAliveClientMixin 保持页面状态

花里胡哨。。。

通过事件打开侧边栏

final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
return Scaffold( key:_scaffoldKey,
appBar: AppBar(
title: Text("商品列表"),
) )
Expanded(
flex: 1,
child: InkWell( onTap: () {
_scaffoldKey.currentState.openEndDrawer(); },
child: Text("筛选", textAlign: TextAlign.center), ),
)

修改主题样式

return MaterialApp(
debugShowCheckedModeBanner: false, // home: Tabs(),
initialRoute: '/', onGenerateRoute:onGenerateRoute, theme: ThemeData(
primaryColor: Colors.white ),
);

下拉菜单 showMenu

IconButton(
              icon: Icon(Icons.more_horiz),
              onPressed: () {
                showMenu(
                    context: context,
                    position: RelativeRect.fromLTRB(500, 76, 10, 0),
                    items: [
                      PopupMenuItem(
                        child: Row(
                          children: <Widget>[
                            Icon(Icons.home),
                            Container(
                              padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
                              child: Text("首页"),
                            )
                          ],
                        ),
                      ),
                      PopupMenuItem(
                        child: Row(
                          children: <Widget>[
                            Icon(Icons.search),
                            Container(
                              padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
                              child: Text("搜索"),
                            )
                          ],
                        ),
                      )
                    ]);
              })

弹出底部菜单

实际就是点击事件后,弹出showModalBottomSheet,参考diolog笔记

StatefulBuilder更新Flutter showDialog 、showModalBottomSheet 中的状态

参考:https://www./topic/5d202202403aa10564178c65

状态管理

通俗的讲:当我们想在多个页面(组件/Widget)之间共享状态(数据),或者一个页面(组件/Widget)中的多个子组件之间共享状态(数据),这个时候我们就可以用 Flutter 中的状态管理来管理统一的状态(数据),实现不同组件直接的传值和数据共享。

现在 Flutter 的状态管理方案很多,redux、bloc、state、provide、provider。

目前我们推荐使用 provider,这个是官方提供的状态管理解决方案。相比其他状态管理库使用起来比较方便。

provider库和flutter provide库

provider 是 Flutter 团队推出的状态管理模式。

官方地址为:https://v/packages/provider

注意:provider 和 provide 是两个库哦。Flutter 官方推荐使用的是 provider 哦,provider 是flutter 官方出的。provide 不是 Flutter 官方写的哦。

provider 的使用

(官方文档为准,builder关键字变creat)

1、配置依赖 provider: ^4.3.3
2、新建一个文件夹叫 provider,在 provider 文件夹里面放我们对于的状态管理类

3、在 provider 里面新建 Counter.dart
4、Counter.dart 里面新建一个类继承 minxins 的 ChangeNotifier 代码如下

import 'package:flutter/material.dart';

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

5、找到 main.dart 修改代码如下

import 'package:flutter/material.dart';
import 'routers/router.dart';
import 'package:provider/provider.dart';
import 'provider/Counter.dart';

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

// void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
  MyApp({Key key}) : super(key: key);

  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
        providers: [
          // Provider<Counter>.value(value: foo),
             ChangeNotifierProvider(create: (_) => Counter()), // provider4.x的写法全局监听
          //ChangeNotifierProvider(builder: (_) => Counter()),
        ],
        child: MaterialApp(
          // home: Tabs(),
          debugShowCheckedModeBanner: false,
          initialRoute: '/productContent',
          onGenerateRoute: onGenerateRoute,
          theme: ThemeData(
              // primaryColor: Colors.yellow
              primaryColor: Colors.white),
        ));
  }
}

6、获取值、以及设置值

import 'package:provider/provider.dart';
import '../../provider/Counter.dart';

Widget build(BuildContext context) {
  final counter = Provider.of<Counter>(context); // counter.init();//在build里面
  return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          counter.increment();
        },
      ),
      body: Text("counter 的值:${counter.count}"));
}

扩展栗子

import 'package:flutter/material.dart';

class Cart with ChangeNotifier {
  List _cartList = []; //状态
  // int _cartNum=0; //数量直接从数组中获取,不用在定义一个获取数量的方法

  int get cartNum => this._cartList.length; //数量直接从数组中获取,不用在定义一个获取数量的方法

  List get cartList => this._cartList;

  addData(value) {
    this._cartList.add(value);
    notifyListeners();
  }

  deleteData(value) {
    this._cartList.remove(value);
    notifyListeners();
  }
}

event_bus 事件广播 事件监听

花里胡哨。。。

MediaQuery.removePadding移除元素的pandding

通过MediaQuery.removePadding可以移除元素的pandding,需要注意要指定移除哪个方向的padding,例如移除上面的padding

MediaQuery.removePadding(
            removeTop: true,
            context: context,
            child: ,
)

瀑布流布局

https://v/packages/flutter_staggered_grid_view

new StaggeredGridView.countBuilder(
  crossAxisCount: 4,
  itemCount: 8,
  itemBuilder: (BuildContext context, int index) => new Container(
      color: Colors.green,
      child: new Center(
        child: new CircleAvatar(
          backgroundColor: Colors.white,
          child: new Text('$index'),
        ),
      )),
  staggeredTileBuilder: (int index) =>
      new StaggeredTile.count(2, index.isEven ? 2 : 1), //固定个数修改count()为fit(2)
  mainAxisSpacing: 4.0,
  crossAxisSpacing: 4.0,
)

Sliver牛逼!!!

return Scaffold(
      body: CustomScrollView(
        slivers: <Widget>[
          SliverAppBar(
//            title: Text("SliverAppBar"),
//            pinned: true,
            floating: true,
            expandedHeight: 200,
            flexibleSpace: FlexibleSpaceBar(
              title: Text("Hello Flutter".toUpperCase()),
              background: Image.network(
                "https://images./photo-1579964190836-13f5022f5c40?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60",
                fit: BoxFit.cover,
              ),
            ),
          ),
          SliverGrid(
              delegate: SliverChildBuilderDelegate((context, index) {
                return Container(
                  child: Center(
                    child: Text("$index"),
                  ),
                );
              }, childCount: 1000),
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2,
                  crossAxisSpacing: 8.0,
                  mainAxisSpacing: 8.0))
        ],
      ),
    );

适配夜间模式

简单做法:

直接设置模式

theme: ThemeData.dark(),

自动切换模式

darkTheme: ThemeData.dark()

正片:

夜间模式(Dark Mode),也被称为暗黑模式或深色模式,是一种高对比度,或者反色模式的显示模式,开启之后在夜间可以缓解疲劳,更易于阅读,同时也能在一定程度上达到省电的效果。

夜间模式跟随系统

使用MaterialAppdarkTheme选项,可以很方便地适配跟随系统的DarkMode:

MaterialApp( 
    theme: ThemeData( 
        brightness: Brightness.light, 
        primaryColor: Colors.blue, 
    ), 
    darkTheme: ThemeData( 
        brightness: Brightness.dark, 
    ),
);

也可直接写做

darkTheme: ThemeData.dark()
  • 这种方式是自动跟随iOS/Android的系统设置来切换的,无需用户再单独设置

手动开启夜间模式

上述的跟随系统自动切换暗黑模式的体验可能并不是很好,比如用户不喜欢夜间模式或者App的夜间模式配色适配并不是很好,这就会导致用户无法手动控制app的夜间模式或者只能关闭系统的设置。因此我们可以增加手动控制以及跟随系统的选项,让用户选择是否开启以及开启的方式。

保存用户配置

在flutter中可以使用shared_preferences来保存用户的配置数据,具体使用方法详见:shared_preferences使用

状态管理

主题的手动切换是影响全局的,如果通过常规的数据流向很难做到。常见的几种状态管理:

  • InheritedWidget
  • Scoped model
  • BLoC
  • Redux
  • Provider

Provider是Google I/O 2019大会宣布的现在官方推荐的状态管理方式,我们需要在设置页里面通过用户设置,把变更状态共享给其他Widget,这里采用Provider这种方式来实现状态共享。

通用夜间模式Provider Model类

import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';

class DarkModeModel with ChangeNotifier {
  /// 夜间模式 0: 关闭 1: 开启 2: 随系统
  int _darkMode;

  static const Map<int, String> darkModeMap = {
    0: "关闭",
    1: "开启",
    2: "跟随系统"
  };

  static const String STORE_KEY = 'darkMode';

  SharedPreferences _prefs;

  int get darkMode => _darkMode;

  DarkModeModel() {
    _init();
  }

  void _init() async {
    this._prefs = await SharedPreferences.getInstance();
    int localMode = this._prefs.getInt(STORE_KEY);
    changeMode(localMode ?? 0);
  }

  void changeMode(int darkMode) async {

    _darkMode = darkMode;

    notifyListeners();

    SharedPreferences prefs = this._prefs ?? SharedPreferences.getInstance();

    await prefs.setInt(STORE_KEY, darkMode);
  }
}

MaterialApp修改

如果手动控制是否开启夜间模式,可以设置MaterialApptheme选项为ThemeData.dark()

theme: ThemeData.dark()

因为需要同时保留随系统自动切换与手动切换,而darkTheme选项和theme又有冲突,所以这里需要根据darkModeModel.darkMode的取值来渲染不同的MaterialApp,如果是手动模式再根据darkModeModel.darkMode的取值来渲染不同的theme

MultiProvider(
  providers: [
    ChangeNotifierProvider(builder: (_) => DarkModeModel())
  ],
  child: Consumer<DarkModeModel>(
    builder: (context, darkModeModel, _) {
      return darkModeModel.darkMode == 2 
        ? MaterialApp(
            title: '特效君',
            theme: ThemeData(
              primarySwatch: Colors.blue,
            ),
            darkTheme: ThemeData.dark(),
              home: MainPage(title: '特效君'),
            ) 
        : MaterialApp(
            title: '特效君',
            theme: darkModeModel.darkMode == 1 
              ? ThemeData.dark()
              : ThemeData(
                  primarySwatch: Colors.blue,
                ),
            home: MainPage(title: '特效君'),
          );
    },
  ),
)

这样我们就可以给用户提供自动跟随系统切换以及手动控制的选项了

登录注册案例

login.dart

class Login extends StatefulWidget {
  @override
  _LoginState createState() => _LoginState();
}

class _LoginState extends State<Login> {
  String _nickname = "";
  String _password = "";
  final _formKey = GlobalKey<FormState>();
  bool _autoValidate = false;

  _toLogin() async {
    Response response = await Dio().post("http://127.0.0.1:8080/auth",
        data: {"nickname": _nickname, "password": _password});
    LoginModel signUp = LoginModel.fromJson(response.data);

    if (signUp.code == 2000) {
      Scaffold.of(context).showSnackBar(SnackBar(content: Text(signUp.msg)));
    } else {
      Scaffold.of(context).showSnackBar(SnackBar(content: Text(signUp.msg)));
    }
  }

  void _submitForm() {
    if (_formKey.currentState.validate()) {
      _formKey.currentState.save();
      _toLogin();
    } else {
      setState(() {
        _autoValidate = true;
      });
    }
  }

  String _validateNickname(String value) {
    if (value.isEmpty) {
      return "<昵称>不能为空!";
    } else if (value.length > 20) {
      return "<昵称>不能大于20个字符!";
    }
    return null;
  }

  String _validatePassword(String value) {
    if (value.isEmpty) {
      return "<密码>不能为空!";
    } else if (value.length < 6) {
      return "<密码>不能小于6位!";
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        behavior: HitTestBehavior.translucent,
        onTap: () {
          FocusScope.of(context).requestFocus(FocusNode());
        },
        // 外层包一个Container方便设置内外边距,背景图片等
        child: Container(
          padding: EdgeInsets.all(40.0),
          child: ListView(
            children: <Widget>[
              // 登录LOGO
              Container(
                height: 100,
                decoration: BoxDecoration(
                  image: DecorationImage(
                    image: NetworkImage(
                      "https://c-ssl.duitang.com/uploads/item/201607/23/20160723143004_vyjTa.thumb.1000_0.jpeg",
                    ),
                  ),
                ),
              ),

              // 表单
              Form(
                key: _formKey,
                child: Column(
                  children: <Widget>[
                    // 昵称
                    TextFormField(
                      decoration: InputDecoration(
                        labelText: "昵称",
                        hintText: "请输入登录昵称",
                        helperText: '',
                      ),
                      onSaved: (String value) {
                        _nickname = value;
                      },
                      autovalidate: _autoValidate,
                      validator: _validateNickname,
                    ),

                    // 密码
                    TextFormField(
                      decoration: InputDecoration(
                        labelText: "密码",
                        hintText: "请输入登录密码",
                        helperText: '',
                      ),
                      obscureText: true,
                      onSaved: (String value) {
                        _password = value;
                      },
                      autovalidate: _autoValidate,
                      validator: _validatePassword,
                    ),

                    // 登录按钮
                    Container(
                      child: RaisedButton(
                        child: Text("登录"),
                        onPressed: _submitForm,
                        color: Theme.of(context).accentColor,
                        elevation: 0.0,
                      ),
                    ),
                  ],
                ),
              ),

              // 注册新账号
              Container(
                child: FlatButton(
                  onPressed: () {
                    Navigator.of(context).push(
                        MaterialPageRoute(builder: (context) => SignUp()));
                  },
                  child: Text("注册新账号"),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Flutter SliverAppBar 隐藏/显示导航栏

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
          headerSliverBuilder: _sliverBuilder,
          body: Center(
            child: Text('hahaha'),
          )),
    );
  }
}
 
List<Widget> _sliverBuilder(BuildContext context, bool innerBoxIsScrolled) {
  return <Widget>[
    SliverAppBar(
      centerTitle: true, //标题居中
      expandedHeight: 200.0, //展开高度200
      backgroundColor: Colors.tealAccent,
      floating: false, //不随着滑动隐藏标题
      pinned: false, //不固定在顶部
      flexibleSpace: FlexibleSpaceBar(
        centerTitle: true,
        background: Image.asset(
          "assets/pic.jpg", 
          fit: BoxFit.cover,
        ),
      ),
    )
  ];
}

骨架屏

https://v/packages/pk_skeleton

flutter 全屏背景图(包括appbar和状态栏)

class _HomeState extends State<Home> {
  @override
  Widget build(BuildContext context) {
    SelfAdapt _adapt = SelfAdapt.init(context);
    return Container(
      width: _adapt.width,
      height: _adapt.height,
      decoration: BoxDecoration(
        image: DecorationImage(
          image: NetworkImage('https://img./community/0372d195ac1cd55a8012062e3b16810.jpg'),
          fit: BoxFit.cover,
        ),
      ),
      child: Scaffold(
        backgroundColor: Colors.transparent,
        appBar: AppBar(
          elevation: 0,
          backgroundColor: Colors.transparent,
          // title: Text('首页'),
        ),
        drawer: MyDrawer(),
        body: Container(
          width: _adapt.width,
          padding: _adapt.setfromLTRB(100, 0, 100, 0),
          child: Text('hello'),
        ),
      ),
    );
  }
}

极光推送:

注册账户-----创建应用-----appkey—应用报名要一致

集成

1.下载依赖

Android:
在 /android/app/build.gradle 中添加下列代码:

android: {
  ....
  defaultConfig {
    applicationId "替换成自己应用 ID"
    ...
    ndk {
//选择要添加的对应 cpu 类型的 .so 库。
abiFilters 'armeabi', 'armeabi-v7a', 'x86', 'x86_64', 'mips', 'mips64', 'arm64-v8a',        
    }

    manifestPlaceholders = [
        JPUSH_PKGNAME : applicationId,
        JPUSH_APPKEY : "appkey", // NOTE: JPush 上注册的包名对应的 Appkey.
        JPUSH_CHANNEL : "developer-default", //暂时填写默认值即可.
    ]
  }    
}
 void initState() {
    super.initState();
    this.initJpush();

    // ///创建 JPush
    // JPush jpush = new JPush();

    // ///配置应用 Key
    // jpush.setup(
    //   appKey: "3cc9670e26b5b3e83cabe979",
    //   channel: "theChannel",
    //   production: false,

    //   /// 设置是否打印 debug 日志
    //   debug: true,
    // );
  }

或者init:

 void initState() {
    // TODO: implement initState
    super.initState();
    this.initJpush();
  }

  //监听极光推送 (自定义的方法)
  //https://github.com/jpush/jpush-flutter-plugin/blob/master/documents/APIs.md
  initJpush() async {
    JPush jpush = new JPush();
    //获取注册的id
    jpush.getRegistrationID().then((rid) {
      print("获取注册的id:$rid");
    });
    //初始化
    jpush.setup(
      appKey: "17d78ecf32c322db169a1d98",
      channel: "theChannel",
      production: false,
      debug: true, // 设置是否打印 debug 日志
    );

    //设置别名  实现指定用户推送
    jpush.setAlias("jg123").then((map) {
      print("设置别名成功");
    });

    try {
      //监听消息通知
      jpush.addEventHandler(
        // 接收通知回调方法。
        onReceiveNotification: (Map<String, dynamic> message) async {
          print("flutter onReceiveNotification: $message");
        },
        // 点击通知回调方法。
        onOpenNotification: (Map<String, dynamic> message) async {
          print("flutter onOpenNotification: $message");
        },
        // 接收自定义消息回调方法。
        onReceiveMessage: (Map<String, dynamic> message) async {
          print("flutter onReceiveMessage: $message");
        },
      );
    } catch (e) {
      print('极光sdk配置异常');
    }
  }

指定设备推送

sockio


var http=require('http');

var fs=require('fs');  /*fs内置的模块*/

var app=http.createServer(function(req,res){
    //加载静态页面
    fs.readFile('app.html',function(err,data){

        res.writeHead(200,{"Content-Type":"text/html;charset='utf-8'"});
        res.end(data);
    })
})

//引入socket.io
var io = require('socket.io')(app);

io.on('connection', function (socket) {

    console.log('服务器建立连接了');

    //服务器获取客户端广播的数据

    socket.on('addcart',function(data){

        console.log(data);

        //服务器给客户端发送数据

        //socket.emit();   /*谁给我发信息我把信息广播给谁*/

        //io.emit() ;   /*群发  给所有连接服务器的客户都广播数据*/


        //socket.emit('to-client','我是服务器的数据'+data.client);

        io.emit('to-client','我是服务器的数据'+data.client)

    })

});




app.listen(3000);


/*使用socket.io
1.安装

 npm install socket.io

 2、引入建立连接

 var io = require('socket.io')(app);


 io.on('connection', function (socket) {

    console.log('服务器建立连接了');
 });

3、在客户端 html里面引入js

 http://localhost:3000/socket.io/socket.io.js





* */

指纹

image-20210131110616915

 android:theme="@style/Theme.AppCompat"

image-20210131110317782

package com.xinxing.luckly_flutter

//import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity: FlutterFragmentActivity() {
}

FlutterIos中使用生物识别认证的一些配置
Info.plist中加入下面配置

NSFaceIDUsageDescriptionfaceid进行身份验证?

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多