重学flutter

文章目录

vscode插件

Flutter Widget Snippets
Awesome Flutter Snippets

1、省略new

runApp()是入口方法

void main(){
  runApp(Center( // 实例化的时候new可以省略   new Center( 
    child: Text(  // new Text(
      '你好',
      textDirection: TextDirection.ltr,
    ),
  ));
}

void main(){
  runApp(MyApp()); // 省略new  new MyApp()
}

2、所有和数字相关的都是double类型(浮点类型),要加.0

fontSize: 40.0, 

3、color用法

 // color: Colors.lightGreen
color: Color.fromRGBO(244, 233, 121, 0.8)

4、MaterialApp作为flutter的根组件来使用,里面用Scaffold组件来装饰,因为有appBar

去掉右侧图标

MaterialApp( 
      debugShowCheckedModeBanner: false,
      home: Tabs(), 
);

在这里插入图片描述

Scaffold(
  floatingActionButton: FloatingActionButton( //浮动按钮
    child: Text('返回'),
      onPressed: (){
        Navigator.of(context).pop(); // 返回
      },
  ),
  appBar: AppBar(
    title: Text('搜索页面'),
  ),
  body: Text('搜索内容页'),
);

5、快捷键

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

6、Container、TextStyle、BoxDecoration

Center( // 实例化的时候new可以省略
  child: Container(
    child: Text('我是一个文本我是一个文本我是一个文本我是一个文本我是一个文本',
      textAlign: TextAlign.left, 
     textDirection: TextDirection.ltr, 
      overflow: TextOverflow.ellipsis, // clip裁断,没有后面点点点
      maxLines: 2, // 显示多少行
      textScaleFactor: 1.2, // 字体方法两倍
      style:TextStyle(
        fontSize: 16.0, // 所有和数字相关的都是double类型(浮点类型),要加.0
        // color: Color.fromRGBO(244, 233, 121, 0.8)
        color: Colors.red,
        fontWeight: FontWeight.w800, // 字体粗细
        fontStyle: FontStyle.italic, // 倾斜
        decoration: TextDecoration.lineThrough,
        decorationColor: Colors.blue,
        decorationStyle: TextDecorationStyle.dashed,
        letterSpacing: 15.0 // 间距
      )
      ),
    height: 300.0,
    width: 300.0,
    alignment: AlignmentDirectional.center,
    decoration: BoxDecoration(
      color: Colors.yellow,
      border:Border.all(
        color: Colors.blue,
        width: 2.0,
      ),
      borderRadius:BorderRadius.all(Radius.circular(20))
    ),
    // padding: EdgeInsets.all(30), // 内边距
    margin: EdgeInsets.fromLTRB(10, 30, 60, 90), // 外边距
    // transform: Matrix4.translationValues(100, 0, 0) // x, y, z
    // transform: Matrix4.rotationZ(-0.3)  // 旋转
    // transform: Matrix4.skew(0, 0.2) // 倾斜
    transform: Matrix4.diagonal3Values(1.2, 1, 1) // 缩放 把x轴拉伸了
  )
);

在这里插入图片描述

7、图片组件

image.asset 本地图片
image.network 远程图片

注意:建议把图片放在container组件里面,比较好控制

Container(
   child: Image.network(
     'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg',
     // alignment: Alignment.bottomLeft, //图片显示方位
     // color: Colors.blue, // 图片颜色
     // colorBlendMode: BlendMode.luminosity, // 图片混合模式
     // fit: BoxFit.cover, // 充满整个容器,等比例拉伸图片,会裁剪
     // fit: BoxFit.fill, // 充满整个容器,会变形
     repeat: ImageRepeat.repeat, // 重复平铺
   ),
   width: 300,
   height: 300,
   decoration: BoxDecoration(
     color: Colors.yellow
   ),
)

在这里插入图片描述
圆形图片:
1)DecorationImage()组件

Container(
    width: 300,
    height: 300,
    decoration: BoxDecoration(
      color: Colors.yellow,
      borderRadius: BorderRadius.circular(150),
      image: DecorationImage(
        image: NetworkImage('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'),
        fit: BoxFit.cover
      )
    ),
)

2)ClipOval()组件 (推荐使用)

Container(
  child: ClipOval(
   child: Image.network(
      'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg',
      height: 300,
      width: 300,
      fit:BoxFit.cover
   ),
  ), 
)

在这里插入图片描述

8、引入本地图片

在这里插入图片描述
然后,打开 pubspec.yaml 声明一下添加的图片文件,注意要配置对
在这里插入图片描述

Container(
   child: ClipOval(
    child: Image.asset(
       'images/a.jpeg',
       height: 300,
       width: 300,
       fit:BoxFit.cover
    ),
   ), 
)

9、圆角的两种方式

borderRadius: BorderRadius.all(
    Radius.circular(150)
)

borderRadius: BorderRadius.circular(150)

10、ListTile

ListView(
      padding: EdgeInsets.all(10),
      children: <Widget>[
        ListTile(
          leading: Icon(Icons.settings, color: Colors.blue, size:30),
          title:Text('今日宜嗑 CP,宜秀恩爱',
            style: TextStyle(fontSize: 24),
          ),
          subtitle: Text('今年的情人节有点特殊,疫情之下,许多情侣硬是变成了「异地恋」。在这个不能出门不能见面的情人节,为各位知友贴心的准备了一种新过节方式'), // 二级标题
        ),
        ListTile(
          title:Text('今日宜嗑 CP,宜秀恩爱',
            style: TextStyle(fontSize: 24),
          ),
          subtitle: Text('今年的情人节有点特殊,疫情之下,许多情侣硬是变成了「异地恋」。在这个不能出门不能见面的情人节,为各位知友贴心的准备了一种新过节方式'), // 二级标题
          trailing: Icon(Icons.shop, color: Colors.blue,),
        ),
        ListTile(
          leading: Container(
            child: Image.network(
              'https://pic2.zhimg.com/80/v2-b5476d31a5cb86912152ebe84386264c_qhd.jpg',
              fit: BoxFit.cover,
            ),
            width: 100,
            height: 100,
            decoration: BoxDecoration(
              color: Colors.blue
            ),
          ),
          title:Text('今日宜嗑 CP,宜秀恩爱',
            style: TextStyle(fontSize: 24),
          ),
          subtitle: Text('今年的情人节有点特殊,疫情之下,许多情侣硬是变成了「异地恋」。在这个不能出门不能见面的情人节,为各位知友贴心的准备了一种新过节方式'), // 二级标题
        ),
        ListTile(
          leading: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg',),
          title:Text('今日宜嗑 CP,宜秀恩爱',
            style: TextStyle(fontSize: 24),
          ),
          subtitle: Text('今年的情人节有点特殊,疫情之下,许多情侣硬是变成了「异地恋」。在这个不能出门不能见面的情人节,为各位知友贴心的准备了一种新过节方式'), // 二级标题
        )
      ],
    );

在这里插入图片描述

11、ListView基础列表

注意:ListView没法再包含ListView
必须要有宽度,否则没生效

Container(
      height: 180,
      child: ListView(
      scrollDirection: Axis.horizontal,
      children: <Widget>[
        Container(
          width: 180,
          // height: 180,
          color: Colors.blue,
        ),
        Container(
          width: 180,
          // height: 180,
          color: Colors.yellow,
          child: ListView(
            children: <Widget>[
              Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'),
              Text('哈哈哈哈哈', textAlign:TextAlign.center)
            ],
          ),
        ),
        Container(
          width: 180,
          // height: 180,
          color: Colors.red,
        ),
        Container(
          width: 180,
          // height: 180,
          color: Colors.pink,
        )
      ],
    ),
    );

在这里插入图片描述

12、ListView动态列表

循环动态数据的两种方法:
1)先遍历好数据,接下来放到ListView里面
2)使用ListView.builder方法

先遍历好再放ListView

class HomeContent extends StatelessWidget{
  // 要求List数据必须是Widget组件,表示List返回的每一项必须是组件
  List<Widget> _getData(){
    return [
      ListTile(
        title: Text('我是一个列表')
      ),
      ListTile(
        title: Text('我是一个列表')
      ),
      ListTile(
        title: Text('我是一个列表')
      ),
      ListTile(
        title: Text('我是一个列表')
      )
    ];
  }

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: this._getData(),
    )
  }
}

优化成:

class HomeContent extends StatelessWidget{
  // 要求List数据必须是Widget组件,表示List返回的每一项必须是组件
  List<Widget> _getData(){
    List<Widget> list = new List();
    for(var i=0;i<20;i++){
      list.add(ListTile(
        title:Text('我是$i列表')
      ));
    }
    return list;
  }

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: this._getData(),
    );
  }
}

在这里插入图片描述

ListView.builder()

itemCount // 数据的长度
itemBuilder // list的方法

class HomeContent extends StatelessWidget{
  
  List list = new List();
  HomeContent(){ // 类初始化的时候增加数据
    for(var i=0;i<20;i++){
      this.list.add('我是第$i条');
    }
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: this.list.length,
      itemBuilder:(context,index){
        return ListTile(
          title:Text(this.list[index])
        );
      }
    );
  }
}

在这里插入图片描述
例子2:

List listData=[
  {
    'title':'哈哈哈哈哈哈哈1',
    'author':'xiaoming',
    'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'
  },
  {
    'title':'哈哈哈哈哈哈哈2',
    'author':'xiaoming',
    'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'
  },
  {
    'title':'哈哈哈哈哈哈哈3',
    'author':'xiaoming',
    'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'
  },
  {
    'title':'哈哈哈哈哈哈哈4',
    'author':'xiaoming',
    'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'
  }
];

class HomeContent extends StatelessWidget{
  
  // 自定义方法
  Widget _getListData(context, index){
    return ListTile(
      title: Text(listData[index]['title']),
      leading: Image.network(listData[index]['imageUrl']),
      subtitle: Text(listData[index]['author']),
    );
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: listData.length,
      itemBuilder:this._getListData
    );
  }
}

在这里插入图片描述

13、GridView网格组件

ListView 可以实现新闻列表布局
GridView 可以用在商品列表布局

实现网格布局的两种实现方式:
1)GridView.count
2)GridView.builder

crossAxisCount 控制多少列

注意:没法设置高度(设置高度也没有反应),可以用childAspectRatio来调整x和y轴的比例(宽度和高度的比例),也可以用wrap来实现

GridView.count

class HomeContent extends StatelessWidget{

  List<Widget> _getListData(){
    List<Widget> list = new List();
    for(var i=0;i<20;i++){
      list.add(Container(
        alignment: Alignment.center,
        child: Text(
          '这是第$i条数据',
          style: TextStyle(color: Colors.white, fontSize: 20),
        ),
        color: Colors.blue,
      ));
    }
    return list;
  }

  @override
  Widget build(BuildContext context) {
    return GridView.count(
      crossAxisCount: 3,
      crossAxisSpacing: 20.0, // 水平子部件的间距
      mainAxisSpacing: 20.0, // 垂直子部件的间距
      padding:EdgeInsets.all(10), // 整个模块的边距
      childAspectRatio: 0.7, // 用来设置高度,宽度和高度的比例
      children: this._getListData(),
    );
  }
}

在这里插入图片描述

垂直布局:ListView里面的子部件会自适应宽度,column不会

List listData=[
  {
    'title':'哈哈哈哈哈哈哈1',
    'author':'xiaoming',
    'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'
  },
  {
    'title':'哈哈哈哈哈哈哈2',
    'author':'xiaoming',
    'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'
  },
  {
    'title':'哈哈哈哈哈哈哈3',
    'author':'xiaoming',
    'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'
  },
  {
    'title':'哈哈哈哈哈哈哈4',
    'author':'xiaoming',
    'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'
  }
];

class HomeContent extends StatelessWidget{

  List<Widget> _getListData(){
    var tempList = listData.map((value){
      return Container(
        child: Column(
          children: <Widget>[
            Image.network(value['imageUrl']),
            Text(value['title'])
          ],
        ),
        decoration: BoxDecoration(
          border:Border.all(
            color: Color.fromRGBO(233, 233, 233, 1),
            width: 1
          )
        )
      );
    });

    return tempList.toList(); // 转换为数组
  }

  @override
  Widget build(BuildContext context) {
    return GridView.count(
      crossAxisCount: 2,
      crossAxisSpacing: 20.0, // 水平子部件的间距
      mainAxisSpacing: 10.0, // 垂直子部件的间距
      padding:EdgeInsets.all(10), // 整个模块的边距
      childAspectRatio: 1.2, // 用来设置高度,宽度和高度的比例
      children: this._getListData(),
    );
  }
}

在这里插入图片描述

GridView.builder

class HomeContent extends StatelessWidget{

  Widget _getListData(context, index){
     return Container(
        child: Column(
          children: <Widget>[
            Image.network(listData[index]['imageUrl']),
            Text(listData[index]['title'])
          ],
        ),
        decoration: BoxDecoration(
          border:Border.all(
            color: Color.fromRGBO(233, 233, 233, 1),
            width: 1
          )
        )
      );
  }

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      itemCount: listData.length,
      itemBuilder: this._getListData, 
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( // 注意,要在这个组件里设置
        crossAxisCount: 2,
        crossAxisSpacing: 10.0,
        mainAxisSpacing: 10.0
      ),
    );
  }
}

在这里插入图片描述

14、Paddiing 组件

Flutter 中很多 Widget 是没有 padding 属性,这时候就可以用padding组件

Padding(
  padding: EdgeInsets.fromLTRB(0, 0, 10, 0),
   child: GridView.count(
     crossAxisCount: 2,
     childAspectRatio: 1.7,
     children: <Widget>[
       Padding(
         padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
         child: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', fit:BoxFit.cover),
       ),
       Padding(
         padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
         child: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', fit:BoxFit.cover),
       ),
       Padding(
         padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
         child: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', fit:BoxFit.cover),
       ),
       Padding(
         padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
         child: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', fit:BoxFit.cover),
       ),
       Padding(
         padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
         child: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', fit:BoxFit.cover),
       ),
       Padding(
         padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
         child: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', fit:BoxFit.cover),
       ),
     ],
   ),
 );

在这里插入图片描述

15、Row水平布局组件

自定义按钮组件

class HomeContent extends StatelessWidget{

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 400.0,
      width: 400.0,
      color: Colors.pink,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          IconContainer(Icons.search, color: Colors.blue,),
          IconContainer(Icons.search, color: Colors.orange,),
          IconContainer(Icons.search, color: Colors.green,)
        ],
      ),
    );
  }
}

// 自定义按钮组件
class IconContainer extends StatelessWidget{
  
  // 哪些属性需要动态传入
  double size=32.0; // 指定默认值
  Color color=Colors.red;
  IconData icon;

  // 定义构造函数,可以传入实例化的时候传过来的数据
  // this.size 表示把值赋值给this,小括号{}里面是可选的
  IconContainer(this.icon,{this.color, this.size}); // 后面的括号里没内容,可以省略

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

}

在这里插入图片描述

16、Column垂直布局组件

ListView会铺满整个空间,column是多宽就多宽

class HomeContent extends StatelessWidget{

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 400.0,
      width: 400.0,
      color: Colors.pink,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          IconContainer(Icons.search, color: Colors.blue,),
          IconContainer(Icons.search, color: Colors.orange,),
          IconContainer(Icons.search, color: Colors.green,)
        ],
      ),
    );
  }
}

// 自定义按钮组件
class IconContainer extends StatelessWidget{
  
  // 哪些属性需要动态传入
  double size=32.0; // 指定默认值
  Color color=Colors.red;
  IconData icon;

  // 定义构造函数,可以传入实例化的时候传过来的数据
  // this.size 表示把值赋值给this,小括号{}里面是可选的
  IconContainer(this.icon,{this.color, this.size}); // 后面的括号里没内容,可以省略

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

}

在这里插入图片描述

17、Expanded组件

类似 Web 中的 Flex 布局,可以用在 Row 和 Column 布局中

Row(
    // mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    // crossAxisAlignment: CrossAxisAlignment.start,
    children: <Widget>[
      // Expanded(
      //   flex:1, // 占多少份
      //   child: IconContainer(Icons.search, color: Colors.blue,),
      // ),
      // 左侧固定宽度,右侧自适应
      IconContainer(Icons.search, color: Colors.blue,),
      Expanded(
        flex:1, // 占多少份
        child: IconContainer(Icons.search, color: Colors.orange,),
      ),
    ],
  );

在这里插入图片描述
在这里插入图片描述

18、Stack层叠组件(定位布局)

Center(
  child: Stack(
    // alignment: Alignment.center, // 让里面所有的元素都居中
    alignment: Alignment(-0.5, -1), // x, y轴 可以调整元素到任意位置
    children: <Widget>[
      Container(
        height: 400,
        width: 300,
        color: Colors.red,
      ),
      Text('我是一个文本',style: TextStyle(
        fontSize: 30,
        color: Colors.white
      ),) // 文本叠加在元素上面
    ],
  )
);

在这里插入图片描述

多个元素需要定位:可以结合Align或者Positiond来实现页面的定位布局

结合Align使用

Center(
      child: Container(
        height: 400,
        width: 300,
        color: Colors.red,
        child: Stack(
          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.send, size:60, color:Colors.white),
            )
          ],
        ),
      ),
    );

在这里插入图片描述

Container(
padding: EdgeInsets.all(ScreenAdapter.width(20)),
    child: Stack(
      children: <Widget>[
        Align(
          alignment: Alignment.centerLeft,
          child: Text('忘记密码'),
        ),
        Align(
          alignment: Alignment.centerRight,
          child: InkWell(
            onTap: (){
              Navigator.pushNamed(context, '/registerFirst');
            },
            child: Text('新用户注册'),
          ),
        )
      ],
    ),
  ),

在这里插入图片描述

结合Positiond使用

Center(
child: Container(
     height: 400,
     width: 300,
     color: Colors.red,
     child: Stack(
       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.send, size:60, color:Colors.white),
         )
       ],
     ),
   ),
 );

在这里插入图片描述

19、AspectRatio组件(自适应)

AspectRatio:设置调整子元素 child 的宽高比,是相对于父元素的,可以让子元素尽可能去铺满父元素
属性:aspectRatio(宽高比)、child(子组件)

AspectRatio(
  aspectRatio: 3.0/1.0, // 高度是宽度的三分之一
  child: Container(
    color: Colors.pink,
  ),
);

在这里插入图片描述

20、Card卡片组件

属性:margin(外边距)、child(子组件)、Shape(阴影)

ListView(
      children: <Widget>[
        Card(
          margin: EdgeInsets.all(10),
          child: Column(
            children: <Widget>[
              ListTile(
                title: Text('张三', style: TextStyle(fontSize: 28),),
                subtitle: Text('高级工程师'),
              ),
              ListTile(
                title: Text('电话:13688888888'),
              ),
              ListTile(
                title: Text('地址:广州市天河区xxxx'),
              ),
            ],
          ),
        )
        Card(...)
      ],
    );

在这里插入图片描述
例子2:

ListView(
      children: <Widget>[
        Card(
          margin: EdgeInsets.all(10),
          child: Column(
            children: <Widget>[
              AspectRatio(
                aspectRatio: 20/9,
                child: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg',fit: BoxFit.cover,),
              ),
              ListTile(
                leading:CircleAvatar(
                  backgroundImage: NetworkImage('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'),
                ),
                title: Text('标题标题标题标题标题'),
                subtitle: Text('描述描述描述描述描述'),
              )
            ],
          ),
        )
      ],
    );

在这里插入图片描述
动态渲染卡片

List listData=[
  {
    'title':'哈哈哈哈哈哈哈1',
    'des':'描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述',
    'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg',
  },
  {
    'title':'哈哈哈哈哈哈哈2',
    'des':'描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述',
    'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg',
  },
  {
    'title':'哈哈哈哈哈哈哈3',
    'des':'描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述',
    'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg',
  },
  {
    'title':'哈哈哈哈哈哈哈4',
    'des':'描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述',
    'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg',
  }
];

class HomeContent extends StatelessWidget{

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: listData.map((value){
        print(value);
        return Card(
          margin: EdgeInsets.all(10),
          child: Column(
            children: <Widget>[
              AspectRatio(
                aspectRatio: 20/9,
                child: Image.network(value['imageUrl'],fit: BoxFit.cover,),
              ),
              ListTile(
                leading:CircleAvatar(
                  backgroundImage: NetworkImage(value['imageUrl']),
                ),
                title: Text(value['title']),
                subtitle: Text(value['des'], maxLines: 2,overflow: TextOverflow.ellipsis,),
              )
            ],
          ),
        );
      }).toList(),
    );
  }
}

21、Wrap 组件实现流布局

跟Row、Column组件相似,但是Row和Column都是单行。
Wrap组件在mainAxis上空间不足时,则向crossAxis上去扩展显示。

class HomeContent extends StatelessWidget{

  @override
  Widget build(BuildContext context) {
    return Wrap(
      spacing: 10, // 元素与元素之间的边距
      runSpacing: 10, // 纵轴边距
      // direction: Axis.vertical,// 改变方向
      alignment: WrapAlignment.spaceAround, // 左右对齐 类似flex
      children: <Widget>[
        MyButton('第一季'),
        MyButton('第一季11111'),
        MyButton('第一季'),
        MyButton('第一季'),
        MyButton('第一季'),
        MyButton('第一季'),
        MyButton('第一季'),
        MyButton('第一季'),
        MyButton('第一季'),
        MyButton('第一季'),
        MyButton('第一季'),
      ],
    );
  }
}

class MyButton extends StatelessWidget{
  final String text;
  //把当前key传过去
  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: (){ },
    );
  }

}

在这里插入图片描述

22、StatefulWidget有状态组件

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

注意:
要在setState(() { });里面修改数据,跟react数据绑定有点相似

// 继承StatefulWidget抽象类,它里面有个抽象方法createState()
class HomeContent extends StatefulWidget {
  @override
  _HomeContentState createState() => _HomeContentState();
}

class _HomeContentState extends State<HomeContent> {
  int countNum=0;
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        SizedBox(height: 200,),
        Chip(
          label: Text('${this.countNum}'),
        ),
        SizedBox(height: 20,),
        RaisedButton(
          child: Text('按钮'),
          onPressed: (){
            setState(() { // 只有有状态组件里面才有,跟react数据绑定有点相似
              this.countNum++ ;             
            });
          },
        )
      ],
    );
  }
}

在这里插入图片描述

状态组件接收参数

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

class _TabsState extends State<Tabs>{
	int _currentIndex;
	_TabsState(index){
		this._currentIndex = index ; // 赋值
	}
}

Navigator.pushNamed(context, '/newscontent',arguments:{
   "aid":this._list[index]["aid"]
});

class NewsContent extends StatefulWidget {

  Map arguments;
  NewsContent({Key key,this.arguments}) : super(key: key);

  _NewsContentState createState() => _NewsContentState(this.arguments);
}

class _NewsContentState extends State<NewsContent> {
  Map arguments;
  List _list=[];
  
  _NewsContentState(this.arguments);

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

    this._getData();
  }
}

或者简化

Navigator.pushNamed(context, '/newscontent',arguments:{
   "aid":this._list[index]["aid"]
});

class NewsContent extends StatefulWidget {
  Map arguments;
  NewsContent({Key key,this.arguments}) : super(key: key);

  _NewsContentState createState() => _NewsContentState();
}

class _NewsContentState extends State<NewsContent> {
	var _content;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    print(widget.arguments);
    this._content = widget.arguments; // 间接获取上个类传过来的数据
  }
}

23、BottomNavigationBar自定义底部导航条(Scaffold 组件里的参数)

BottomNavigationBar是底部导航条,可以让我们定义底部Tab切换
1)点击的时候改变currentIndex
2)body来显示当前页面(根据currentIndex和页面列表,可以判断对应到哪个页面)

class Tabs extends StatefulWidget {
  @override
  _TabsState createState() => _TabsState();
}

// 点击的时候改变currentIndex
class _TabsState extends State<Tabs> {
  int _currentIndex = 0;
  List _pageList=[
    HomePage(),
    CategoryPage(),
    MyPage(),
  ];
  @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){
          print(index);
          setState(() {
              this._currentIndex = index;         
          });
        },
        iconSize: 36.0, 
        fixedColor: Colors.orange, // 选中的颜色
        type: BottomNavigationBarType.fixed, // 配置底部可以有多个按钮(按钮大于4个显示不了)
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            title: Text('首页')
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.category),
            title: Text('分类')
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.my_location),
            title: Text('我的')
          )
        ],
      ),
    );
  }
}

在这里插入图片描述

底部导航凸起按钮

Scaffold(
      appBar: AppBar(
        title:Text('Flutter Demo')
      ),
      floatingActionButton: Container(
        height: 80,
        width: 80,
        padding: EdgeInsets.all(8),
        margin: EdgeInsets.only(top: 10),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(40),
          color:Colors.white
        ),
        child: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: (){
            setState(() {
              this._currentIndex = 1;       
            });
          },
        ),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
}

在这里插入图片描述

24、Navigator 路由组件

通过 Navigator 组件管理路由导航,Navigator.push()跳转和 Navigator.pop()返回。
两种配置路由跳转的方式:1、基本路由 2、命名路由

基础路由

传参跳转 Navigator.push()

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        RaisedButton(
          child: Text('跳转到搜索页面'),
          onPressed: (){
            // 路由跳转
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context)=>SearchPage(title:'哈哈哈',countNum:666)
              )
            );
          },
        )
      ],
    );
  }
}

class SearchPage extends StatelessWidget {
  String title;
  int countNum;
  SearchPage({this.title='嘿嘿', this.countNum = 0});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton( //浮动按钮
        child: Text('返回'),
        onPressed: (){
          Navigator.of(context).pop(); // 返回
        },
      ),
      appBar: AppBar(
        title: Text('搜索页面'),
      ),
      body: Text('搜索内容页${this.title}----->${this.countNum}'),
    );
  }
}

在这里插入图片描述

命名路由

统一管理路由:
1)在根组件定义路由
2)使用Navigator.pushNamed(context, ‘/search’);

class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return MaterialApp( 
      home: Tabs(), 
      routes: { // 定义命名路由(map类型)
        '/search':(context)=>SearchPage(),
        '/my':(context)=>MyPage()
      },
    );
  }
}

Column(
  children: <Widget>[
    RaisedButton(
      child: Text('跳转到搜索页面'),
      onPressed: (){
        Navigator.pushNamed(context, '/search');  // 路由跳转
      },
    )
  ],
);

命名路由传参

//固定写法
var onGenerateRoute=(RouteSettings settings) {
  // 统一处理
  final String name = settings.name; 
  final Function pageContentBuilder = routes[name];
  if (pageContentBuilder != null) {
    if (settings.arguments != null) {
      final Route route = MaterialPageRoute(
          builder: (context) =>
              pageContentBuilder(context, arguments: settings.arguments));
      return route;
    }else{
        final Route route = MaterialPageRoute(
          builder: (context) =>
              pageContentBuilder(context));
        return route;
    }
  }
};

final routes={
  '/':(context)=> Tabs(),
  '/search': (context, {arguments}) => SearchPage(arguments: arguments),
  '/my':(context)=>MyPage()
};

class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return MaterialApp( 
      // home: Tabs(), 
      // routes: { // map类型,命名路由
      //   '/search': (context) => SearchPage(),
      //   '/my':(context)=>MyPage()
      // },
      initialRoute: '/',     //初始化的时候加载的路由
      onGenerateRoute: onGenerateRoute
    );
  }
}

class SearchPage extends StatelessWidget {
  String title;
  int countNum;
  final Map arguments;
  SearchPage({this.arguments,this.title='嘿嘿', this.countNum = 0});
  @override
  Widget build(BuildContext context) {
    print(this.arguments);
    return Scaffold(
      floatingActionButton: FloatingActionButton( //浮动按钮
        child: Text('返回'),
        onPressed: (){
          Navigator.of(context).pop(); // 返回
        },
      ),
      appBar: AppBar(
        title: Text('搜索页面'),
      ),
      body: Text('搜索内容页${this.title}--->${this.countNum}--->${arguments!=null?arguments["id"]:"0"}'),
      // body: Text('搜索内容页${this.arguments}'),
    );
  }
}

Column(
  children: <Widget>[
    RaisedButton(
      child: Text('跳转到搜索页面'),
      onPressed: (){
        Navigator.pushNamed(context, '/search',arguments: {
            "id":123
          });
      },
    )
  ],
);

在这里插入图片描述

在有状态组件里面传参
在这里插入图片描述

返回

Navigator.of(context).pop(); 返回到上一页
Navigator.of(context).pushReplacementNamed('/registerSecond');  替换路由

// 返回到根路由
Navigator.of(context).pushAndRemoveUntil( 
	new MaterialPageRoute(builder: (context) => new Tabs(index:1)),   // 要引入Tabs页面
	(route) => route == null  // 把前面所有路由都置为空
);

25、AppBar自定义顶部导航(Scaffold 组件里的参数)

注意:加了leading就没有返回按钮,会替换掉返回按钮

Scaffold(
   appBar: AppBar(
     title:Text('appbar'),
     centerTitle: true, // 无论是安卓还是ios,标题都是居中显示
     leading: IconButton(
       icon:Icon(Icons.menu),
       onPressed: (){
         print('menu');
       },
     ),
     actions: <Widget>[
       IconButton(
         icon: Icon(Icons.search),
         onPressed: (){
           print('search');
         },
       ),
       IconButton(
         icon: Icon(Icons.settings),
         onPressed: (){
           print('settings');
         },
       )
     ],
   ),
   body: Text('sssssssss'),
 );

在这里插入图片描述

26、TabBar 定义顶部 Tab 切换

注意:TabBar和TabBarView的数量要对应起来

MaterialApp( 
	home:DefaultTabController(
      length: 2,
      child: Scaffold(
        appBar: AppBar(
          title:Text('appbar'),
          centerTitle: true, // 无论是安卓还是ios,标题都是居中显示
          leading: IconButton(
            icon:Icon(Icons.menu),
            onPressed: (){
              print('menu');
            },
          ),
          bottom: TabBar(
            tabs: <Widget>[
              Tab(text: '热门',),
              Tab(text: '推荐',),
            ],
          ),
        ),
        body: TabBarView(
          children: <Widget>[
            ListView(
              children: <Widget>[
                ListTile(
                  title: Text('第一个tab'),
                )
              ],
            ),
            ListView(
              children: <Widget>[
                ListTile(
                  title: Text('第二个tab'),
                )
              ],
            )
          ],
        ),
      ),
    );
) 

在这里插入图片描述

把 TabBar 放在导航最顶部

把TabBar放appBar的title上

DefaultTabController(
      length: 2,
      child: Scaffold(
        appBar: AppBar(
          // title:Text('appbar'), 
          title: Row(    // 把tabBar放title里
            children: <Widget>[
              Expanded(
                child: TabBar(
                  indicatorColor: Colors.yellow, // 指示器颜色
                  labelColor: Colors.yellow,
                  unselectedLabelColor: Colors.white, // 未选中的颜色
                  indicatorSize: TabBarIndicatorSize.label, // 条变短
                  tabs: <Widget>[
                    Tab(text: '热门',),
                    Tab(text: '推荐',),
                  ],
                ),
              )
            ],
          ),
          // bottom: 
          centerTitle: true, // 无论是安卓还是ios,标题都是居中显示
          leading: IconButton(
            icon:Icon(Icons.menu),
            onPressed: (){
              print('menu');
            },
          ),
        ),
        body: TabBarView(
        // physics:NeverScrollableScrollPhysics(), 禁止pageView滑动
          children: <Widget>[
            ListView(
              children: <Widget>[
                ListTile(
                  title: Text('第一个tab'),
                )
              ],
            ),
            ListView(
              children: <Widget>[
                ListTile(
                  title: Text('第二个tab'),
                )
              ],
            )
          ],
        ),
      ),
    );

在这里插入图片描述

TabBar实现Tabs的另一种方法

1)多继承 with SingleTickerProviderStateMixin
2)定义 _tabController,初始化的时候赋值,传入两参数
3)在TabBar和TabBarView都要配置controller参数

这种方法的好处是可以调用一些方法,监听改变

class TabBarControllerPage extends StatefulWidget {
  @override
  _TabBarControllerPageState createState() => _TabBarControllerPageState();
}

class _TabBarControllerPageState extends State<TabBarControllerPage> with SingleTickerProviderStateMixin {
  // 2)定义 _tabController,初始化的时候赋值,传入两参数
  TabController _tabController;
  @override
  void initState() { 
    super.initState();
    _tabController = new TabController(
      vsync: this,
      length: 2 // 长度
    );
    _tabController.addListener((){ // 传入方法,监听改变
      print(_tabController.index);
      // setState(() {});   // 来实现自定义操作
    }); 
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar( 
        title: Text('TabBarController'),
        bottom: TabBar(// 3)在TabBar、TabBarView都要配置controller参数
          controller: this._tabController,  // 不要this也可以
          tabs: <Widget>[
            Tab(text: '热销',),
            Tab(text: '推荐',)
          ],
        ),
      ),
      body: TabBarView( // 3)在TabBar、TabBarView都要配置controller参数
        controller: this._tabController,  // 不要this也可以
        children: <Widget>[
          Center(child: Text('热销'),),
          Center(child: Text('推荐'),)
        ],
      ),
    );
  }
}

在这里插入图片描述

27、Drawer侧边栏

Scaffold( 
	appBar: AppBar( title: Text("Flutter App"), ),
	drawer: Drawer( child: Text('左侧边栏'), ),
	endDrawer: Drawer( child: Text('右侧侧边栏'), ), 
);
Scaffold(
  appBar: AppBar(
      title:Text('Flutter Demo')
    ),
    drawer: Drawer( 
      child: Column(
        children: <Widget>[
          // DrawerHeader(
          //   child: Text('哈哈哈哈'),
          // ),
          Row(
            children: <Widget>[
              Expanded(
                child: DrawerHeader(
                  child: Text('哈哈哈哈'),
                  decoration: BoxDecoration(
                    // color:Colors.yellow
                    image: DecorationImage(
                      image: NetworkImage('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'),
                      fit: BoxFit.cover
                    )
                  ),
                ),
              )
            ],
          ),
          ListTile(
            leading: CircleAvatar(
              child: Icon(Icons.home),
            ),
            title: Text('我的'),
          ),
          Divider(), 
          ListTile(
            leading: CircleAvatar(
              child: Icon(Icons.home),
            ),
            title: Text('我的'),
          ),
          Divider(), 
          ListTile(
            leading: CircleAvatar(
              child: Icon(Icons.home),
            ),
            title: Text('我的'),
          )
        ],
      ) 
    ),
    endDrawer: Drawer( 
      child: Text('右侧侧边栏'), 
    ),
}

在这里插入图片描述

定义头部的组件UserAccountsDrawerHeader

上面的头部要自己去定义,UserAccountsDrawerHeader这个是给我们提供定义好的样式,格式比较固定

Scaffold(
      appBar: AppBar(
        title:Text('Flutter Demo')
      ),
      drawer: Drawer( 
        child: Column(
          children: <Widget>[
            // DrawerHeader(
            //   child: Text('哈哈哈哈'),
            // ),
            Row(
              children: <Widget>[
                Expanded(
                  child: UserAccountsDrawerHeader(
                    accountName:Text('哈哈'),
                    accountEmail:Text('8888888@qq.com'),
                    currentAccountPicture: CircleAvatar(
                      backgroundImage: NetworkImage('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'),
                    ),
                    decoration: BoxDecoration(
                      // color:Colors.yellow
                      image: DecorationImage(
                        image: NetworkImage('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'),
                        fit: BoxFit.cover
                      )
                    ),
                    otherAccountsPictures: <Widget>[
                      Image.network('https://ucc.alicdn.com/pic/developer-ecology/de4c239d57ff4820811524c3860b1728.jpg'),
                      Image.network('https://ucc.alicdn.com/pic/developer-ecology/91d2e73798c245a39650fa4476e785e0.jpg')
                    ],
                  )
                )
              ],
            ),
            ListTile(
              leading: CircleAvatar(
                child: Icon(Icons.home),
              ),
              title: Text('我的'),
            ),
            Divider(), 
            ListTile(
              leading: CircleAvatar(
                child: Icon(Icons.home),
              ),
              title: Text('我的'),
            ),
            Divider(), 
            ListTile(
              leading: CircleAvatar(
                child: Icon(Icons.home),
              ),
              title: Text('我的'),
            )
          ],
     ) 
  ),
}

在这里插入图片描述

侧边栏跳转到其它页面

ListTile(
  leading: CircleAvatar(
    child: Icon(Icons.home),
  ),
  title: Text('我的'),
  onTap: (){
    Navigator.of(context).pop(); // 隐藏侧边栏
    Navigator.pushNamed(context, '/CategoryPage');
  },
),

28、按钮组件

RaisedButton :凸起的按钮,其实就是 Material Design 风格的 Button FlatButton :扁平化的按钮
OutlineButton:线框按钮
IconButton :图标按钮 (用在导航栏)
ButtonBar:按钮组
FloatingActionButton:浮动按钮

1、如果要高度,要用Container组件包裹
2、onPressed:null 表示禁用,加颜色也是灰色
3、线框按钮加背景颜色无效

Scaffold(
 appBar: AppBar(
    title: Text('按钮演示'),
    actions: <Widget>[
      IconButton(
        icon: Icon(Icons.settings),
        onPressed: (){},
      )
    ],
  ),
  body: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Row(
        children: <Widget>[
          RaisedButton(
            child: Text('普通按钮'),
            onPressed: (){
              print('普通按钮');
            },
          ),
          SizedBox(width: 10,),
          Container(
            height: 50,
            width: 100,
            child: RaisedButton(
              child: Text('有颜色有宽度按钮'),
              color: Colors.blue,
              textColor: Colors.white,
              onPressed: (){
                print('有颜色有宽度按钮');
              },
            ),
          ),
          SizedBox(width: 10,),
          RaisedButton(
            child: Text('有阴影的按钮'),
            color: Colors.blue,
            textColor: Colors.white,
            elevation: 10, // 阴影
            onPressed: (){
              print('有阴影的按钮');
            },
          ),
          SizedBox(width: 10,),
          RaisedButton.icon(
            icon: Icon(Icons.search),
            label: Text('带图标按钮'),
            color: Colors.blue,
            textColor: Colors.white,
            onPressed: null, // 按钮禁用掉,所以为灰色
            // onPressed: (){},
          )
        ],
      ),
      Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Expanded( // Expanded嵌套按钮,可以让它自适应
            child: Container(
              height: 40,
              margin: EdgeInsets.all(10),
              child: RaisedButton(
                child: Text('自适应圆角按钮'),
                color: Colors.blue,
                textColor: Colors.white,
                shape: RoundedRectangleBorder( // 配置圆角
                  borderRadius: BorderRadius.circular(10)
                ),
                onPressed: (){
                  print('自适应圆角按钮');
                },
              ),
            ),
          )
        ],
      ),
      Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Container(
            width: 100,
            height: 100,
            margin: EdgeInsets.all(10),
            child: RaisedButton(
              child: Text('有点击颜色的圆形按钮'),
              color: Colors.blue,
              textColor: Colors.white,
              splashColor: Colors.yellow, // 水波纹颜色
              shape:CircleBorder( // 圆形按钮
                side: BorderSide(
                  color: Colors.white
                )
              ),
              onPressed: (){
                print('有点击颜色的圆形按钮');
              },
            ),
          ),
          FlatButton(
            child: Text('扁平化按钮'),
            color: Colors.blue,
            textColor: Colors.white,
            onPressed: (){
              print('扁平化按钮');
            },
          ),
          SizedBox(width: 10,),
          OutlineButton(
            child: Text('线框按钮'),
            // color: Colors.blue, // 背景颜色没效果
            textColor: Colors.blue,
            onPressed: (){
              print('线框按钮');
            },
          )
        ],
      ),
      Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          ButtonBar(
            children: <Widget>[
              RaisedButton(
                child: Text('登录'),
                onPressed: (){
                  print('登录');
                },
              ),
              RaisedButton(
                child: Text('注册'),
                onPressed: (){
                  print('注册');
                },
              ),
            ],
          )
        ],
      )
    ],
  ),
);

在这里插入图片描述

自定义按钮

注意:double 必须指定会默认转换,不指定就要传double类型的数据(即80.0)

class MyButton extends StatelessWidget {
  final text;
  final pressed; // 方法
  final double width;
  final double height;
  const MyButton({this.text='', this.pressed = null, this.width=80, this.height=80});
  @override
  Widget build(BuildContext context) {
    return Container(
      height: this.height,
      width: this.width,
      child: RaisedButton(
        child: Text(this.text),
        onPressed: this.pressed,
      ),
    );
  }
}

MyButton(text: '自定义按钮',height: 60, width: 100,pressed: (){
  print('自定义按钮');
},)

29、表单组件

TextField文本框组件

Column(
   children: <Widget>[
     TextField(
       decoration: InputDecoration(
         icon: Icon(Icons.people),
         hintText: '请输入用户名' // 类似placeholder
       ),
     ),
     SizedBox(height: 20,),
     TextField(
       obscureText:true, // 设置密码
       decoration: InputDecoration(
         hintText: '请输入搜索的内容',
         border: OutlineInputBorder(), // 设置边框
         labelText: '输入密码', // lable的名称
         labelStyle: TextStyle(fontSize: 20) // 配置lable的样式
       ),
     ),
     SizedBox(height: 20,),
     TextField(
       maxLines: 2, // 设置多行
       decoration: InputDecoration(
         hintText: '多行文本框',
         border: OutlineInputBorder()
       ),
     ),
   ],
 ),

在这里插入图片描述

获取表单内容

controller结合实例化的TextEditingController(),加上onChanged监听获取表单内容。
实例化TextEditingController()的作用是为了初始化的时候赋值,也可以不用,直接var _username;

class _TextFieldPageState extends State<TextFieldPage> {
  // 1.实例化(这个是赋初始值用的,如果不用赋初始值,可以直接var _username;)
  var _username=new TextEditingController();

  @override
    void initState() {
      super.initState();
      // 2.赋值 (可以加this,也可以不加)
      _username.text = '初始值'; 
    }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TextField'),
      ),
      body: Padding(
        padding: EdgeInsets.all(20),
        child: Column(
          children: <Widget>[
            TextField(
              decoration: InputDecoration(
                icon: Icon(Icons.people),
                hintText: '请输入用户名' // 类似placeholder
              ),
              // 3.把TextEditingController绑定在controller上
              controller: _username,
              onChanged: (value){ // 4.监听赋值,_username.text就可以获取文本框的值
                _username.text = value;
              },
            ),
            SizedBox(height: 20,),
            Container(
              width: double.infinity,
              height: 40,
              child: RaisedButton(
                child: Text('登录'),
                onPressed: (){
                  print(_username.text);
                },
                color: Colors.blue,
                textColor: Colors.white,
              ),
            )
          ],
        ),
      ),
    );
  }
}

Checkbox、CheckboxListTile多选框组件

var flag=true;

Column(
  children: <Widget>[
    Checkbox(
      value: this.flag,
      onChanged: (v){
        setState(() {
           this.flag = v;                 
        });
      },
      activeColor: Colors.green,
    ),
    Text(this.flag?'选中':'未选中'),
    SizedBox(height: 20,),
    CheckboxListTile(
      value: this.flag, 
      title: Text("一级标题"), 
      subtitle: Text("二级标题"), 
      onChanged: (v){ setState(() { this.flag=v; }); },
      activeColor: Colors.red, 
      secondary: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'), 
      selected:this.flag 
    )
  ],
),

在这里插入图片描述

Radio、RadioListTile单选按钮组件

int sex=1;

Row(
  children: <Widget>[
    Text('男:'),
    Radio(
      value: 1,
      onChanged: (v){
        setState(() {
           this.sex = v;                   
        });
      },
      groupValue: this.sex,
    ),
    Text('女:'),
    Radio(
      value: 2,
      onChanged: (v){
        setState(() {
           this.sex = v;                   
        });
      },
      groupValue: this.sex,
    )
  ],
),
Row(
  children: <Widget>[
    Text('${this.sex}'),
    Text(this.sex == 1?'男':'女')
  ],
),
RadioListTile(
  value: 1,
  onChanged: (v){
    setState(() {
        this.sex = v;                   
    });
  },
  groupValue: this.sex,
  title: Text('一级标题'),
  subtitle: Text('二级标题'),
  secondary: Icon(Icons.help),
  selected: this.sex == 1?true:false,
),
RadioListTile(
  value: 2,
  onChanged: (v){
    setState(() {
        this.sex = v;                   
    });
  },
  groupValue: this.sex,
  title: Text('一级标题'),
  subtitle: Text('二级标题'),
  secondary: Icon(Icons.help),
  selected: this.sex == 2?true:false,
),
])

在这里插入图片描述

开关Switch

属性:value(值)、onChanged(改变时触发)、activeColor(选中的颜色、背景颜色)

bool flag = true;

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

在这里插入图片描述

30、日期时间

安装第三方库

找插件pub.dev,复制进去,ctrl+s保存(会有flutter packages get命令),没报错就是安装成功
在这里插入图片描述

// 引入库
import 'package:date_format/date_format.dart';

 var now = DateTime.now();

@override
void initState() {
  super.initState();
  print(now); // 2020-02-15 14:20:00.823996
  print(now.millisecondsSinceEpoch); // 转时间戳 1581747600823  
  print(DateTime.fromMillisecondsSinceEpoch(1581747600823)); // 转换为日期 2020-02-15 14:20:00.823
  print(formatDate(DateTime.now(), [yyyy, '-', mm, '-', dd])); // 第三方插件转换日期 2020-02-15
}

汉化

参考

自带日期组件和时间组件

showDatePicker、showTimePicker

import 'package:date_format/date_format.dart';
class DatePickerPage extends StatefulWidget {
  @override
  _DatePickerPageState createState() => _DatePickerPageState();
}

class _DatePickerPageState extends State<DatePickerPage> {
  DateTime _nowDate = DateTime.now();
  var _nowtime=TimeOfDay(hour: 9,minute: 20);

  _showDatePicker() async {
   /* showDatePicker(
      context: context,
      initialDate: _nowDate,
      firstDate: DateTime(1980), // 起始日期
      lastDate: DateTime(2025)
    ).then((res){
      print(res);
    });
    */
    var result = await showDatePicker(
      context: context,
      initialDate: _nowDate, // 初始值
      firstDate: DateTime(1980), // 起始日期
      lastDate: DateTime(2025) // 结束日期
    );

    // print(result);
    setState(() {
      this._nowDate = result;
    });
  }

  _showTimePicker() async {
   var result = await showTimePicker(
      context: context,
      initialTime: _nowtime,
    );
    setState(() {
      this._nowtime = result;
    });
  }

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('DatePicker'),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
             InkWell(
               child: Row(
                 children: <Widget>[
                    Text('${formatDate(this._nowDate, [yyyy, '年', mm, '月', dd, '日'])}'),
                    Icon(Icons.arrow_drop_down)
                 ],
               ),
               onTap: _showDatePicker,
             ),
             InkWell(
               child: Row(
                 children: <Widget>[
                    Text('${_nowtime.format(context)}'),
                    Icon(Icons.arrow_drop_down)
                 ],
               ),
               onTap: _showTimePicker,
             )
            ],
          )
        ],
      ),
    );
  }
}

在这里插入图片描述

第三方日期时间库

flutter_cupertino_date_picker

// 引入
import 'package:flutter_cupertino_date_picker/flutter_cupertino_date_picker.dart';

class _DatePickerPageState extends State<DatePickerPage> {
  
 DateTime _dateTime=DateTime.now();

  _showDatePicker(){
      DatePicker.showDatePicker(
          context,
          pickerTheme: DateTimePickerTheme(
            showTitle: true,
            confirm: Text('确定', style: TextStyle(color: Colors.red)),
            cancel: Text('取消', style: TextStyle(color: Colors.cyan)),
          ),
          minDateTime: DateTime.parse("1980-05-12"),
          maxDateTime: DateTime.parse("2100-05-12"),
          initialDateTime: _dateTime,
          // dateFormat: "yyyy-MMMM-dd",

          dateFormat:'yyyy年M月d日    EEE,H时:m分',  // show TimePicker
          pickerMode: DateTimePickerMode.datetime,  // show TimePicker

           
          locale: DateTimePickerLocale.zh_cn,
          onCancel: () {
            debugPrint('onCancel');
          },         
          onConfirm: (dateTime, List<int> index) {
            setState(() {
              _dateTime = dateTime;
            });
          },
      );  
  }

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('DatePicker'),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              InkWell(
                child: Row(
                  children: <Widget>[
                     Text("${formatDate(_dateTime, [yyyy, '年', mm, '月', dd,' ',HH, ':', nn])}"),
                    Icon(Icons.arrow_drop_down)
                  ],
                ),
                onTap: _showDatePicker,
              )
            ],
          )
        ],
      ),
    );
  }
}

在这里插入图片描述

31、flutter_swiper轮播图

flutter_swiper

class _SwiperPageState extends State<SwiperPage> {
  List<Map> imgList = [
    {
      'url':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'
    },
    {
      'url':'https://image3.suning.cn/uimg/aps/material/157529162928370083.jpg'
    },
    {
      'url':'https://image.suning.cn/uimg/aps/material/158167381752182833.jpg'
    }
  ];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('轮播图'),
      ),
      body: Column(
        children: <Widget>[
          Container(
            width: double.infinity,
            // height: 200,
            child: AspectRatio( // 自适应
              aspectRatio: 16/9, // 宽度和高度是16比9
              child: new Swiper(
                itemBuilder: (BuildContext context,int index){
                  return new Image.network(
                    imgList[index]['url'],
                    fit: BoxFit.fill,
                  );
                },
                itemCount: imgList.length,
                pagination: new SwiperPagination(), // 小圆点
                control: new SwiperControl(), // 左右箭头
                loop: true, // 自动轮播
              ),
            )
          )
        ],
      )
    );
  }
}

在这里插入图片描述

32、弹窗

AlertDialog、SimpleDialog、showModalBottomSheet、第三方库showToast

// 引入库
import 'package:fluttertoast/fluttertoast.dart';

class _DialogPageState extends State<DialogPage> {
  _alertDialog() async{
    var result = await showDialog(
      context: context,
      builder: (context){
        return AlertDialog(
          title: Text('提示信息!'),
          content: Text('您确定要删除吗?'),
          actions: <Widget>[
            FlatButton(
              child: Text('取消'),
              onPressed: (){
                Navigator.pop(context,'cancel'); // 把值返回出去
              },
            ),
            FlatButton(
              child: Text('确定'),
              onPressed: (){
                Navigator.pop(context,'ok'); // 把值返回出去
              },
            )
          ],
        );
      }
    );
    print(result);
  }

  // 选择弹窗
  _simpleDialog() async{
    var result =await showDialog(
      context: context,
      builder: (context){
        return SimpleDialog(
          title: Text('选择内容'),
          children: <Widget>[
            SimpleDialogOption(
              child: Text('option a'),
              onPressed: (){
                Navigator.pop(context,'a');
              },
            ),
            Divider(),
            SimpleDialogOption(
              child: Text('option b'),
              onPressed: (){
                Navigator.pop(context,'b');
              },
            ),
          ],
        );
      }
    );
    print(result);
  }

  // 底部弹出
  _modalBottomSheet() async{ 
    var result =await showModalBottomSheet(
      context: context,
      builder: (context){
        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');
                },
              )
            ],
          ),
        );
      }
    );
    print(result);
  }

  _toast(){
    Fluttertoast.showToast(
      msg: "错误信息",
      toastLength: Toast.LENGTH_SHORT,
      gravity: ToastGravity.BOTTOM,
      timeInSecForIos: 1,  // 只在ios有效果
      backgroundColor: Colors.black,
      textColor: Colors.white,
      fontSize: 16.0
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('弹窗'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              child: Text('alertDialog'),
              onPressed: _alertDialog,
            ),
            SizedBox(height: 20,),
            RaisedButton(
              child: Text('simpleDialog'),
              onPressed: _simpleDialog,
            ),
            SizedBox(height: 20,),
            RaisedButton(
              child: Text('showModalBottomSheet'),
              onPressed: _modalBottomSheet,
            ),
            SizedBox(height: 20,),
            RaisedButton(
              child: Text('toast-fluttertoast第三方库'),
              onPressed: _toast,
            ),
            SizedBox(height: 20,),
          ],
        ),
      )
    );
  }
}

在这里插入图片描述

自定义弹窗

自定义 Dialog 对象,需要继承 Dialog 类,重写 build 函数。(默认的 Dialog 背景框是满屏的)

import 'dart:async';

RaisedButton(
  child: Text('alertDialog'),
  onPressed: (){
    showDialog(
      context: context,
      builder: (context){
        return MyDialog(title:'关于我们', content:'内容内容内容内容内容内容');
      }
    );
  },
),

// 继承Dialog,然后重写
class MyDialog extends Dialog{
  String title;
  String content;

  MyDialog({this.title='', this.content=''});

  // 定时器
  _showTimer(context){
    var timer;
    timer = Timer.periodic( // 要引入dart:async,才能用Timer
      Duration(milliseconds: 1000), (t) { // 触发回调函数  
        print('关闭');
        Navigator.pop(context);
        t.cancel();   //取消定时器   timer.cancel();
    });
  }

  @override
  Widget build(BuildContext context) {
   
    _showTimer(context);

    return Material(
      type: MaterialType.transparency,
      child: Center(
        child: Container(
          width: 200,
          height: 200,
          color: Colors.white,
          child: Column(
            children: <Widget>[
              Padding(
                padding: EdgeInsets.all(10),
                child: Stack(
                  children: <Widget>[
                    Align(
                      alignment: Alignment.center,
                      child: Text('${this.title}'),
                    ),
                    Align(
                      alignment: Alignment.topRight,
                      child: InkWell(
                        child: Icon(Icons.close),
                        onTap: (){
                          Navigator.pop(context);
                        },
                      ),
                    )
                  ],
                )
              ),
              Divider(),
              Container(
              width: double.infinity,
                child: Text('${this.content}', textAlign: TextAlign.left,),
              )
            ],
          ),
        ),
      )
    );
  }
}

在这里插入图片描述

33、请求/提交、渲染动态数据

1、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 类型

2、使用 http 库进行网络请求
http

渲染动态数据

渲染的两种方法: 1)通过map来渲染 2)通过ListView.builder来渲染

      {
        "result": [{
            "_id": "5ac0896ca880f20358495508",
            "title": "精选热菜",
            "pid": "0",		
          }, {
            "_id": "5ac089e4a880f20358495509",
            "title": "特色菜",
            "pid": "0",
            
          }
        ]
      }
// 通过map来渲染数据
import 'package:http/http.dart' as http;
import 'dart:convert';

class _HttpDemoState extends State<HttpDemo> {

  List _list=[];

  @override
  void initState() { // 1. 通过initState获取数据  
    super.initState();
    this._getData();
  }
  _getData() async{
    var apiUrl="http://localhost:4000/productlist";
    var result=await http.get(apiUrl); // 2.请求数据
    if(result.statusCode==200){
      print(result.body);
      setState(() {
       this._list=json.decode(result.body)["result"]; // 3.赋值
      });
    }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){
            return ListTile(
              title: Text(value["title"]),
            );
        }).toList(),
      ):Text("加载中...")
    );
  }
}
// 通过ListView.builder来渲染
import 'package:http/http.dart' as http;
import 'dart:convert';

class _HttpDemoState extends State<HttpDemo> {

  List _list=[];

  @override
  void initState() { // 1. 通过initState获取数据  
    super.initState();
    this._getData();
  }
  
  _getData() async{
    var apiUrl="http://localhost:4000/productlist";
    var result=await http.get(apiUrl); // 2.请求数据
    if(result.statusCode==200){
      print(result.body);
      setState(() {
       this._list=json.decode(result.body)["result"]; // 3.赋值
      });
    }else{
      print("失败${result.statusCode}");
    }
  }

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

34、Dio请求

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

// 对比
http.get(apiUrl);
Dio().get(apiUrl)

// post 
Map jsonData={
	'username':'哈哈哈哈',
	'age':20
}
Response response = await Dio().post(apiUrl, data:jsonData)
import 'dart:convert';
import 'package:dio/dio.dart';

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

  @override
  void initState() { 
    super.initState();
    this._getData();
  }

void  _getData() async{ // 如果函数没有返回值,那么应声明为void类型,可加,可不加
    var apiUrl="http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1";
    Response result=await Dio().get(apiUrl);

    // print(json.decode(result.data)["result"]);
    setState(() {
     this._list=json.decode(result.data)["result"]; 
    });

    
  }

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

35、上拉刷新

  • 官方提供下拉刷新的组件RefreshIndicator,但是没有上拉分页加载更多的组件。
    RefreshIndicator里面有onRefresh事件,如果有下拉刷新可以触发方法。规定返回的方法必须是Future类型
  • Flutter ListView 中有一个 ScrollController 属性,它就是专门来控制 ListView 滑动事件。

RefreshIndicator下拉刷新

Future<void> _onRefresh() async{
	print('下拉刷新');
	await Future.delayed(Duration(milliseconds:2000),(){ // 2秒后消失
		print('请求数据完成')
	})
}

@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("加载中...")
  ); 
}

ScrollController上拉加载分页

ListView里的属性,通过ScrollController来监听滚动事件。
1)_scrollController.position.pixels 滚动的距离 (滚动条距离顶部的距离,下拉时值变大)
_scrollController.position.maxScrollExtent 总距离 (整个页面的高度,值不变)
两个值相等说明到底部了(一般让还没到底部就开始加载)
2)判断是否最后一页,是就不请求

ScrollController _scrollController = ScrollController(); //listview 的控制器

void initState() { 
  super.initState(); 
  this._getData(); 
  _scrollController.addListener(() { 
    if (_scrollController.position.pixels > _scrollController.position.maxScrollExtent-20) { 
      print('滑动到了最底部'); 
      _getData(); 
    }
  });
}

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

下拉刷新、上拉加载

import 'package:flutter/material.dart';

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

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

  _NewsPageState createState() => _NewsPageState();
}

class _NewsPageState extends State<NewsPage> {
  List _list = [];
  int _page = 1;
  bool hasMore = true; //判断有没有数据
  ScrollController _scrollController = new ScrollController();

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

    //监听滚动条事件
    _scrollController.addListener(() {
      print(_scrollController.position.pixels); //获取滚动条下拉的距离

      print(_scrollController.position.maxScrollExtent); //获取整个页面的高度

      if (_scrollController.position.pixels >
          _scrollController.position.maxScrollExtent - 40) {
        this._getData();
      }
    });
  }

  void _getData() async {
    if (this.hasMore) {
      var apiUrl =
          "http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=${_page}";

      var response = await Dio().get(apiUrl);
      var res = json.decode(response.data)["result"];   

      setState(() {
        this._list.addAll(res);  //拼接
        this._page++;
      });
      //判断是否是最后一页
      if (res.length < 20) {
        setState(() {
          this.hasMore = false;
        });
      }
    }
  }

  //下拉刷新
  Future<void> _onRefresh() async {
    await Future.delayed(Duration(milliseconds: 2000), () {
      print('请求数据完成');
      _getData();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("新闻列表"),
      ),
      body: this._list.length > 0
          ? RefreshIndicator(
              onRefresh: _onRefresh,
              child: ListView.builder(
                controller: _scrollController,
                itemCount: this._list.length, //20
                itemBuilder: (context, index) {//19                  
                  if (index == this._list.length-1) {   //列表渲染到最后一条的时候加一个圈圈
                    //拉到底
                     return Column(
                      children: <Widget>[
                        ListTile(
                          title: Text("${this._list[index]["title"]}",
                              maxLines: 1),
                        ),
                        Divider(),
                        _getMoreWidget()
                      ],
                    );

                  } else {
                    return Column(
                      children: <Widget>[
                        ListTile(
                          title: Text("${this._list[index]["title"]}",
                              maxLines: 1),
                        ),
                        Divider()
                      ],
                    );
                  }
                },
              ))
          : _getMoreWidget(),
    );
  }

  //加载中的圈圈
  Widget _getMoreWidget() {

    if(hasMore){
      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("--我是有底线的--"),
       );
    }
  }
}

36、flutter_html解析html

flutter_html

import 'package:flutter_html/flutter_html.dart';

Html(
  data: """
  ${this._list.length>0?this._list[0]["content"]:''}                            
  """,
  //Optional parameters:
  padding: EdgeInsets.all(8.0),
  backgroundColor: Colors.white70,
  defaultTextStyle: TextStyle(fontFamily: 'serif'),
  linkStyle: const TextStyle(
    color: Colors.redAccent,
  ),
  onLinkTap: (url) {
    // open url in a webview
  }
)

37、WebView 组件flutter_inappbrowser 加载远程 web 页 面

flutter_inappbrowser
在app里面打开一个浏览器来加载数据

flutter_inappbrowser: ^1.2.1
import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
Expanded(
  child: InAppWebView(
           initialUrl: "http://www.phonegap100.com/newscontent.php?aid=${this.arguments["aid"]}",                   
           onProgressChanged: (InAppWebViewController controller, int progress) {
               print(progress/100);
               if((progress/100)>0.999){
                 setState(() {
                    this._flag=false;
                 });
               }
           },
   ),
 )

加粗样式

38、获取设备信息

device_info

import 'package:device_info/device_info.dart';

@override
void initState() { 
  super.initState();
  this._getDevice();
}

_getDevice() async{
  DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
  AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
  print('Running on ${androidInfo.model}');  // e.g. "Moto G (4)"
}

39、获取地理位置

获取当前经度纬度,然后去数据库匹配,看看当前经度纬度最近的一些餐馆,显示出来
amap_location

1)获取PackageName
在这里插入图片描述
在这里插入图片描述
2)获取调式安全码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3)获取完key值后输入
在这里插入图片描述

import 'package:amap_location/amap_location.dart';

class _LocationPageState extends State<LocationPage> {
  double _longitude=0;
  double _latitude=0;

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


  _getLocation() async{
    //启动一下
    await AMapLocationClient.startup(new AMapLocationOption( desiredAccuracy:CLLocationAccuracy.kCLLocationAccuracyHundredMeters  ));  
    //获取地理位置
    var result = await AMapLocationClient.getLocation(true);

    print("经度:${result.longitude}");

    print("纬度:${result.latitude}");

    setState(() {
       this._longitude=result.longitude;
       this._latitude=result.latitude;
    });
  } 

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("地理定位演示"),
      ),
      body:Column(
        children: <Widget>[
          Text("经度:${this._longitude}"),
          Text("纬度:${this._latitude}"),
        ],
      ),
    );
  }
}

40、相机拍照和相册选择

image_picker

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:dio/dio.dart';
  
class ImagePickerPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _ImagePickerState();
  }
}

class _ImagePickerState extends State<ImagePickerPage> {

  File _image;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ImagePicker"),
      ),
      body: SingleChildScrollView(
        child: Column(

          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[             
            RaisedButton(
              onPressed: _takePhoto,
              child: Text("拍照"),
            ),
            RaisedButton(
              onPressed: _openGallery,
              child: Text("选择图库照片"),
            ),
            _buildImage()

          ],
        ),
      )
    );
  }

 
  
  /*拍照*/
  _takePhoto() async {
    var image = await ImagePicker.pickImage(source: ImageSource.camera,maxWidth:400);
    setState(() {
      this._image= image;
    });
    this._uploadImage(image);
  }

  /*相册*/
  _openGallery() async {
    var image = await ImagePicker.pickImage(source: ImageSource.gallery,maxWidth:400);
    setState(() {
      this._image= image;
    });
  }

  //定义一个组件显示图片
  Widget _buildImage(){
      if(this._image==null){
        return Text("请选择图片...");
      }
      return Image.file(this._image);

  }

  //上传图片
  _uploadImage(_imageDir) async{
      FormData formData = new FormData.from({
        "name": "zhangsna 6666666666",
        "age": 20,
        "sex":"男",
        "file":new UploadFileInfo(_imageDir, "xxx.jpg"),
      });
      var response = await Dio().post("http://jd.itying.com/imgupload", data: formData);
      print(response);
  }
}



// node.js
var express = require("express");
var router=express.Router();
var multiparty = require('multiparty');

//nodejs教程 40

router.get('/',function(req,res){
    res.send('前台首页')
})


//给编辑器提供的上传图片的接口
router.post('/imgupload',function(req,res){
    console.log('imgupload');
    var form = new multiparty.Form();
    form.uploadDir='public/upload'  /*设置图片上传的路径*/

    form.parse(req, function(err, fields, files) {
		console.log(files); //文件
		console.log(fields); //post数据
        var path="/"+files.file[0].path;
        res.json({"success":"true","path":path})  /*给编辑器返回地址信息*/
    });
})

module.exports=router;

41、视频播放

video_playe
chewie
注意:页面销毁时,要结束视频播放

import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:chewie/chewie.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.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4');

    chewieController = ChewieController(
      videoPlayerController: videoPlayerController,
      aspectRatio: 3 / 2,
      autoPlay: true,
      looping: true,
    );


  }
  /*销毁*/
  @override
  void dispose() {
    videoPlayerController.dispose();
    chewieController.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('在线视频播放'),
      ),
      body: Center(
        child: Chewie(
          controller: chewieController,
        )
      ),
    );
  }
}

42、检测网络插件

connectivity
注意:页面销毁时,检测网络插件也要销毁

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> {

  var subscription;
  String _stateText;
  
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
     subscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
       if(result==ConnectivityResult.wifi){
         setState(() {
          _stateText="处于wifi";
         });
       }else if(result==ConnectivityResult.mobile){
         setState(() {
            _stateText="处于手机网络";
        });
       }else{
        setState(() {
          _stateText="没有网络";
        });         
       }
      // Got a new connectivity status!
     });
  }

  @override
  dispose() {
    super.dispose();
    subscription.cancel();  //注意
  }

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

43、本地存储

shared_preferences
方法

//设置值
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)

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

// 删除值
SharedPreferences prefs = await SharedPreferences.getInstance(); 
prefs.remove(key); //删除指定键 
prefs.clear();//清空键值对
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 sp=await SharedPreferences.getInstance();
    sp.setString("username", "张三111");
    sp.setString("age", "26");
  }
  _getData() async{
    SharedPreferences sp=await SharedPreferences.getInstance();
    print(sp.getString("username"));
    print(sp.getString("age"));
  }
  _removeData() async{
    SharedPreferences sp=await SharedPreferences.getInstance();
    print(sp.remove("age"));
  }

  @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,
          ),
           SizedBox(height: 10),
          RaisedButton(
            child: Text('清除数据'),
            onPressed:_removeData,
          )         
        ]),
      ),
    );
  }
}

// util.js
import 'package:shared_preferences/shared_preferences.dart';

class Storage{
  static Future<void> setString(key,value) async{
       SharedPreferences sp=await SharedPreferences.getInstance();
       sp.setString(key, value);
  }

  static Future<String> getString(key) async{
       SharedPreferences sp=await SharedPreferences.getInstance();
       return sp.getString(key);
  }

  static Future<void> remove(key) async{
       SharedPreferences sp=await SharedPreferences.getInstance();
       sp.remove(key);
  }
}
// 引用
import '../../common/Storage.dart';
onPressed: () async{
   var username=await Storage.getString('username');
   print(username);
},

44、扫描二维码条形码插件

barcode_scan
在android studio配置,如果错误有提示
在这里插入图片描述
在这里插入图片描述

// 配置权限
<uses-permission android:name="android.permission.CAMERA" />

<activity android:name="com.apptreesoftware.barcodescan.BarcodeScannerActivity"/>

从远程下载包
在这里插入图片描述

import 'package:flutter/material.dart';

import 'package:barcode_scan/barcode_scan.dart';
import 'package:flutter/services.dart';

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

  _ScanPageState createState() => _ScanPageState();
}

class _ScanPageState extends State<ScanPage> {
  String barcode;

  Future _scan() async {
    try {
      String barcode = await BarcodeScanner.scan();
      setState(() {
        return this.barcode = barcode;
      });
    } on PlatformException catch (e) {
      if (e.code == BarcodeScanner.CameraAccessDenied) {
        setState(() {
          return this.barcode = 'The user did not grant the camera permission!';
        });
      } else {
        setState(() {
          return this.barcode = 'Unknown error: $e';
        });
      }
    } on FormatException {
      setState(() => this.barcode =
          'null (User returned using the "back"-button before scanning anything. Result)');
    } catch (e) {
      setState(() => this.barcode = 'Unknown error: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.photo_camera),
          onPressed: _scan,
        ),
        appBar: AppBar(
          title: Text("扫码"),
        ),
        body: Text("${barcode}")
    );
  }
}

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

1、Android App 升级执行流程
1)获取本地版本号
2)请求服务器获取服务器版本号
3)本地版本和服务器版本不一致提示升级,弹窗提示用户是否更新
4)用户确定升级,调用文件传输方法下载 apk 文件
5)监听下载进度
6)下载完成打开 Apk 进行安装
注意:在 Ios 中没法直接下载安装,如果版本不一致直接跳转到 Ios 应用对应的应用市场就 可以了。

2、配置 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 检测版本号
path_provider 获取文件存储路径 (保存目录)
flutter_downloader 下载文件
open_file 打开文件插件
如何获取版本号(package_info),如何从服务器下载文件(flutter_downloader),获取本地存储路径,把它保存起来(path_provider),调用open_file打开文件

服务器的包替换本地的包,要对应用进行签名打包
配置版本号:

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果有key就用以前的key,没有就创建一个
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这样就会对应用做正式打包,打包后就可以上传到应用市场
在这里插入图片描述
注意:这里要和第一次打包的路径一样,这样新版本就可以和老版本一样
在这里插入图片描述

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(){
    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.ionic.wang/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升级演示"),
    );
  }
}

注意:
1、服务器的 App 版本必须大于本地 App 版本
2、本地 App 和服务器 App 的包名称 签名必须一致,这样的话服务器的包才可以替换本地 的包
3、安卓9.0以后要求https协议请求

46、打开外部浏览器/应用、拨打电话、发送短信/文件

Flutter url_launcher

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

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

  _UrlLauncherState createState() => _UrlLauncherState();
}

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: [
            RaisedButton(
              child: Text('打开外部浏览器'),
              onPressed: () async{                                 
                  const url = 'https://cflutter.com';
                  if (await canLaunch(url)) {
                    await launch(url);
                  } else {
                    throw 'Could not launch $url';
                  }
              },
            ),
            SizedBox(height: 10),
            RaisedButton(
              child: Text('拨打电话'),
              onPressed: () async{
                  var tel = 'tel:10086';
                  if (await canLaunch(tel)) {
                    await launch(tel);
                  } else {
                    throw 'Could not launch $tel';
                  }
                
              },
            ),
            SizedBox(height: 10),
            RaisedButton(
              child: Text('发送短信'),
              onPressed: () async{
                 var 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://
                    alipays://
                  */
                  var url = 'alipays://';
                  if (await canLaunch(url)) {
                    await launch(url);
                  } else {
                    throw 'Could not launch $url';
                  }
              },
            )       
          ]),
        )));
  }
}

打开ios应用市场,获取应用市场地址,调用外部浏览器方式打开launch(url)

47、微信支付

1)微信公众号(认证300、申请微信支付)、商户平台(生成商户平台账户)、开放平台(认证300,用于:第三方平台开发)的准备
2)开放平台创建应用
在这里插入图片描述

在这里插入图片描述
**加粗样式**

在这里插入图片描述
在这里插入图片描述
应用签名:

  1. 需要签名打包您的 App,并保存好签名文件以及签名密码。
  2. 把刚打包的 app 发到手机上面安装
  3. 使用微信官方工具获取当前应用的签名。

签名工具地址
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意

1、

 // int countNum = 0; // 构造函数是常量,属性不是常量会报错
  final int countNum = 0;
  const HomeContent({key key}):super(key:key);

调用原生库的时候,可能会下载第三方库,下载可能会失败,可以打开android studio让它下载

11、一些方法

(111,222,333).toList() // 转成数组[111, 222, 333]
double.Parse() // 可以将字符串string转换为double类型
https://www.jianshu.com/p/df225c203e96

Future 就是相当于promise异步语法

Future<DateTime> showDatePicker({})

如果函数没有返回值,那么应声明为void类型

其他

1、线 Divider()
2、设置宽高空隙 SizeBox(height:10)
3、列表

ListTile(
	leading:Container()
	title:Text('标题')
	subtitle:Text('描述~~~')
)

4、头像

ClipOval( child: Image.network('xxx',fit: BoxFit.cover,width: 60,height: 60,), )

处理圆形图片:把图片转成头像(推荐这种)

CircleAvatar(backgroundImage: NetworkImage('xxx'),)

5、循环

ListView(
	children:listData.map((value){
		return Card(...)
	}).toList()
)

6、按钮组件RaisedButton

RaisedButton( 
      child: Text('女装'), 
      textColor: Theme.of(context).accentColor, 
      onPressed: (){ },
      color: Theme.of(context).accentColor, 
      textTheme: ButtonTextTheme.primary
);

7、水墨点击组件
可以当做按钮组件,但是它没有样式

InkWell(
	child: ,onTap:(){}
)
GestureDetector( // InkWell有水墨,GestureDetector没有
   child: Container(
   		height: 400,
        child: Text("底部"),
  ),
  onTap: (){ return false ;}, // 阻止showModalBottomSheet弹窗点击消失
);

8、宽度100% width: double.infinity,

9、渐变颜色

Container(
       width: ScreenAdapter.width(750),
         height: ScreenAdapter.height(100),
         child: Text('ssss'),
         decoration: BoxDecoration(
             gradient: LinearGradient(
           colors: [Color.fromRGBO(255, 170, 134, 1), Color.fromRGBO(255, 57, 78, 1)],
           begin: Alignment.centerLeft,
           end: Alignment.centerRight,
         )),
)

wifi无线真机调试

1)安装 ADB Commanads for VSCode扩展 (vscode要高版本,比如1.42.1版本)
2)真机用usb连接电脑 (终端输入adb devices可以看到有没有找到)
在这里插入图片描述
3)在vscode中按快捷键 Ctrl + Shift + P,输入Disconnect from any devices(切断所有连接),Reset connected devices port to :5555,最后选择Connect to device IP
4)输入flutter run,如果提示选择设备,输入flutter run -d DU3ADH14CB024315
在这里插入图片描述
参考了

另一种方案:
使用数据线连接手机和电脑,在ide端能显示移动设备名称
输入命令行adb tcpip 5555
输入命令行adb connect 你的手机ip地址(例: adb connect 192.168.1.2)
拔掉线,ide端若还能显示设备名称,即OK
参考

注意事项:手机要有足够内存,打开usb调试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值