Drat与Flutter基础

Drat语法

1、基本语法

返回修饰词 main (){}

void main(){
//程序的主入口
}

2、基本数据类型

Drat是强类型语言
var 代表不确定类型 当变量第一次被赋值时,那么该变量就是当前类型,重新赋值不能改变类型
int 整数型
double 浮点型
bool 布尔型
String 字符串类型
num数字类型不区分浮点与整数型

void main(){
  int a = 10;
  a = 10.1;//会报错
  String b = "this is Drat";
  bool flag = true;
  double c = 10.2; 
  var d = 123;
  d = "123";//报错
}

3、final 与const

final 要求变量只能初始化一次,并不要求赋的值一定是编译时常量,可以是常量也可以不是。而 const要求在声明时初始化,并且赋值必需为编译时常量。

void main(){
}

List集合

List类似前端的数组,注意的是
add()增加元素

List a = new List();
a.add(456)

class Person{
  String name;
  int age;
  Person(String name,int age){
    this.name = name;
    this.age = age;
  }//构造函数一种写法
  Person(this.name,this.age){}//构造函数一种写法
  
  void printInfo(){
    print("${this.name} --- ${this.age}");
  }
}

Flutter

1、区分StatefullWidgetStatelessWidget的区别

StatefullWidget:为动态组件,状态会发生改变的,例如进度条等。
StatelessWidget:静态组件,例如文本框。

2、Hello Word

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context){
    return MaterialApp(
      title : "欢迎学习,Flutter",
      home : Scaffold(
        appBar : AppBar(title:Text("Hello Wrod")),
        body : Center(child:Text("Hello Wrod",style:TextStyle(color:Colors.blue,fontSize:50)))
      )
    );
  }
}
image.png

3、MaterialApp组件

MaterialApp组件一般是我们的根组件他可以定义我们的APP的主题颜色,已经背景等
查看MaterialApp源文件

MaterialApp({
  Key key,
  this.title = '', // 设备用于为用户识别应用程序的单行描述
  this.home, // 应用程序默认路由的小部件,用来定义当前应用打开的时候,所显示的界面
  this.color, // 在操作系统界面中应用程序使用的主色。
  this.theme, // 应用程序小部件使用的颜色。
  this.routes = const <String, WidgetBuilder>{}, // 应用程序的顶级路由表
  this.navigatorKey, // 在构建导航器时使用的键。
  this.initialRoute, // 如果构建了导航器,则显示的第一个路由的名称
  this.onGenerateRoute, // 应用程序导航到指定路由时使用的路由生成器回调
  this.onUnknownRoute, // 当 onGenerateRoute 无法生成路由(initialRoute除外)时调用
  this.navigatorObservers = const <NavigatorObserver>[], // 为该应用程序创建的导航器的观察者列表
  this.builder, // 用于在导航器上面插入小部件,但在由WidgetsApp小部件创建的其他小部件下面插入小部件,或用于完全替换导航器
  this.onGenerateTitle, // 如果非空,则调用此回调函数来生成应用程序的标题字符串,否则使用标题。
  this.locale, // 此应用程序本地化小部件的初始区域设置基于此值。
  this.localizationsDelegates, // 这个应用程序本地化小部件的委托。
  this.localeListResolutionCallback, // 这个回调负责在应用程序启动时以及用户更改设备的区域设置时选择应用程序的区域设置。
  this.localeResolutionCallback, // 
  this.supportedLocales = const <Locale>[Locale('en', 'US')], // 此应用程序已本地化的地区列表 
  this.debugShowMaterialGrid = false, // 打开绘制基线网格材质应用程序的网格纸覆盖
  this.showPerformanceOverlay = false, // 打开性能叠加
  this.checkerboardRasterCacheImages = false, // 打开栅格缓存图像的棋盘格
  this.checkerboardOffscreenLayers = false, // 打开渲染到屏幕外位图的图层的棋盘格
  this.showSemanticsDebugger = false, // 打开显示框架报告的可访问性信息的覆盖
  this.debugShowCheckedModeBanner = true, // 在选中模式下打开一个小的“DEBUG”横幅,表示应用程序处于选中模式
}) 

利用MaterialApp修改我们App的主题

    return MaterialApp(title: "MaterialApp",
      home:Scaffold(
        appBar : AppBar(title:Text("Flutter MaterialApp"))
      ),
      theme:ThemeData(
        primaryColor: Colors.yellow,//appbar为黄色
        scaffoldBackgroundColor:Colors.pink//设置底层背景为粉红色
      )
    );
image.png

4、Container组件的属性

Container类似前端的div元素

属性 描述 用法
child Container容器中包含的子组件 child: Text("这是Container的子组件")
alignment 将子组件对齐方式 alignment:Alignment.bottomRight
width Container容器的宽度 width:300.0
height Container容器的高度 height:300.0
padding Container容器的内边距 padding: EdgeInsets.all(20.0) 或者 padding: EddgeInsets.fromLTRB(double left, double top, double right, double bottom)
margin Container容器的外边距 margin: EdgeInsets.all(20.0) 或者 margin: EddgeInsets.fromLTRB(double left, double top, double right, double bottom)
color Container容器的背景色 Colors.yellow 或者 color:Color.fromRGBO(r, g, b, opacity)
decoration 装饰器,可以设置边框、圆角、背景图片,背景色:注意此属性不能与color属性同时使用 具体可看代码
transform 变换旋转 transform: Matrix4.rotationZ(0.3)
@override
  Widget build(BuildContext context) {
    return Container(
      child:Text("这是包裹在Container组件内的Text组件。"),
      alignment : Alignment.bottomRight,
      width: 300.0,
      height: 300.0,
      padding: EdgeInsets.fromLTRB(20.0, 40.0, 60.0, 40.0),
      margin: EdgeInsets.all(40.0),
      //color: Colors.yellow,//背景色不能与decoration同时使用,
      decoration:BoxDecoration(
        color: Colors.yellow,//背景色
        border: Border.all(//线条样式
          color: Colors.black,//边框颜色
          width: 3.0,//线框
          style: BorderStyle.solid,//实现
        ),
        image: DecorationImage(//背景图片 注意使用https地址图片
          image: NetworkImage('https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2606284972,2670724825&fm=26&gp=0.jpg'),
          alignment: Alignment.topCenter,//对齐方式
        ),
      )
    );
  }
image.png

4-1使用Container制造圆形且带阴影

Container(
  width:50.0,
  height:50.0,
  decoration:BoxDecoration(
    color:Colors.yellow,//背景图片
    shape:BoxShape.circle,//圆形
    boxShadow:[BoxShadow(color: Colors.black, offset: Offset(0, 0),blurRadius: 2.0, spreadRadius: 0.0)]//阴影
  )
)

5、TextWidget常用属性

属性 描述 用法
textAlign 文本对齐方式 textAlign:TextAlign.right
overflow 文本超出样式 overflow:TextOverflow.ellipsis,
maxLines 最大显示的行数 maxLines:2
style 设置文本样式 style:TextStyle(color:Color.fromRGBO(25, 201, 124, 1),)
style_color 属于style属性下,设置文本颜色 style:TextStyle(color:Color.fromRGBO(25, 201, 124, 1),)
style_fontSize 属于style属性下,设置文本字体大小 style:TextStyle(fontSize: 20.0,)
style_fontWeight 属于style属性下,设置文本字体加粗 style:TextStyle(fontWeight: FontWeight.w800,)
style_fontStyle 属于style属性下,设置文本字体斜体 style:TextStyle(fontStyle: FontStyle.italic,)
style_decoration 属于style属性下,设置文本中间的横线 style: TextStyle(decoration: TextDecoration.lineThrough)
Text("这是包裹在Container组件中的Text子组件",
style:TextStyle(
  color:Color.fromRGBO(30, 201, 124, 1),
  fontSize: 20.0,
  fontWeight: FontWeight.w800,
  fontStyle: FontStyle.italic
  ),
 textAlign:TextAlign.right,
 overflow:TextOverflow.ellipsis,
 maxLines:2
)

6、Image组件

Image一种是网络图片、一种是本地图片
Image.network()Image.asset()

属性 描述 用法
默认值 图片应用的地址 -
color 图片的颜色,直接使用会覆盖掉图片 color:Colors.pink
colorBlendMode 混合模式,类似在图片上弄个遮罩需要与color一起使用 colorBlendMode: BlendMode.screen
fit 图片裁剪 fit:BoxFit.cover
repeat 平铺 repeat: ImageRepeat.repeat,

BoxFit.cover:可能拉伸,裁切,充满,但是不会是图片变形
BoxFit.fill:图片充满整个容器,会被拉伸
BoxFit.contain:全图显示(不是充满容器),保留原宽高比例,会有空隙

BoxFit.cover

image.png

BoxFit.fill
image.png

BoxFit.contain
image.png

    Container(
        width:300.0,
        height:300.0,
        child: Image.network(//设置远程图片
          "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2606284972,2670724825&fm=26&gp=0.jpg",
          fit: BoxFit.cover,
          repeat: ImageRepeat.repeat,
          color:Colors.green,
          colorBlendMode: BlendMode.screen,
        ),
        decoration: BoxDecoration(
          //color:Colors.lime
          border: Border.all(
            color:pink,
            width: 4,
            style: BorderStyle.solid
          ),
        ),
      )
image.png

6-1、 实现圆角、以及实现圆形图片

第一种:使用container背景图片实现圆形图片

    Container(
        width: 300.0,
        height: 300.0,
        decoration: BoxDecoration(
          image: DecorationImage(
            image: NetworkImage("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2606284972,2670724825&fm=26&gp=0.jpg"),
            fit:BoxFit.cover//填充背景图片
          ),
          border:Border.all(
            width:3,
            color:Colors.black,
            style: BorderStyle.solid
          ),
          borderRadius:BorderRadius.circular(150.0)//圆角
        ),
      )

第二种利用ClipOval组件实现圆形图片 推荐使用这个

return Center(
  child:Container(
    child:ClipOval(
      child:Image.network("图片地址",width:100.0,height:100.0,fit:BoxFit.cover)
    )
  )
);

第三种利用CircleAvatar

CircleAvatar(
      radius: 25.0,
      backgroundImage: NetworkImage("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2611652800,2596506430&fm=26&gp=0.jpg")
    );

6-3、image组件之引入本地图片

1、在项目根目录创建静态资源文件夹

创建images文件夹其次在这个文件夹下创建2.0x和3.0x文件夹,然后把静态资源考进去,images文件夹下也得拷一份

- images
-- 2.0x
-- 3.0x
image.png

2、在配置文件夹中写入刚刚的路径

pubspec.yaml注意:格式需要对齐,不要保留空格

  assets:
    - images/1.jpg
    - images/3.0x/1.jpg
    - images/2.0x/1.jpg

3、使用图片

      Container(
        child: ClipOval(
          child: Image.asset("images/1.jpg",width: 200.0,height: 200.0,fit: BoxFit.fill,),
        ),
      )
image.png

7、 Icon组件

图标组件

属性 描述 用法
size 图标大小 size: 30.0,
color 图标颜色 color: Colors.blue,
基础值 具体的图标 Icons.sentiment_neutral
    Icon(Icons.sentiment_neutral,
       size: 30.0,
        color: Colors.blue
     )
image.png

8、 ListView组件

ListView:基础列表组件、水平列表组件、图标组件,相当于父容器,里面很多子元素

属性 描述 使用
scrollDirection 定义水平列表还是垂直列表 scrollDirection:Axis.horizontal水平列表scrollDirection:Axis.vertical垂直列表
padding 外边距 padding: EdgeInsets.all(20.0) 或者 padding: EddgeInsets.fromLTRB(double left, double top, double right, double bottom)
children 子元素,并且它的子元素不是一个,数组类型 children:[Container(width: 100.0,height: 100.0,color: Colors.red,),Container(width: 100.0,height: 100.0,color: Colors.blue,),Container(width: 100.0,height: 100.0,color: Colors.pink,),Container(width: 100.0,height: 100.0,color: Colors.yellow,)]
    ListView(
        scrollDirection:Axis.vertical,
        children: <Widget>[
          Image.asset("images/1.jpg",width: 200.0,height: 300.0,fit: BoxFit.fill,),
          Text("这是列表组件"),
          Container(width: 200.0,height: 200.0,color: Colors.red,)
        ],
      ),

8-1、我们可以配合ListTitle一起使用

ListTitle组件,类似于新闻页主标题,副标题

属性 描述 使用
leading 前面的图标 leading:ICon(Icons.sentiment_neutral)也可以图片
trailing 后面的图标 trailing:ICon(Icons.sentiment_neutral)
title 一级标题 title:Text("阿斯达四大")
title 一级标题 title:Text("阿斯达四大")
subtitle 二级标题/文本/描述 subtitle:Text("阿斯达四大")
image.png

这是listTtile的效果

ListView(
        scrollDirection:Axis.vertical,
        padding: EdgeInsets.all(10.0),
        children: <Widget>[
          Container(
            margin: EdgeInsets.fromLTRB(0, 0, 0, 10.0),
            child: ListTile(
              leading:Image.asset("images/1.jpg",width: 100.0,height: 200.0,fit: BoxFit.fill,),
              trailing:Icon(Icons.sentiment_neutral,
                size: 30.0,
                color: Colors.blue
              ),
              title:Container(
                child:Text(
                  "黄山旅游董事长章德辉:景区停摆每天损失450万 关注“两只票”",
                  style: TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.w900
                  ),
                ),
              ),
              subtitle: Text("1月13日-23日,每日经济新闻推出《专访董事长·第一季》,华谊兄弟董事长王中军、工业富联董事长李军旗、通威集团董事局主席刘汉元等多位重磅嘉宾接受采访,畅谈行业现状、直陈公司利弊、展望未来前景,在业界引发强烈反响。时值全国“两会”即将召开之际,又是抗击新冠肺炎疫情之后,行业、企业复苏的关键时刻,作为知名上市公司的领头人,他们怎样看待疫情对行业、企业的影响?又如何带领企业走出困境?")
            ),
          ),
          Container(
            margin: EdgeInsets.fromLTRB(0, 0, 0, 10.0),
            child: ListTile(
              leading:Image.asset("images/1.jpg",width: 100.0,height: 200.0,fit: BoxFit.fill,),
              trailing:Icon(Icons.sentiment_neutral,
                size: 30.0,
                color: Colors.blue
              ),
              title:Container(
                child:Text(
                  "黄山旅游董事长章德辉:景区停摆每天损失450万 关注“两只票”",
                  style: TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.w900
                  ),
                ),
              ),
              subtitle: Text("1月13日-23日,每日经济新闻推出《专访董事长·第一季》,华谊兄弟董事长王中军、工业富联董事长李军旗、通威集团董事局主席刘汉元等多位重磅嘉宾接受采访,畅谈行业现状、直陈公司利弊、展望未来前景,在业界引发强烈反响。时值全国“两会”即将召开之际,又是抗击新冠肺炎疫情之后,行业、企业复苏的关键时刻,作为知名上市公司的领头人,他们怎样看待疫情对行业、企业的影响?又如何带领企业走出困境?")
            ),
          )
        ],
      ),
image.png

9、动态组件

动态组件:顾名思义会改变组件状态的,例如进度条,或者我们拿到数据循环出组件(类似v-for);

我们看个简单的例子

我们要知道 任何的组件他都是一个类,只不过继承了StatelessWidget、StatefulWidget这两个接口,只要是类,他就有变量成员,方法

class MyBody extends StatelessWidget{

  List<Widget> _getData(){ //重点

    List<Widget> MyList = new List();

    for(int i = 0 ;i<20;i++){
      MyList.add(
        Container(
          padding: EdgeInsets.all(10.0),
          child: ListTile(
            leading: Icon(Icons.smartphone,size: 38,color: Colors.pink,),
            title: Text("美药企跳过疫苗研发关键实验环节 美股还会暴跌吗",style: TextStyle(fontSize: 22,fontWeight: FontWeight.w900),),
            subtitle: Text("据报道,美药企跳过疫苗研发关键实验环节,直接进行人体临床试验,而该做法备受一些专家质疑。美国总统特朗普此前称,争取2020年年底前实现疫苗的量产和分发。值得一提的是,对于加速疫苗的研发,一些美国网友也不认同上述做法。",maxLines:3,overflow:TextOverflow.ellipsis),
          ),
        )
      );
    }
    return MyList;
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return ListView(
      children: this._getData()
    );
  }
}
image.png

mock拿取服务端数据渲染

//服务端数据
var ListData = [
  {
    "title":"北京一单位33人集中发热 初判与使用中央空调有关",
    "pubtime":2,
    "reply":255,
    "imageUrl" : "https://cms-bucket.ws.126.net/2020/0518/f55c820fp00qaitd8006qc000s600e3c.png"
  },
  {
    "title":'蓬佩奥"警告"中国不要干涉美记者在港工作 中方回应',
    "pubtime":4,
    "reply":0,
    "imageUrl" : "https://cms-bucket.ws.126.net/2020/0518/b76b3832j00qaiq2u004tc000s600e3c.jpg"
  },
  {
    "title":"失联6天的翼装女飞行员遇难:遗体最先被村民发现",
    "pubtime":5,
    "reply":"4.2万",
    "imageUrl" : "https://cms-bucket.ws.126.net/2020/0518/4689114ep00qail4e00e9c000s600e3c.png"
  }]
 List<Widget> _getData(){
    var result = ListData.map((item){
      return ListTile(
        leading: Image.network(item["imageUrl"],width: 90.0,height:60.0,fit: BoxFit.fill,),
        title:Text(item["title"],style: TextStyle(fontSize:18,fontWeight: FontWeight.w900),),
        subtitle: Row(
          children: <Widget>[
            Container(child: Text(item['pubtime'].toString() + "小时前"),margin: EdgeInsets.fromLTRB(0, 10, 10.0, 0),),
            Container(child: Text(item['reply'].toString() + "万人查看"),margin: EdgeInsets.fromLTRB(0, 10, 10.0, 0),)
          ],
        )
      );
    });

    return result.toList();
  }
image.png

9-1、ListView.builder方法

itemCount必填参数:循环的次数
itemBuilder:循环主体

@override
  Widget build(BuildContext context) {
    return ListView.builder(//两个必填参数itemCount循环次数
      itemCount: ListData.length,
      itemBuilder: (ctx,index){//itemBuilder循环体 
        return ListTile(
          leading: Image.network(ListData[index]["imageUrl"],width: 90.0,height: 70.0,fit: BoxFit.fill,),
          title: Text(ListData[index]["title"],style: TextStyle(fontSize: 20,fontWeight: FontWeight.w900),),
        );
      }
    );

10、GridView组件

image.png

栅格布局:
常用有两种方法:
1、GridView.builder()
2、GridView.count()
请注意当GridView被放在Cloumn或者Row里面使用那么一定要指定GridView的高度,即在GridView外面包裹一层Container并设置高度

属性 描述 用法
scrollDirection 横向还是纵向 scrollDirection:Axis.vertical
padding 内边距 同上面几个例子
resolve 组件反向排序 resolve:true
crossAxisSpacing 一行子元素的间距 crossAxisSpacing:20.0
mainAxisSpacing 垂直方向的子元素的间距 mainAxisSpacing:20.0
crossAxisCount 一行子元素的个数 crossAxisCount:3
childAspectRatio 子元素的宽高比例 childAspectRatio:1.0
chidren 子元素集合 chidren:<Widget>[]
gridDelegate 该属性是GridView.builder中使用 gridDelegate:SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount:3,mainAxisSpacing : 20.0,crossAxisSpacing :20.0,childAspectRatio:0.6)
physics 禁止滚动 physics:NeverScrollableScrollPhysics()

10-1、简单的例子

GridView.count

List<Widget> _getData(){
   var list = ListData.map((i){
      return Container(
          decoration: BoxDecoration(
            border: Border.all(width:0.3,style: BorderStyle.solid,color: Colors.pink)
          ),
          child: Column(
            children: <Widget>[
              Image.network(i["imageUrl"],height: 50.0,fit: BoxFit.cover,),
              Container(height: 10.0,),
              Text(i["title"],style: TextStyle(fontSize: 13.0),maxLines: 2,overflow: TextOverflow.ellipsis)
            ],
          ),
        );
    });
    return list.toList();
  }
  
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return GridView.count(
      crossAxisCount: 3,//一行三个
      crossAxisSpacing:20.0,//一行之间的间距
      mainAxisSpacing:20.0,//垂直之间的间距
      padding: EdgeInsets.all(10.0),//内边距
      children:this._getData(),//子元素
      childAspectRatio:0.6,//设置宽高比例不能直接设置宽高的
    );
  }

10-2、GridView.builder

Widget _getData(ctx,i){
      return Container(
        decoration: BoxDecoration(
          border: Border.all(width:0.3,style: BorderStyle.solid,color: Colors.pink)
        ),
        padding: EdgeInsets.fromLTRB(15, 5, 15, 10),
        child: Column(
          children: <Widget>[
            Image.network(ListData[i]["imageUrl"],height: 120.0,fit: BoxFit.fitWidth,),
            SizedBox(height: 6.0,),//撑开间距
            Text(ListData[i]["title"],style: TextStyle(fontSize: 16.0,fontWeight: FontWeight.w900),maxLines: 2,overflow: TextOverflow.ellipsis)
          ],
        ),
      );
  }
  
  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount:3,
        mainAxisSpacing : 20.0,
        crossAxisSpacing :20.0,
        childAspectRatio:0.6
      ),
      padding: EdgeInsets.all(10.0),
      itemCount: ListData.length,
      itemBuilder: this._getData//注意这个地方不是调用哦
    );
  }

11、Padding、Row、Colum、Expanded组件

11-1、Padding组件

在Flutter中有许多组件是没有padding属性,我们之前都是包裹在Container中使用

属性 描述 使用
padding 内边距 padding:EdgeInsets.all(10.0)
child 子元素 child:Text()
ListView(
  children:[
    Padding(padding:padding:EdgeInsets.all(10.0),child:Text("121313")),
    Padding(padding:padding:EdgeInsets.all(10.0),child:Text("121313")),
  ]
)

11-2、Row组件

水平列表组件
mainAxisAlignment、crossAxisAlignment是相对于父元素的位置起值的,如果父元素没有那么默认就是内容的高度

属性 描述 使用
crossAxisAlignment 纵轴对齐方式 crossAxisAlignment:CrossAxisAlignment.spaceEvenly
mainAxisAlignment 横轴对齐方式 mainAxisAlignment:MainAxisAlignment.center
children 子元素集合 children:[]

测试下mainAxisAlignment、crossAxisAlignment

     return Container(
      width: 800.0,
      height: 600.0,
      color: Colors.lightBlue,
      child: Row(
        children: <Widget>[
          Container(width: 100.0,height: 100.0,color: Colors.red,),
          Container(width: 100.0,height: 100.0,color: Colors.orange,),
          Container(width: 100.0,height: 100.0,color: Colors.green,)
        ],
      )
    );

默认情况

默认情况

MainAxisAlignment.spaceEvenly均匀分配横轴方向
image.png

纵轴相对于父元素,如果父元素没有,那么无效CrossAxisAlignment.start

image.png

11-2、Colum组件

用法属性和Row一致

mainAxisAlignment:MainAxisAlignment.spaceEvenly,
crossAxisAlignment:CrossAxisAlignment.start,
image.png

11-3、Expanded组件

Expanded组件类似于前端的display:flex;
注意一点:在Row中使用Expanded的时候,无法指定Expanded中的子组件的宽度width,但可以指定其高度height。同理,在Column中使用Expanded的时候,无法指定Expanded中的子组件的高度height,可以指定宽度width。

属性 描述 使用
flex 占用多少格 flex:1整数型
child 子元素 child:Text()

和Row组件一起使用

@override
  Widget build(BuildContext context) {
    return Row(
        children: <Widget>[
          Expanded(child: Container(height:200.0,color: Colors.blue,),flex: 1),
          Expanded(child: Container(height:200.0,color: Colors.pink),flex: 2),
        ],
      );
  }
image.png

和Cloumn一起使用

return Column(
        children: <Widget>[
          Expanded(child: Container(height:200.0,color: Colors.blue,),flex: 1),
          Expanded(child: Container(height:200.0,color: Colors.pink),flex: 2),
        ],
      );
image.png

12、布局小结

实现下面的布局


image.png
@override
  Widget build(BuildContext context) {
    return Padding(padding: EdgeInsets.all(10.0),
      child: Column(
        children: <Widget>[
          Container(height: 180.0,color: Colors.black,),
          Padding(
            padding: EdgeInsets.fromLTRB(0, 10.0, 0, 0),
            child: Row(
              children: <Widget>[
                Expanded(
                  child: Padding(padding: EdgeInsets.fromLTRB(0, 0, 10.0, 0),child: Image.network("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1809168321,2277139267&fm=26&gp=0.jpg"),),
                  flex: 2,
                ),
                Expanded(
                  child:Column(
                      children: <Widget>[
                        Image.network("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2484090652,3266104108&fm=26&gp=0.jpg",fit: BoxFit.fill,),
                        SizedBox(height: 10.0,),
                        Image.network("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2382576479,3688530667&fm=26&gp=0.jpg",fit: BoxFit.fill)
                      ],
                    ),
                  flex: 1,
                )
              ],
            ),
          )
        ],
      ),
    );
  }

13、Stack、Align、Positioned组件

Stack、Align、Positioned是用来定位布局使用的。
Stack只能单一的组件进行定位
Stack与其他两个组件同时使用就可以对多个组件进行定位

13-1、Stack堆叠组件

属性 描述 用法
alignment 配置所有子元素显示的位置 alignment:AlignmentDirectional.topStart
children 子元素集合 children:[]
    Stack(
      children: <Widget>[
        Container(width: 300.0,height: 400.0,color: Colors.red,),
        Container(width: 100.0,height: 100.0,color: Colors.black,),
        Text("测试测试测试测试测试测试",style: TextStyle(color: Colors.white),)
      ],
    );
image.png

结果发现子组件都堆叠在一起了

alignment: Alignment.center

Alignment.center会把所有的组件居中堆叠在一起``

image.png

Alignment自带很多方位,当自带的方位不满足我们的需求,我们可以自定义方位
Alignment()他有两个参数X、Y 分别是0、1、-1之间的值
Alignment(-1,-1)最左上角
Alignment(-1,0) 贴近左侧垂直居中
Alignment(-1,1) 贴近左侧垂直靠底部(左下角)
Alignment(0,-1)横轴居中贴近顶部
Alignment(0,0)居中
Alignment(0,1)横轴居中贴近底部
Alignment(1,-1)右上角
当然也可以小数位

单一使用Stack的缺点我们也知道了,没办法对子组件一一定位

13-2、Stack与Align组件一起使用

这样就可以实现我们类似前端的定位了

Center(
      child: Container(//给了个最大的容器
        width: 400.0,
        height: 400.0,
        color: Colors.yellow,
        child: Stack(//然后使用Stack
          children: <Widget>[
            Align(//使用Align对子组件定位
              alignment:Alignment.topRight,
              child: Icon(Icons.home,size:40.0,color: Colors.red,)
            ),
            Align(
              alignment:Alignment.bottomRight,
              child: Icon(Icons.settings,size:40.0,color: Colors.blue,),
            ),
            Align(
              alignment:Alignment(0,0),//我们使用实例化alignment:Alignment类
              child: Icon(Icons.slow_motion_video,size:40.0,color: Colors.black,),
            ),
            
          ],
        ),
      ),
    );
image.png

13-3、Stack与Position一起使用(推荐使用这个)

Position组件就比Align好用多了,他有left、top、bottom、right四个值

Center(
      child: Container(
        width: 400.0,
        height: 400.0,
        color: Colors.yellow,
        child: Stack(
          children: <Widget>[
            Positioned(
              left:50,
              child: Icon(Icons.home,size:40.0,color: Colors.red,)
            ),
            Positioned(
              left:150,
              top: 100,
              bottom: 20,
              child: Icon(Icons.settings,size:40.0,color: Colors.blue,),
            ),
            Positioned(
              left:250,
              child: Icon(Icons.slow_motion_video,size:40.0,color: Colors.black,),
            ),
            
          ],
        ),
      ),
    );
image.png

14、AspectRatio组件

AspectRatio定义子元素相对于父元素的宽高比例

属性 描述 用法
aspectRatio 定义子元素相对父元素的宽高比 aspectRatio:2.0/1.0
child 子元素 child:Widget
Container(
      width: 300.0,
      child: AspectRatio(
        aspectRatio: 2.0/1.0,
        child: Container(
          color:Colors.yellow
        ),
      ),
    );

黄色区域的宽高比例是2/1


image.png

15、Card

类似于前端中ElementUI中的Card差不多,我们可以给想要加阴影的容器套一层Card就有阴影效果了

属性 描述 用法
margin 外边距 margin: EdgeInsets.all(10.0),
shadowColor 阴影颜色 shadowColor:Colors.red,
elevation 阴影扩散的大小 elevation : 10.0
return Contanier(width:200.0,height:200.0,child:Card());

16、练习布局

实现以下布局

image.png
class MyBody extends StatelessWidget{
  List list = new List();

  MyBody(){
    this.list = [{
      'avtorImg' : "https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1452751795,1897979528&fm=26&gp=0.jpg",
      'avtorName' : "十年之后_1",
      'avtorDesc' : "1描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
      'backGround' : "https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3778173668,1422088699&fm=26&gp=0.jpg"
    },{
      'avtorImg' : "https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2405043393,1737103092&fm=26&gp=0.jpg",
      'avtorName' : "十年之后_2",
      'avtorDesc' : "2描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
      'backGround' : "https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2335751370,711568964&fm=26&gp=0.jpg"
    },{
      'avtorImg' : "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3940043770,1553058007&fm=26&gp=0.jpg",
      'avtorName' : "十年之后_3",
      'avtorDesc' : "3描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
      'backGround' : "https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1532844926,3671374399&fm=26&gp=0.jpg"
    },{
      'avtorImg' : "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2255910787,3354486640&fm=26&gp=0.jpg",
      'avtorName' : "十年之后_4",
      'avtorDesc' : "4描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
      'backGround' : "https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2155983538,3860699715&fm=26&gp=0.jpg"
    },{
      'avtorImg' : "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2611652800,2596506430&fm=26&gp=0.jpg",
      'avtorName' : "十年之后_5",
      'avtorDesc' : "5描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
      'backGround' : "https://dss1.bdstatic.com/6OF1bjeh1BF3odCf/it/u=1242146115,2880436607&fm=74&app=80&f=JPEG&size=f121,90?sec=1880279984&t=0136ee81b657a616fbe11b994842e68e"
    }];
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return ListView.builder(
      itemCount: this.list.length,
      itemBuilder: (ctx,i){
        return Padding(
          padding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 0),
          child: Card(
            elevation:3.0,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                AspectRatio(aspectRatio: 10.0/5.0,child: Image.network(this.list[i]["backGround"],fit: BoxFit.cover,),),
                Padding(
                  padding: EdgeInsets.fromLTRB(0,10.0,0,10.0),
                  child: ListTile(
                    leading: ClipOval(child: Image.network(this.list[i]["avtorImg"],width:50.0,height:50.0,fit: BoxFit.cover,),),
                    title: Text(this.list[i]["avtorName"],style: TextStyle(fontWeight: FontWeight.w600),),
                    subtitle: Text(this.list[i]["avtorDesc"],maxLines:1,overflow: TextOverflow.ellipsis,style: TextStyle(color: Color.fromRGBO(102, 102, 102, 1))),
                  )
                )
              ],
            )
          )
        );
      }
    );
  }
}

17、RaisedButton按钮组件

如果需要给按钮宽高,那么可以通过给RaisedButton包裹一层Container设置宽高

属性 描述 用法
color 按钮的背景颜色 color:Colors.***
textColor 按钮里面的文本颜色 textColor:Colors.pink
child 子元素 child:Text()
onPressed 点击事件 onPressed(){}/onPressed:自定义函数
disabledTextColor 按钮禁用状态时文本颜色 disabledTextColor:Colors.pink
disabledColor 按钮禁用状态时背景颜色 disabledColor:Colors.pink
elevation 按钮的阴影 elevation:20.0
shape 圆角、原型圆形按钮 看例子
splashColor 点击按钮之后颜色变化过渡的效果 splashColor: Colors.red

focusColor、hoverColor
圆角:shape:RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(25.0)) )

RaisedButton(
      padding: EdgeInsets.all(10.0),
      child: Text("按钮"),
      onPressed: (){},
      shape:RoundedRectangleBorder(//圆角按钮
              borderRadius: BorderRadius.all(Radius.circular(25.0))
            ),

    );

17-1圆形按钮

使用shape属性,如果文本溢出,我们可以在包裹一层Container容器

RaisedButton(onPressed: (){
              print("圆形按钮");
            },child: Text("圆形按钮"),elevation: 20,splashColor: Colors.red,shape:CircleBorder(
              side:BorderSide(color:Colors.white)
            ),)

17-2图标按钮

RaisedButton.icon()

    RaisedButton.icon(onPressed: (){
            print("图标按钮");
          }, icon: Icon(Icons.search), label: Text("图标按钮")),

18、Wrap流式布局

它类似与GridView.count,但是GridView.count需要定义一行显示几个,而且子元素宽度自动填满

属性 描述 用法
spacing 子元素之间的间距 spacing:20.0
runSpacing 垂直之间的间距 runSpacing:20.0
direction 主轴的方向,默认水平 direction:Axis.horizontal,
alignment 主轴的对齐方式 alignment:WrapAlignment.start
Wrap(
      spacing : 3.0,
      runSpacing : 20.0,
      direction:Axis.horizontal,
      children: <Widget>[
        MyButton("啊实打实群"),
        MyButton("啊实打"),
        MyButton("啊实打2352实群"),
        MyButton("实群"),
        MyButton("实群"),
        MyButton("啊实打2实群"),
        MyButton("啊实打实群3463"),
        MyButton("群"),
        MyButton("啊实43群"),
        MyButton("啊4群"),
      ],
    );
image.png

19、StatefulWidget

当页面改变数据的时候我们需要使用StatefulWidget,例如当我们点击按钮,让文本的内容发生改变
StatefulWidget中如果需要初始化值需要在initState方法中实现

 int _curderIndex = 0;
  List _navList = new List();

  @override
  void initState() {
    super.initState();
    this._navList = [
      
    ];
  }
class HomePage extends StatelessWidget {
  String text = "你好Flutter1";
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Text(this.text),
        SizedBox(height: 30.0,),
        RaisedButton(
          child: Text("按钮"),
          onPressed: (){
            this.text = "asdq";
            print(this.text);
          },
        )
      ],
    );
  }
}

通过上述例子我们知道 文本不会发生变化但是值是变化了的。这说明在StatelessWidget不会改变组件的状态了

19-1、定义一个StatefulWidget组件

class HomePage extends StatefulWidget{
  HomePage(Key key) : super({key : key});//可以省略
  _HomePageState createState()=>_HomePageState ();
}
class _HomePageState extends State<HomePage>{
  @overirde
  Widget build(BuildContext context){
    return Text("1");
  }
}

StatefulWidget组件中有一个setState((){})的方法,可以改变组件的状态

class _HomePageState extends State<HomePage>{
  int num = 0;
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Chip(label: Text("点击${num}次")),
        SizedBox(height: 30.0,),
        RaisedButton(child: Text("按钮"),
        onPressed: (){
          setState(() {//重点
            this.num ++;
          });
        })
      ],
    );
  }
}

通过动态组件我们写一个案例:点击按钮增加一条数据

class _HomePageState extends State<HomePage>{
  int num = 0;
  List list = new List();
  
  @override
  Widget build(BuildContext context) {
    return ListView(
      children: <Widget>[
        Column(
          children: this.list.map((e){**重点**
            return Text(e);
          }).toList(),
        ),
        SizedBox(height:30.0),
        RaisedButton(
          child: Text("按钮"),
          onPressed: (){
            setState((){**重点**
              this.num ++;
              this.list.add("${this.num}条数据");
            });
          }
        )
      ],
    );
  }
}
image.png

20、BottomNavigationBar自定义底部导航栏组件,以及页面切换

BottomNavigationBar底部导航栏组件,它是属于Scaffold组件中的属性

属性 值类型 说明
items 一个为BottomNavigationBarItem的集合 items:[ BottomNavigationBarItem(icon: Icon(Icons.home),title: Text("首页"))]
onTap 点击导航栏的回调事件 onTap:(index){print(index);},
currentIndex 默认选中的下标 currentIndex:2
type 导航栏的类型:fixed、shifting type:BottomNavigationBarType.shifting
fixedColor 底部导航栏type为fixed时导航栏的颜色,如果为空的话默认使用ThemeData.primaryColor fixedColor:Colors.red
iconSize 导航栏图片的大小 iconSize:23
bottomNavigationBar: BottomNavigationBar(
          currentIndex : 1,
          onTap:(i){},
          items: [
            BottomNavigationBarItem(icon: Icon(Icons.home),title: Text("首页")),
            BottomNavigationBarItem(icon: Icon(Icons.list),title: Text("分类")),
            BottomNavigationBarItem(icon: Icon(Icons.settings),title: Text("设置")),
          ],
        ),

20-1、设置点击导航栏切换页面/选中状态

我们知道改变状态需要在StatefulWidget中实现,那么我们把整个Scaffold单独抽出成一个组件

class HomePage extends StatefulWidget{
  HomePage({Key key}):super(key:key);
  @override
  _HomePageState createState()=>_HomePageState();
}

class _HomePageState extends State<HomePage>{
  int index = 0;
  @override
  Widget build(BuildContext context) {
    
    return Scaffold(
        appBar: AppBar(title: Text("页面定位布局"),),
        body: MyBody(),
        bottomNavigationBar: BottomNavigationBar(
          currentIndex : this.index,
          onTap:(i){
            setState(() {
              this.index = i;
            });
          },
          items: [
            BottomNavigationBarItem(icon: Icon(Icons.home),title: Text("首页")),
            BottomNavigationBarItem(icon: Icon(Icons.list),title: Text("分类")),
            BottomNavigationBarItem(icon: Icon(Icons.settings),title: Text("设置")),
          ],
        ),
        drawer: Drawer(
          child: ListView(
            children: <Widget>[
              DrawerHeader(child: Text("Drawer"),decoration: BoxDecoration(color:Colors.pink),),
              ListTile(leading: Icon(Icons.settings),title: Text("设置"),),
            ],
          ),
        ),
      );
  }
}

20-2、利用bottomNavigationBar切换页面

我们在lib中新建一个pages文件夹,然后创建几个简单的Widget

import 'package:flutter/material.dart';
import './tabs/home.dart';
import './tabs/list.dart';
import './tabs/seting.dart';


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

  @override
  _TabsState createState()=>_TabsState();
}

class _TabsState extends State<Tabs>{

  int _curInedex = 0;

  List _pageList = [//页面数组
    HomePage(),
    ListPage(),
    SetingPage()
  ];

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(title:Text("导航栏的使用,切换")),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: this._curInedex,
        onTap: (i){
          setState((){
            this._curInedex = i;
          });
        },
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home),title: Text("首页")),
          BottomNavigationBarItem(icon: Icon(Icons.line_weight),title: Text("分类")),
          BottomNavigationBarItem(icon: Icon(Icons.settings),title: Text("设置")),
        ],
      ),
      drawer: Drawer(
        child: ListView(
          children: <Widget>[
            DrawerHeader(child: Text("抽屉组件"),decoration:BoxDecoration(color:Colors.pink)),
            ListTile(leading: Icon(Icons.settings),title: Text("设置"),)
          ],
        ),
      ),
      body: this._pageList[this._curInedex],重点
    ); 
  }
}

21、Flutter中的路由

Navigator.of()跳转某一页面,Navigator.pop()回到上一页面

Navigator.of(context).push(
  MaterialPageRoute(builder:()=>页面组件())
)

21-1、页面跳转传值

类似于vuerouter传值
非常简单,我们知道构造函数是可以传参的而我们的builder :()=>页面组件(这里是可以传参的)

Navigator.of(context).push(
  MaterialPageRoute(builder:()=>MyPage(title:"标题"));
)

class MyPage extends StatelessWidget{
  String title = "";
  MyPage({title:this.title})
  ...
}

22-2、路由命名跳转

使用Navigator.pushNamed()

如果要使用pushNamed方法那么需要在MaterialApp中定义好路由

  MaterialApp(
    title: "路由跳转",
    theme: ThemeData(primaryColor: Colors.pink),
    routes: {//这里的key可以随意起
      "/form":(BuildContext  context)=>FormPage(),
      "/serach":(BuildContext  context)=>SerachPage()
    },
    home: MyScaffold()
  );
  按钮中实现路由跳转
  onPressed:(){
    Navigator.pushNamed(context, "/serach");
  }

22-3、命名路由传值、路由验证

命名路由传值不能像构造参数传值那样,需要指定onGenerateRoute属性Flutter中文网onGenerateRoute属于MaterialApp的属性

路由拦截验证

假设我们要开发一个电商APP,当用户没有登录时可以看店铺、商品等信息,但交易记录、购物车、用户个人信息等页面需要登录后才能看。

首先我们定义路由Map
案例:我们超前使用本地存储功能获取userName
MaterialApp(
  routes: {
    "/login" : (BuildContext context)=>LoginPage(),
    "/home" :(BuildContext context)=>HomePage(), 
    "/card" :(BuildContext context)=>CardPage(), 
    ...
  },
  "onGenerateRoute":(RouteSettings setings){
    final String routeName = settings.name;//获取进入的路由名字,
    final prefs = await SharedPreferences.getInstance(); //本地存储获取
    final userName = prefs.getString("userName");
    if(routeName !="/login" && !userName.isEmpty){//如果用户名不存在那么我们跳转至登录页面
       Navigator.pushNamed(context, "/login");
    }
  }
);

命名路由传参

这里有个巨坑,如果此时MaterialApp还配置routes属性会报错;
切记,切记,切记

final _routes = {
    "/login" : (BuildContext context)=>LoginPage(),
    "/home" :(BuildContext context)=>HomePage(), 
    "/card" :(BuildContext context,{arguments})=>CardPage(infoCard:arguments),
    ...
  },
MaterialApp(
  "onGenerateRoute" : (RouteSettings  setings){
    final String name = settings.name;
    final Function pageBuilder = this._routes[name];
    if (pageBuilder != null) {
      if (settings.arguments != null) {
        // 如果透传了参数
        return MaterialPageRoute(
            builder: (context) =>
                pageBuilder(context, arguments: settings.arguments));
      } else {
        // 没有透传参数
        return MaterialPageRoute(builder: (context) => pageBuilder(context));
      }
    }
  }
)
然后我们需要在 CardPage页面中接收`infoCard`参数
class CardPage extends StatelessWidget{
  final infoCard;
  CardPage({this.infoCard});
  ...
}

配置根路由

我们开发Vue项目知道,一般会配置一个根路由
flutter中配置跟路由使用initialRoute属性,该属性属于MaterialApp,当使用了该属性时,在使用home属性可能会导致混乱

MaterialApp(
  initialRoute:"/home"
)

22-4、替换根路由

Navigator.pushReplacementNamed();
Navigator.of(context).pushAndRemoveUntil;
使用场景:当我们跳转页面中在跳转页面,例如当我们点击注册按钮进入注册页面,然后输入完信息之后,点击下一步,跳转第二步注册,然后点击注册完成按钮,返回的页面应该不是第一步中的注册页面,而是其实页面

      Column(
           mainAxisAlignment: MainAxisAlignment.center,
           children: <Widget>[
             Text("请输入手机号,密码,点击下一步继续完成注册操作"),
              RaisedButton(onPressed: (){
                //进入第二步骤注册页面
                Navigator.pushReplacementNamed(context, "/regSend");
              },child: Text("下一步"),)
           ],
         )
      Column(
           mainAxisAlignment: MainAxisAlignment.center,
           children: <Widget>[
             Text("注册完成"),
              RaisedButton(onPressed: (){
                //此时pop返回的不是第一步注册页面了,而是其实页面了
                Navigator.of(context).pop();
              },child: Text("完成注册"),)
           ],
         )

如果页面嵌套很多层,而每次都使用Navigator.pushReplacementNamed();保存根确实有些小麻烦,那么我们可以使用pushAndRemoveUntil方法,不管你嵌套多少层,直接使用该方法直接可返回跟路由

      Navigator.of(context).pushAndRemoveUntil(
        //这个案例是返回tab页面
        new MaterialPageRoute(builder: (context)=>Tabs(index:1)),
            (route)=>route == null);

23、floatingActionButton浮动按钮

floatingActionButton是属于Scaffold中的属性

floatingActionButton: FloatingActionButton(onPressed: (){
        Navigator.pop(context);//路由返回
      },child: Text("返回"),),

23-1 控制floatingActionButton的显示位置

floatingActionButtonLocation属性该属性是在Scaffold中的,
floatingActionButton是同级关系

Scaffold(
  floatingActionButton: FloatingActionButton(onPressed: (){},child: Icon(Icons.add),),
  floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,//底部中间
)

23-2实现底部导航的凸起按钮

思路:我们可以定义5个bottomNavigationBar然后使floatingActionButton盖住中间的那一个然后点击事件我们改变curIndex的值即可

image.png
Scaffold(
  floatingActionButton: Container(
        width: 62.0,
        height: 62.0,
        padding: EdgeInsets.all(3.0),//padding
        decoration: BoxDecoration(
          shape:BoxShape.circle,//圆形
          color: Colors.white,//白色
          boxShadow:[BoxShadow(color: Colors.black26, offset: Offset(0, 0),blurRadius: 2.0, spreadRadius: 0.0)]//阴影
        ),
        child: FloatingActionButton(onPressed:(){
          setState(() {
            this.curIndex = i;
          });
        },splashColor: null,elevation:0,child: Icon(Icons.add,size: 40.0,color: Color.fromRGBO(51, 51, 51, 1),),backgroundColor: Colors.yellow,),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home),title: Text("首页")),
          BottomNavigationBarItem(icon: Icon(Icons.list),title: Text("分类")),
          BottomNavigationBarItem(icon: Icon(Icons.text_fields),title: Text("卖闲置")),
          BottomNavigationBarItem(icon: Icon(Icons.shopping_cart),title: Text("购物车")),
          BottomNavigationBarItem(icon: Icon(Icons.people),title: Text("我的")),
        ],
        type:BottomNavigationBarType.fixed,
        currentIndex: this.curIndex,
        onTap: (i){
          setState(() {
            this.curIndex = i;
          });
        },
      ),
)

image.png

24、SingleChildScrollView

当我们软键盘弹起的时候,会盖住我们的内容,这是就会报越界,使用SingleChildScrollView页面会随高度改变而又滚动条

25、自定义AppBar以及Tab切换

在使用AppBar我们之前一直都是直接title属性带过,现在我们仔细看看它有哪些属性

属性 描述 用法
title 标题 title:Text("标题")当然也可以不是Text
leading AppBar左侧组件 leading:Icon(Icons.back)当然也可以不是Icon
actions 右侧组件集合 actions:[Icon(Icons.back),Icon(Icons.back)]
backgroundColor Appbar背景颜色 backgroundColor:Colors.blue
bottom 通常放TabBar,标题下面放一栏tab切换 bottom:TabBar(tabs: [ ])
iconTheme appbar中的图标统一样式 iconTheme: IconThemeData(color: Colors.pink,opacity:0.3,size:20),
textTheme 文字样式 textTheme : TextTheme(headline1:TextStyle(color: Colors.black))
centerTitle 标题是否居中 centerTitle:true
  Scaffold(
      appBar : AppBar(
        leading: IconButton(icon: Icon(Icons.pin_drop), onPressed: (){
          //这里我们使用了IconButton组件图标按钮点击返回根组件
          Navigator.pushNamed(context, "/");
        }),
        backgroundColor: Colors.blue,
        title: Text("AppBarDemo"),
        actions: [//右侧组件集合
          Icon(Icons.home),
          Text("电话")
        ],
        centerTitle : true,//标题居中
        iconTheme: IconThemeData(color: Colors.pink,opacity:1,size:30),
        textTheme : TextTheme(headline1:TextStyle(color: Colors.black),overline:TextStyle(color: Colors.black))
      )
    )
image.png

25-1Tab切换

进行Tab切换在Scaffold组件中需要包裹一层DefaultTabController组件

TabBar常用属性

属性 描述 用法
tabs tab的内容 tabs: [Tab(text: "tab1"),Tab(text: "tab2",) ]
controller TabController对象 controller:TabController()
isScrollable 是否可以滚动,当有多个Tab选项时就会被挤在一起,设置为true可以解决 isScrollable:false
indicatorColor 指示器颜色 indicatorColor:Colors.blue
indicatorWeight 指示器高度 indicatorWeight:2
indicatorPadding 指示器的padding indicatorPadding:EdgeInsets.all(10),
indicator 指示器的描述样式 indicator: Decoration(***)
labelColor 文字的颜色 labelColor:Colors.blue
labelStyle 文字的样式 labelStyle:TextStyle()
labelPadding 文字的padding labelPadding:EdgeInsets.all(10)
unselectedLabelColor 未选中的文字颜色 unselectedLabelColor:Colors.pink
unselectedLabelStyle 未选中的文字样式 lunselectedLabelStyle:TextStyle()
DefaultTabController(
  length:2,//定义有几个tab切换
  child:Scaffold(
    appBar:AppBar(
      title:Text("自定义Tab切换"),
       bottom:TaBar(
        tabs:[
          Tab(text:"音乐",icon:Icon(Icons.headset)),
          Tab(text:"推荐"),
        ],//tab切换
      ),
    ),
    body:TabBarView(//主内容需要用TabBarView包括
      children:[
        Center(child:Text("对应音乐")),//顺序对应bottom中的tabs顺序
        Center(child:Text("对应推荐")),
      ]
     )
  )
)
image.png

这个是单独页面定义自动以Tab切换,如果我们要在底部导航栏页面增加Tab切换怎么做呢。因为我们底部导航栏页面是分别抽出来公共Scaffold组件的,如果被DefaultTabController包裹,那么所有的底部导航栏页面都会增加Tab切换

Scaffold中可以继续嵌套Scaffold

DefaultTabController(length: 2, child: Scaffold(
      appBar: AppBar(
        leading: Icon(null),
        centerTitle: true,
        title: Row(//这里title我们改成了TaBar
          children: <Widget>[
            Expanded(child: TabBar(labelColor:Colors.blue,tabs: [
              Tab(text: "tab1"),
              Tab(text: "tab2",)
            ]))
          ],
        ),
      ),
      body: TabBarView(children: [
        Column(
          mainAxisAlignment:MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Text("页面传值跳转,类似于vue、Router跳转传值"),
            RaisedButton(onPressed: (){
              Navigator.of(context).push(
                MaterialPageRoute(builder: (context)=>FormPage(formInfo:{'id':"传值的12"}))
              );
            },child: Text("传值跳转"),)
          ],
        ),
        Center(child:Text("第二个tab2"))
      ]),
    )
    )
image.png

25-2 自定义TabBar

为什么我们要自定义呢?为什么不用上述的例子呢?用上述的例子我们无法监听tab切换的事件,而自定义我们可以监听
如果要使用自定义TabBar的话,组件必须是StatefulWidget动态组件并且需要实现SingleTickerProviderStateMixin

class TabBarController extends StatefulWidget{
 _TabBarControllerState createState()=> _TabBarControllerState();
}
//这里要实现SingleTickerProviderStateMixin
class _TabBarControllerState extends State<TabBarController> with SingleTickerProviderStateMixin{
  TabController _tabController;
  @override
  void initState(){
    this._tabController = TabController(length:2,vsync:this);
    this._tabController.addListener((){//监听切换事件
      print(this._tabController.index);//切换的下标
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold{
      appBar:AppBar(
        title:Text("TabBarController"),
        bottom:TabBar(tabs:[
            Tab(child:Text("tab1")),
            Tab(child:Text("tab2")),
          ], 
          controller:this._tabController//别忘了这一步
        )
      ),
      body:TabBarView(
        children : [
          Center(child:Text("切换至Tab1")),
          Center(child:Text("切换至Tab2")),
        ],
        controller:this._tabController//别忘了这一步TabBarView也需要加上
      )
    }
  }
}

26、Draw抽屉侧边栏

drawer是属于Scaffold中的属性左侧侧边栏
endDrawer是属于Scaffold中的属性右侧侧边栏

Scaffold(
  drawer: Drawer(
        child: ListView(
          children: <Widget>[
            DrawerHeader(child: Text("抽屉组件"),decoration:BoxDecoration(color:Colors.pink)),
            ListTile(leading: Icon(Icons.settings),title: Text("设置"),)
          ],
        ),
      ),
  endDrawer : Drawer(
        child: ListView(
          children: <Widget>[
            DrawerHeader(child: Text("抽屉组件"),decoration:BoxDecoration(color:Colors.pink)),
            ListTile(leading: Icon(Icons.settings),title: Text("设置"),)
          ],
        ),
      )
)
image.png

26-1使用UserAccountsDrawerHeader在侧边栏显示用户信息

属性 描述 用法
accountName 用户名 accountName:Text("十年之后")
accountEmail 可以称为用户描述寄语,官方是邮箱的意思 accountEmail:Text("简书写你所想")
currentAccountPicture 用户头像图片 currentAccountPicture:CircleAvatar(backgroundImage: NetworkImage(***)))
drawer: Drawer(
        child: ListView(
          children: <Widget>[
            UserAccountsDrawerHeader(
              accountName: Text("十年之后"), accountEmail: Text("简书,写你所想"),
              currentAccountPicture:CircleAvatar(backgroundImage: NetworkImage("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2586537036,779451914&fm=26&gp=0.jpg"),radius: 25.0,)
            ),
            ListTile(
              leading: Icon(Icons.settings),
              title: Text("设置"),
            )
          ],
        ),
      ),

27 ButtonBar按钮组

ButtonBar里面放N个按钮

属性 描述 用法
children 按钮组 children:[]
alignment 按钮组的对齐方式 alignment:MainAxisAlignment.start,
ButtonBar(
              alignment:MainAxisAlignment.start,
              children: <Widget>[
                FlatButton(onPressed: (){},child: Text("扁平化按钮"),color:Colors.blue),
                
              ],
            )

28表单

28-1TextFaild

输入表单类似input

属性 描述 应用
decoration - hintText 类似于前端placeholder提示信息 decoration:InputDecoration(hintText:"输入名字")
decoration - border 给输入框增加边框 decoration:InputDecoration(border:OutlineInputBorder())
maxLength 限制输入框输入的字符长度 maxLength:4限制只能输入4个字符
maxLines 多行文本,类似于textarea maxLines:4限制4行
obscureText 密码框 obscureText:true
controller 输入框里面输入的值 controller:TextEditingController(text:'十年之后')
decoration - labelText
labelText
decoration:InputDecoration(labelText:"用户名")
decoration - icon
表单图标
decoration:InputDecoration(icon: Icon(Icons.home))
onChanged 监听输入框文本改变的事件 onChanged:(val){print(val);}
TextField(
  obscureText : true,//密码
  maxLength : 14,//只能输入14个字符
  maxLines : 1,//只有一行
  decoration : InputDecoration(
    border:OutlineInputBorder(),//边框
    labelText:"密码",
    icon:Icon(Icons.format_indent_increase)
  )
)
image.png

28-1-1、TextFaild获取输入的值/修改值

利用TextFaild中的controller属性我们绑定值

var user_name = TextEditingController();
@initState(){
  user_name.text = "十年之后初始值";注意这里修改的是text的属性的值
}
TextField(
    controller:user_name,这里赋值
    decoration: InputDecoration(
    labelText: "用户名",
    contentPadding: EdgeInsets.all(3),
    border: OutlineInputBorder()
  ),
)

点击按钮获取表单输入的值

TextField(
    controller:user_name,这里赋值
    decoration: InputDecoration(
    labelText: "用户名",
    contentPadding: EdgeInsets.all(3),
    border: OutlineInputBorder()
  ),
)
RaisedButton(onPressed: (){
   print(this.user_name.text);注意这里是获取user_name中的text
},child: Text("获取表单的值"),)

点击修改输入框的值

RaisedButton(onPressed: (){
  setState(() {
    this.user_name.text = "十年之后Dart与Flutter";
  });
},child: Text("修改表单的值"),),

28-2 CheckBox多选框

属性 描述 应用
value 绑定的值 value:flag事先定义flag
onChanged 监听状态改变 onChanged:(val){setState((){flag=val;})}
activeColor 选中时的颜色 activeColor:Colors.blue
var flag = false;
Row(children:[
  Checkbox(value: flag, onChanged: (val){
   setState(() {
     this.flag = val;
   });
  }),
  Text(this.flag?"选中":"未选中")
])

28-2-1 CheckBoxListTitle

属性 描述 用法
selected 选中高亮 selected:判断当前枚举值是否等该枚举值就高亮

CheckBoxListTitleListTitle类似

CheckboxListTile(value: flag,onChanged: (val){
 setState(() {
  flag = val;
  });
 },title:Text("同意协议"),subtitle: Text("23456"),),
image.png

28-3 Radio单选框

Radio往往是成对出现的,怎么保证多个Radio是一个组呢?
这时候我们要用groupValue属性给多个Radio绑定同一个值

属性 描述 应用
value 该单选框的枚举值 value:1写上你的枚举值不用是变量
groupValue 绑定的值 groupValue:this.sex事先定义sex
onChanged 监听改变事件 onChanged:(val){setState((){sex=val;})}
             var sex = 1;
            Row(
              children: <Widget>[
                Text("男:"),
                Radio(value: 1, groupValue: this.sex, onChanged: (v){
                  setState(() {
                    this.sex = v;
                  });
                }),
                Text("女:"),
                Radio(value: 2, groupValue: this.sex, onChanged: (v){
                  setState(() {
                    this.sex = v;
                  });
                })
              ],
            )

28-3-1、RadioListTitle

属性 描述 用法
selected 选中高亮 selected:判断当前枚举值是否等该枚举值就高亮

CheckBoxListTitle类似

RadioListTile(value: 1, groupValue: this.a, onChanged: (v){
                    setState(() {
                      this.a = v;
                    });
                  },title: Text("爬山"),subtitle: Text("2020-06-02"),selected:this.a==1),
            RadioListTile(value: 2, groupValue: this.a, onChanged: (v){
                    setState(() {
                      this.a = v;
                    });
                  },title: Text("游泳"),subtitle: Text("2020-06-02"),selected:this.a==2),
image.png

28-4、Switch开关

Switch(value: b, onChanged: (v){
              setState(() {
                b =v;
              });
            })

28-4-1 SwitchListTitle

SwitchListTile(value: b, onChanged: (v){
                setState(() {
                  b =v;
                });
              },title: Text("开关"),subtitle: Text("描述"),)

28-5、练习表单

image.png

重点是利用数组循环map渲染兴趣

Scaffold(
        appBar: AppBar(title: Text("formDemo练习"),),
        body: Container(
            padding: EdgeInsets.all(20.0),
            child:  Column(
                children: <Widget>[
                    TextField(
                        controller: this.user_name,
                        onChanged: (v){
                            setState(() {
                              this.user_name.text = v;
                            });
                        },
                        decoration: InputDecoration(
                            labelText: "用户名",
                            border: OutlineInputBorder(),
                            contentPadding: EdgeInsets.all(3.0)
                        ),
                    ),
                    SizedBox(height: 10.0,),
                    Row(
                        children: <Widget>[
                            Text("性别"),
                            Radio(value: true, groupValue: this.sex, onChanged: this._setSex),
                            Text("男"),
                            Radio(value: false, groupValue: this.sex, onChanged: this._setSex),
                            Text("女"),
                        ],
                    ),
                    SizedBox(height: 10.0,),
                    Row(
                        children: this._listCheck(),
                    ),
                    SizedBox(height: 10.0,),
                    TextField(
                        controller: this.user_info,
                        maxLines: 4,
                        decoration: InputDecoration(
                            border: OutlineInputBorder(),
                            hintText:"用户信息"
                        ),
                        onChanged: (v){
                            setState(() {
                              this.user_info.text = v;
                            });
                        },
                    ),
                    SizedBox(height:10.0),
                    RaisedButton(onPressed: (){},child: Text("提交"),color: Colors.blue,textColor: Colors.white,)
                ],
            )
        )
    );

29、DateTime时间、时间组件

29-1、获取当前时间

DateTime.now()

print(DateTime.now())  2020-06-03 15:06:30.832249

29-2、获取时间戳

millisecondsSinceEpoch

print(DateTime.now().millisecondsSinceEpoch) 1591167990832

29-3、时间戳转化成日期对象

DateTime.fromMillisecondsSinceEpoch(时间戳)

DateTime date = DateTime.now() 获取当前时间
int timeStamp = date.millisecondsSinceEpoch;
print(DateTime.fromMillisecondsSinceEpoch(timeStamp))

29-4、转化成指定的年月日

使用第三方库date_format地址

1、在项目的跟目录pubspec.yaml中追加包

dependencies:
  date_format: ^1.0.8

2、在需要用到插件的文件中添加

import 'package:date_format/date_format.dart';

3、使用

DateTime date = DateTime.now();
print(formatDate(date, [yyyy, '年', mm, '月', dd,'日',' ',HH,':',nn,':',ss]));
2020年06月03日 15:23:43

29-5、flutter自带的日期组件

image.png

使用日期组件并获取选中后的值

DateTime date = DateTime.now();//获取当前日期
void _showDatePicker() async{//自定义显示日期方法 
 var result =  await showDatePicker(
    context : context,//上下文
    initialDate:date,//默认选中的日期
    firstDate:DateTime(1980),//日期组件的起始日期我们定在1980年
    lastDate:DateTime(2050),//日期组件的结束日期
  );
  print(result );//这个就是我们选中之后的值注意该方法为异步方法
  setState((){
    this.date = result;
  })
}
//然后我们在点击事件中调用这个自定义方法
InkWell(
  onTap:(){
    this._showDatePicker()//调用日期控件
  },
  child:Text("显示日期组件")
)

29-6、flutter自带的时间控件

image.png
DateTime time = DateTime.now();//获取当前日期
void _showTimePicker() async{//自定义显示日期方法 
 var result = await showTimePicker(
      context: context, 
      initialTime: TimeOfDay(hour:time.hour,minute:time.minute)//重点哦
  );
  print(result );//这个就是我们选中之后的值注意该方法为异步方法
  setState((){
    this.time = result;
  })
}
//然后我们在点击事件中调用这个自定义方法
InkWell(
  onTap:(){
    this._showTimePicker()//调用日期控件
  },
  child:Text("显示时间组件")
)

29-7、怎么把日期、时间控件转为中文

要用到flutter中的国际化,具体可以查看Flutter 中的国际化

29-8、第三方日期、时间组件

flutter_datetime_picker

  DatePicker.showDatePicker(context,
    showTitleActions: true,
    minTime: DateTime(2018, 3, 5),
    maxTime: DateTime(2019, 6, 7), onChanged: (date) {
      print('change $date');
    }, onConfirm: (date) {
     print('confirm $date');
    }, 
  currentTime: DateTime.now(), 
  locale: LocaleType.zh
);

30、InkWell组件

效果:给一些没有点击事件的组件增加事件的,
例如如果我们要给Text增加点击事件,以往我们只能套子button中,但现在不用

InkWell(
  child:Text("监听点击事件"),
  onTap:(){
    print(DateTime.now())
  }
)

31、第三方轮播图插件

flutter_swiper
Swiper需要包裹在Container中,并且需要设置宽高不然报错

Swiper(
        itemBuilder: (BuildContext context,int index){
          return new Image.network("http://via.placeholder.com/350x150",fit: BoxFit.fill,);
        },
        itemCount: 3,
        pagination: new SwiperPagination(),
        control: new SwiperControl(),
      ),
    );

32、各种DiaLog弹出层

AlertDialogSimpleDialogshowModalBottomSheet
这些DiaLog需要放在showDialog方法中
关闭遮罩Navigator.pop()

32-1、AlertDialog

void _showAlertDiaLog() async{
    var result = await showDialog(context: context,builder: (context){
      return AlertDialog(
        title: Text("提示信息"),
        content: Text("您确定要删除么?"),
        actions: <Widget>[
          RaisedButton(onPressed: (){
            print("确定");
            Navigator.pop(context,'传值给result');传值出去
          },child: Text("是"),),
          RaisedButton(onPressed: (){
            print("否");
            Navigator.pop(context);
          },child: Text("否"),)
        ],
      );
    });
    print(result);
  }
image.png

32-2、SimpleDialog

void _showSimpleDiaLog()async {
    var result = await showDialog(context: context,builder: (context){
      return SimpleDialog(
        title: Text("提示信息"),
        children: [
          SimpleDialogOption(
            onPressed:(){
              Navigator.pop(context,'测试');
            },
            child: Text("测试"),
          ),
          Divider(),
          SimpleDialogOption(
            onPressed:(){
              Navigator.pop(context,'测试1');
            },
            child: Text("测试1"),
          ),
          Divider(),
          SimpleDialogOption(
            onPressed:(){
              Navigator.pop(context,'测试2');
            },
            child: Text("测试2"),
          ),
          Divider(),
          //当然也可以是其他的组件
          InkWell(
            
            onTap: (){
              Navigator.pop(context,'InkWell组件');
            },
            child: Text("InkWell组件"),
          )
        ],
      );
    });
    print(result);
  }
image.png

32-3、showModalBottomSheet类似于购物车/时间选择器底部弹出框

他不用使用showDialog来弹出

image.png
void _showShowModalBottomSheet(){
    showModalBottomSheet(
      context: context,
      builder: (context){
        return Container(
          height: 250.0,//控制弹出的高度
          child: Column(
            children: <Widget>[
              ListTile(
                title: Text("分享A"),
                onTap: (){
                  Navigator.pop(context);
                },
              ),
              Divider(),
              ListTile(
                title: Text("分享B"),
                onTap: (){Navigator.pop(context);},
              ),
              Divider(),
              ListTile(
                title: Text("分享C"),
                onTap: (){Navigator.pop(context);},
              ),
            ],
          ),
        );
      }
    );
  }

32-4、使用第三方插件toast

image.png

fluttertoast

dependencies:
  fluttertoast: ^4.0.1
import 'package:fluttertoast/fluttertoast.dart';
void _showShowToast(){
      Fluttertoast.showToast(
        msg: "提示信息",
        gravity: ToastGravity.BOTTOM,
        timeInSecForIosWeb: 1,//设置停留时间 只在IOS端有效
        backgroundColor: Colors.black,
        textColor: Colors.white,
        fontSize: 16.0
      );
  }

33、flutter中网络请求

33-1、JSON字符串与flutter中的Map类型相互转换

需要调用import 'dart:convert';
jsonDecode 字符串json转为Map
jsonEncodeMap转为字符串

String list_json = '{"result":"测试","code":0}';
jsonDecode(list_json ); = >转为Map
Map map = {"result":"测试","code":0};
jsonEncode(map );

技巧

container圆形/阴影

Container(
  width:50.0,
  height:50.0,
  decoration:BoxDecoration(
    color:Colors.yellow,//背景图片
    shape:BoxShape.circle,//圆形
    boxShadow:[BoxShadow(color: Colors.black, offset: Offset(0, 0),blurRadius: 2.0, spreadRadius: 0.0)]//阴影
  )
)

width/height相对于父容器100%

double.infinity

container(
  height:double.infinity,
  height:double.infinity,
)

总结目前学的组件有哪些

名称 描述
MaterialApp 根组件
Scaffold 根组件中的home
Text 文本组件
Container 容器组件
ListView 列表组件
ListView.builder 列表组件中的循环动态生成
SizedBox 这个组件目的撑开组件与组件之间的距离
GridView.count()/GridView.builder() 栅格化布局
Padding 内边距组件EdgeInsets.only(bottom: 10.0)仅bottom增加边距
Row 横轴排列组件
Cloumn 纵轴排列组件
Expanded Flex布局组件
Image 图片组件
Icon 图标组件
Stack 对子元素统一定位
Align Stack一起配合使用,对多个子元素进行定位
Position Stack一起配合使用,对多个子元素进行定位
AspectRatio 定义子组件与父元素的宽高比例
Card 卡片,可以给容器加阴影效果
ClipOval 圆形组件
CircleAvatar 圆形组件含有backgroundImage
Wrap 流式布局,当子元素横轴铺满时自动换行至第二行
RaisedButton 凸起按钮
BottomNavigationBar 导航栏属于Scaffold中的参数/属性
floatingActionButton 浮动底部按钮导航栏属于Scaffold中的参数/属性,可以实现底部凸起导航按钮
SingleChildScrollView 解决键盘弹起覆盖内容而引起的越界错误
IconButton 图标按钮,注意此图标按钮不带字
RaisedButton.icon 带字的图标按钮
FlatButton 扁平化按钮,不带边框,不带阴影
OutlineButton 只带边框的按钮,没有背景色
BottonBar 按钮组
floatingActionButtonLocation 不是button控制floatingActionButton的位置
TextFaild 输入框
Divider() 直接使用效果是一根线条/分割线
CheckBox 复选框
CheckBoxListTitle() 带标题的复选框,可以包裹Container控制宽高
Radio() 单选框
RadioListTitle() 带标题的单选框,可以包裹Container控制宽高
Switch() 开关
SwitchListTitle() 带标题的开关,可以包裹Container控制宽高
InkWell 给一些没有点击事件的组件增加监听点击事件
showDiaLog() 用来显示弹出框
AlertDiaLog() 提示框
SimpleDialog() 提示框1
showModalBottomSheet() 底部弹出框/类似日期控件
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,277评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,777评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,946评论 0 245
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,271评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,636评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,767评论 1 221
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,989评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,733评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,457评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,674评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,155评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,518评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,160评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,114评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,898评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,822评论 2 280
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,705评论 2 273