flutter入门学习记录【一】

相关帖子收藏

windows 打包上传ios到app store

作为android开发者,直接用studio即可。
开发环境配置
中文文档
flutter入门电子书籍

  1. 打开setting,切换到plugin,然后搜索flutter,第一个就是,安装完重启。
    然后file>new的时候就能看到多了个new flutter project 。
    然后发现还得一个flutter SDK,那就去下呗

    image.png

    我的是window10系统,那就下对应的吧
    flutter SDK下载

  2. sdk下载成功,解压缩到比如D盘sdk目录下
    配置环境变量,path最后添加一个 sdk的bin目录路径
    环境变量中新增两个flutter访问的环境变量
    | PUB_HOSTED_URL | https://pub.flutter-io.cn
    | FLUTTER_STORAGE_BASE_URL | https://storage.flutter-io.cn

  3. 检测环境变量
    dos命令下输入flutter doctor
    然后看结果,红框部分错误。下边还有个错误,那不管了,我装了2个studio,只给一个安装了flutter插件,我也只用一个。


    image.png

发现问题

  1. Unable to locate Android SDK
    出现这个原因是环境变量没配置
    加上 ANDROID_HOME 对应的value就是Android的sdk路径

  2. Android license status unknown.
    证书问题,执行下边的命令
    flutter doctor --android-licenses
    结果返回个这个

C:\Windows\System32>flutter doctor --android-licenses
A newer version of the Android SDK is required. To update, run:
D:\sdk\tools\bin\sdkmanager --update

我都28.0.3了,不知道还往哪里新,是要29的preview版,还是28的要更新到最新,过分了啊


image.png

按照提示执行命令,然后好大一堆提示,然后出现个y/N,我输入Y没反应了。如果正常的话,会提示你N次输入yes or no,输入y就ok。

D:\sdk\tools\bin\sdkmanager --update
image.png

flutter doctor --verbose

家里更奇怪

家里安装提示都没问题

image.png

sutdio flutter插件也都装了,也重启了,可是file new 里看不到new flutter project工程。
搜个帖子解决 https://www.jianshu.com/p/ebaf065d7b1c
就是有个插件没勾上。

学习

英文文档
中文文档
github 地址

  1. 随便new一个工程看看
    结构如下,核心文件是lib目录下的dart文件
    最后2个红框的是配置文件,相关库


    image.png

知识:

  1. main函数使用了符号 => , 这是Dart中单行函数或方法的简写
  2. 2种常用的widget:StatelessWidget和StatefulWidget ,前者状态不可变,后者状态可以改变
    看几个简单的
    Icon,给个图片icon,给个颜色color,textDirection左右或者右到左,这个就是简单的展示图片,没有点击事件的
class Icon extends StatelessWidget {
  /// Creates an icon.
  ///
  /// The [size] and [color] default to the value given by the current [IconTheme].
  const Icon(
    this.icon, {
    Key key,
    this.size,
    this.color,
    this.semanticLabel,
    this.textDirection,
  }) : super(key: key);

IconButton 这个就带点击事件了

class IconButton extends StatelessWidget {
  const IconButton({
    Key key,
    this.iconSize = 24.0,
    this.padding = const EdgeInsets.all(8.0),
    this.alignment = Alignment.center,
    @required this.icon,//图片
    this.color,//图片染色用
    this.highlightColor,
    this.splashColor,
    this.disabledColor,
    @required this.onPressed,
    this.tooltip,//长按的提示文字
  })

然后看到个BackButton,可以看到返回的就是iconbutton,不过icon和press事件写好的而已

class BackButton extends StatelessWidget {
  const BackButton({ Key key, this.color }) : super(key: key);
  final Color color;
  @override
  Widget build(BuildContext context) {
    assert(debugCheckHasMaterialLocalizations(context));
    return IconButton(
      icon: const BackButtonIcon(),
      color: color,
      tooltip: MaterialLocalizations.of(context).backButtonTooltip,
      onPressed: () {
        Navigator.maybePop(context);
      },
    );
  }
}

看下BackButtonIcon 返回的就是个icon,只是根据平台返回不同的图片而已。

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

  /// Returns the appropriate "back" icon for the given `platform`.
  static IconData _getIconData(TargetPlatform platform) {
    switch (platform) {
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
        return Icons.arrow_back;
      case TargetPlatform.iOS:
        return Icons.arrow_back_ios;
    }
    assert(false);
    return null;
  }
  @override
  Widget build(BuildContext context) => Icon(_getIconData(Theme.of(context).platform));
}
  1. 在Dart语言中使用下划线前缀标识符,会强制其变成私有的。
  2. 创建一个StatefulWidget
    StatefulWidget 有个抽象方法要实现,返回的是一个State,完事我们需要写个State,这个有个抽象方法build返回我们需要的widget
class RandomWords extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return RandomWordsState();
  }
}

class RandomWordsState extends State<RandomWords> {
  @override
  Widget build(BuildContext context) {
    return _buildSuggestions();
  }
  1. 修改主题颜色
    在 MaterialApp下添加theme属性,ThemeData下好多属性的,自己可以点进去看看,试试,有的看名字大概就知道干啥的,有的还需要测试
    return new MaterialApp(
      title: "first demo",
      theme: ThemeData(
        primaryColor: Colors.white
brightness: Brightness.dark,//android的 白天夜晚模式
      ),
  1. 跳转页面
    如下,push里传个参数route即可
    Navigator.of(context).push(
      new MaterialPageRoute(builder: (context){

fullscreenDialog: true//默认是false,有个后退的箭头,如果改成true,是个叉号

整体的认知

android里一个页面是个activity,而在flutter里,一个页面就是个widget
所以你需要的就是写多个widget即可。
除了启动的main.dart外,你可以写N个其他的xxx.dart 文件,然后通过
import 'xxx.dart'; 导入即可,完事xxx里的类,变量啥的也就可以用了。
所以不同的页面可以写在不同的dart文件里。需要跳转的话,引用下即可。
先学习基本控件,至于跳转就那么几种方式,后边再学。

基本控件的简单学习

  1. Text
    就是简单的文本显示,类似TextView
    几个常用的属性
    overflow 文字过多显示不下的时候咋处理,有4种,下边有讲
    textAlign:文字的对齐方式,left,center,right就是字面意思,justify效果下边有图说明,
    还有start,end,和android的概念一样,是由textDirection决定的,ltr的话start就是left,rtl的话start就是right
    textScaleFactor:文字的缩放
    maxLines:行数限制
    softWrap:是否换行,默认不换行的,你文字再多也只显示一行,要换行得改为true
    structStyle:结构样式,里边2个比较像的参数leading和height,实际大小都是乘以font大小,现在说下他们的作用,详细的可以看源码里的注释
    leading:举例fontsize为10,leading为5,实际就是50,那么效果就是每行的上边和下边个加25,默认是null,比0大就有效
    height:和上边不太一样,这个是平分给baseline的上边和下边。默认值是1,比1小无效
strutStyle: StrutStyle(fontStyle: FontStyle.italic,leading: 5,height: 2,),
 Text(
          "textxxxxxxxxxx",
          overflow: TextOverflow.fade,
          textAlign: TextAlign.right,
          textScaleFactor: 0.5,
          maxLines: 1,
        )

除了ellipsis,其他3种生效需要设置单行

enum TextOverflow {
  /// Clip the overflowing text to fix its container.
  clip,

  /// Fade the overflowing text to transparent.
  fade,

  /// Use an ellipsis to indicate that the text has overflowed.
//不需要设置单行也能生效,末尾省略号
  ellipsis,

  /// Render overflowing text outside of its container.
  visible,
}

justify看效果图,分别是left和justify效果
紫色是容器背景,看下第一行文字,left的情况最后留白比较大,而justify留白没了,平分给其他文字间隔了,就是这个效果,简单来说,就是文字两端对齐,然后大家再平分多余的空白


image.png
image.png
            new Container(
              width: 380,
              color: Colors.purple,
              child:
              Text(
                "Stretch lines of text that end with a soft line break to fill the width of the container",
                textAlign: TextAlign.start,
              ),
            ),
富文本

用到了TextSpan,我还以为和android里一样,还能替换文字为图片啥的,貌似不是,这是文字,可以设置不同的style而已,还可以设置点击事件

style ://这里的style对下边的children也生效,除非children里的child重新设置了别的style属性
recognizer:GestureRecognizer抽象类,找下它的实现类
GestureRecognizer 里边可以看到它的4种子类
decoration:枚举类型,下划线,上划线,删除线,none
height:刚开始理解错了,以为设置text的高度了,看完注释发现这玩意就是个系数,乘以font的大小。
The height of this text span, as a multiple of the font size

image.png

        Text.rich(
          TextSpan(
              text: "text is what",
              style: TextStyle(decoration: TextDecoration.underline),
              recognizer: x,
              children: <TextSpan>[
                TextSpan(
                    text: "second",
                    style: TextStyle(color: Colors.red, height: 2,decoration: TextDecoration.lineThrough)),
                TextSpan(
                    text: "third",
                    style: TextStyle(color: Colors.purple, fontSize: 30,decoration: TextDecoration.overline))
              ]),
        ),
//
  MultiTapGestureRecognizer x =
      new MultiTapGestureRecognizer(kind: PointerDeviceKind.touch)
        ..onTap = (int pointer) {
        };
  1. Row ,Column
    这个类似android的线性布局,一个水平方向的,一个垂直方向的
    里边有个children属性,添加一些widget即可。
    主要看下前3个参数的作用
  Column({
    Key key,
    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
    MainAxisSize mainAxisSize = MainAxisSize.max,
    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
    TextDirection textDirection,
    VerticalDirection verticalDirection = VerticalDirection.down,
    TextBaseline textBaseline,
    List<Widget> children = const <Widget>[],
  })

mainAxisAlignment :child如何分布,start,end,center 大家一起居左或居右或居中
其他几个space文章后边有介绍,有图,比较好理解

enum MainAxisAlignment {
  start,
  end,
  center,
  spaceBetween,//容器两端没有space的,中间平分space
  spaceAround,//每个child 左右【对于row来讲】两边的space一样,换句话说,容器两边的space是1,那么child之间的space就是2
  spaceEvenly,//容器两端的space和child之间的space一样,大家均分
}

mainAxisSize :
这个比较好理解了,就是容器的大小咋算的,max就相当于match_parent,min就相党羽wrap_content
crossAxisAlignment :child之间的对齐方式吧

  1. Statck
    这个看起来像FrameLayout
    overflow:默认是clip,也就是超出容器就不可见了。如果改成visible,那么超出容器的child也是可见的。当然这个是对有具体的position的child生效的,对于没有设置position的child不生效的,non-positioned children 会自动进行缩放的。
    alignment:类似android里的gravity,没啥说的,枚举类很多,看自己需求
    fit:有3种,都是对于 non-positioned children来说的
    ①默认的loose,child比stack容器size还大,那么child会缩小,以child的中心点缩放。
    ②expand:咋说了,字面意思,放大,就是说stack容器的大小是100,完事里边child的大小是50,你看到的效果child可能是100
    ③passthrough:

child修改位置用Positioned容器包裹起来,然后设置大小位置之类的。overflow如果是默认的clip,可能设置个位置看不到
好歹是个容器,咋感觉都不能设置背景。设置背景得用到下边的Container。

            new Stack(
            alignment: AlignmentDirectional.topStart,
            fit: StackFit.expand,
              overflow: Overflow.visible,
              children: <Widget>[
                Icon(Icons.favorite),
                Positioned(child:Icon(Icons.ac_unit),left: 100,top: 100, ),

                IconButton(
                  icon: Icon(Icons.print),
                ),
                IconButton(
                  icon: Icon(Icons.save),
                ),
                Text("xxxxxxxxxxxxxxxxxxx",overflow: TextOverflow.fade,maxLines: 1,),
                Positioned(child: Text("zzzzzzzzzzzzzzzzzz",overflow: TextOverflow.fade,maxLines: 1,),left: 20,top: 20,width: 100,),

              ],
            )
  1. Container
    容器,有背景可以设置
    color:设置一个背景颜色
    decoration:和color冲突,不能都设置,也是用来装饰背景的。
    padding:内容的边界,一般用EdgeInsets来设置4个边的值
    margin:
    constraints:设置最小最大宽高的
            new Container(
//              color: Colors.purpleAccent,
              width: 300,
              height: 300,
padding: EdgeInsets.all(100),
              decoration: BoxDecoration(color: Colors.blue),
              child: Text("xxxxxxxxxx"),
            )
  1. IconButton
            IconButton(
              icon: Icon(Icons.save),
            ),
            Text("xxxxxxxx"),

const double _kMinButtonSize = 48.0;//button的size的最小值
  const IconButton({
    Key key,
    this.iconSize = 24.0,
    this.padding = const EdgeInsets.all(8.0),
    this.alignment = Alignment.center,
    @required this.icon,
    this.color,//icon的颜色
    this.highlightColor,//就是按压的时候背景色
    this.splashColor,//按压的时候波纹颜色
    this.disabledColor,//字面意思
    @required this.onPressed,
    this.tooltip,
  })

从效果图来看,iconButton,默认有个很大的padding的,下图那个波纹效果就能看出来。


image.png
  1. RaisedButton
    字面意思,凸起的button,换句话说带阴影的button


    image.png
            RaisedButton(
              color: Colors.teal,
              highlightColor: Colors.purple,
              onPressed: () {
                print('=============press');
              },
              textColor: Colors.white,
              child: Text("book"),
            ),

常用的一些属性的解释
Color textColor,// 文字颜色
Color disabledTextColor,//button disabled的时候文字颜色
Color color,//button默认的背景
Color disabledColor,//不可用的背景
Color highlightColor,//button,按压的时候的背景颜色
Color splashColor,//按压的时候波纹的颜色
shape://ShapeBorder,背景的形状
ShapeBorder的实现类可以看下 ShapeBorder

系统实现的几个shape

  1. CircleBorder(side:BorderSide(color: Colors.blueAccent,width: 3,style: BorderStyle.solid)),
    可以不带参数,就是把button弄成了圆形,
    参数side:BorderSide》color:边框的颜色,width:边框的线条宽度,style:可以不设置,就2种,还有个none,等于没有border了。


    image.png
  2. RoundedRectangleBorder
    圆角矩形。
    BorderSide:和上边的一样,不解释了,就是边框的效果处理
    borderRadius :这个设置矩形圆角的,可以设置4个角的圆角半径,有很多方法可以实例化
  const RoundedRectangleBorder({
    this.side = BorderSide.none,
    this.borderRadius = BorderRadius.zero,
  })

简单看下

BorderRadius.all(Radius radius)//设置4个角

//最终和上边的一样,设置4个角都是radius大小,radius的大小如果比button的高度一半还大,那么其实最大也就高度的一半,最终效果两边是个半圆。
  BorderRadius.circular(double radius) : this.all(
    Radius.circular(radius),
  );

//这个就是左上,左下用的一个radius,右上右下用的一个radius
  const BorderRadius.horizontal({
    Radius left = Radius.zero,
    Radius right = Radius.zero,
  })
//道理和上边一样,左上,右上一样,左下右下一样
  const BorderRadius.vertical({
    Radius top = Radius.zero,
    Radius bottom = Radius.zero,
  })
//默认4个角都是0,你可以单独为某个角重新设置个值
  const BorderRadius.only({
    this.topLeft = Radius.zero,
    this.topRight = Radius.zero,
    this.bottomLeft = Radius.zero,
    this.bottomRight = Radius.zero,
  });
image.png
  1. ContinuousRectangleBorder
    构造方法里的参数和上个RoundedRectangleBorder一模一样,可是效果不太一样
    同样的borderRadius 效果完全不一样
  const ContinuousRectangleBorder({
    this.side = BorderSide.none,
    this.borderRadius = BorderRadius.zero,
  })

ContinuousRectangleBorder(borderRadius: BorderRadius.circular(113)),

看下效果,上边的circular是113,很大了,效果如下,而如果是RoundedRectangleBorder,大于15就成半圆了。所以啊,这个具体咋画的不太清楚。英文解释这个类的
smooth continuous transitions between the straight sides and the rounded corners.


image.png
  1. BeveledRectangleBorder
    参数同上,只不过这里不是圆弧而是直线连起来
BeveledRectangleBorder(borderRadius: BorderRadius.circular(23))

效果图,radius太大就成了第二种效果了


image.png
  1. BoxBorder 抽象类,有2个实现类,这个只有border了,没有形状一说了。
    就是设置一圈的线条,下边两个差不多,BorderDirectional这玩意用的start,end,而不是left,right,所以和direction有点关系
    Border:
    BorderDirectional:
  const Border({
    this.top = BorderSide.none,
    this.right = BorderSide.none,
    this.bottom = BorderSide.none,
    this.left = BorderSide.none,
  })
  factory Border.all({
    Color color = const Color(0xFF000000),
    double width = 1.0,
    BorderStyle style = BorderStyle.solid,
  })
shape: Border.all()

BorderDirectional(top:BorderSide(color:Colors.purple)),
image.png
  1. InputBorder 抽象类,有2个实现类
    看下构造方法就大概知道设置了啥默认属性
  const OutlineInputBorder({//这个是一圈都有线
    BorderSide borderSide = const BorderSide(),
    this.borderRadius = const BorderRadius.all(Radius.circular(4.0)),
    this.gapPadding = 4.0,
  })

  const UnderlineInputBorder({//这玩意的border只有下边一条线
    BorderSide borderSide = const BorderSide(),
    this.borderRadius = const BorderRadius.only(
      topLeft: Radius.circular(4.0),
      topRight: Radius.circular(4.0),
    ),
  }) 

从效果来看,第一个类似RoundedRectangleBorder,多了个gapPadding的参数,其他类似
第二个可以设置4个角,默认的是上边2个角,完事它只显示下边一条线。

image.png
  1. StadiumBorder
    这个默认效果就是两边半圆的效果,如果你需要的是这种,就比较省事了.
    只有一个参数,可以设置下边框的颜色,线条粗细
const StadiumBorder({this.side = BorderSide.none}) 
image.png
EdgeInsetsGeometry

设置padding之类的时候经常要用到这个类,抽象的,有2个实现类
2个区别,一个用的left,right,一个用的start,end, 后者根据方向不同start和end代表的意思不同

  const EdgeInsets.all(double value)
    : left = value,
      top = value,
      right = value,
      bottom = value;
  const EdgeInsets.only({
    this.left = 0.0,
    this.top = 0.0,
    this.right = 0.0,
    this.bottom = 0.0,
  });

  const EdgeInsetsDirectional.only({
    this.start = 0.0,
    this.top = 0.0,
    this.end = 0.0,
    this.bottom = 0.0,
  });

Container

前边就简单说了下两个属性,这里看下构造方法把能用的属性都看下

  Container({
    Key key,
    this.alignment,
    this.padding,
    Color color,
    Decoration decoration,
    this.foregroundDecoration,
    double width,
    double height,
    BoxConstraints constraints,
    this.margin,
    this.transform,
    this.child,
  }) 
  1. alignment:AlignmentGeometry
    对齐方式,你也可以理解为重心,就是里边才child摆放位置在哪里,左上角,右上角,下边居中之类的
    2个实现类
    Alignment: x和y是百分比,左边界是-1,有边界是1,中心是0,绝对值比1大的就跑到容器外边去了
    A value of -1.0 corresponds to the leftmost edge. A value of 1.0 corresponds to the rightmost edge
 const Alignment(this.x, this.y)

AlignmentDirectional:
和Alignment差不多,构造方法都一样,x变成了start而已,还多了一堆静态变量可以用。

const AlignmentDirectional(this.start, this.y)
static const AlignmentDirectional topStart = AlignmentDirectional(-1.0, -1.0);
static const AlignmentDirectional topCenter = AlignmentDirectional(0.0, -1.0);
  1. padding:EdgeInsetsGeometry
    设置内容距离边界的距离
    2个实现类
    EdgeInsets
    看名字以及方法实现,大概就知道几个意思了,不解释了
const EdgeInsets.fromLTRB(this.left, this.top, this.right, this.bottom);

  const EdgeInsets.only({
    this.left = 0.0,
    this.top = 0.0,
    this.right = 0.0,
    this.bottom = 0.0,
  });

  const EdgeInsets.all(double value)
    : left = value,
      top = value,
      right = value,
      bottom = value;

  const EdgeInsets.symmetric({
    double vertical = 0.0,
    double horizontal = 0.0,
  }) : left = horizontal,
       top = vertical,
       right = horizontal,
       bottom = vertical;

  EdgeInsets.fromWindowPadding(ui.WindowPadding padding, double devicePixelRatio)
    : left = padding.left / devicePixelRatio,
      top = padding.top / devicePixelRatio,
      right = padding.right / devicePixelRatio,
      bottom = padding.bottom / devicePixelRatio;

  /// An [EdgeInsets] with zero offsets in each direction.
  static const EdgeInsets zero = EdgeInsets.only();

 static EdgeInsets lerp(EdgeInsets a, EdgeInsets b, double t)

EdgeInsetsDirectional


const EdgeInsetsDirectional.fromSTEB(this.start, this.top, this.end, this.bottom);

  const EdgeInsetsDirectional.only({
    this.start = 0.0,
    this.top = 0.0,
    this.end = 0.0,
    this.bottom = 0.0,
  });

  static const EdgeInsetsDirectional zero = EdgeInsetsDirectional.only();

  1. Decoration decoration
    设置容器的背景的,和color属性冲突,二选一
    几个实现类
    3.1. BoxDecoration 矩形的背景
  const BoxDecoration({
    this.color,//背景色
    this.image,//DecorationImage 提供一张图片,下边有构造方法,可以看下有啥参数
    this.border,//BoxBorder,上边有见过,有2个子类,画边框线条的,可以设置颜色,线条宽度
    this.borderRadius,//下边的shape为矩形的时候才需要,如果为圆形的话这个不可以设置。
    this.boxShadow,//背景的阴影设置
    this.gradient,//设置背景的渐变色,这个设置了,上边的color就无效了
    this.backgroundBlendMode,//
    this.shape = BoxShape.rectangle,//就两种,矩形或者圆形
  }) 
//DecorationImage 提供一张图片
  const DecorationImage({
    @required this.image,//提供一张图片
    this.colorFilter,
    this.fit,
    this.alignment = Alignment.center,
    this.centerSlice,
    this.repeat = ImageRepeat.noRepeat,
    this.matchTextDirection = false,
  })
image:ImageProvider

常用的2种实现,应用内图片和网络图片

AssetImage("images/file.png")
NetworkImage("url....")
List<BoxShadow> boxShadow

设置阴影
BoxShadow:可以看到有3个参数,和android一样,颜色,偏移量,模糊半径
对比一下效果图,可以看到集合是按照顺序一层一层画的,所以集合前边的偏移量应该大,后边的偏移量应该小,否则后边的就把前边的盖住了

              boxShadow: <BoxShadow>[
                BoxShadow(
                  color: Colors.amber,
                  offset: Offset(9, 9),
                ),
                BoxShadow(
                    color: Colors.indigoAccent,
                    offset: Offset(5, 5),
                    blurRadius: 1),
                BoxShadow(
                    color: Colors.cyan, offset: Offset(2, 2), blurRadius: 2),
              ],
image.png
Gradient

几个子类如下
This is an interface that allows [LinearGradient], [RadialGradient], and [SweepGradient]
上图就是线性的效果,下边是代码,从左顶点到右下顶点
stops:数组长度要和colors一样,从0到1,其实就是标示颜色的分布位置,如果第一个stops不是0,那就会弄个0,并用colors的第一个颜色作为起点,如果stops的最后一个不是1,那么就会用1和colors的最后一个颜色作为终点
tileMode:重复模式3种和android一个样,repeat就是重复,mirror就是镜像,还有个clamp拉伸最后一个色值

              gradient: LinearGradient(
                  colors: <Color>[
                    Colors.indigoAccent,
                    Colors.purple,
                    Colors.deepOrangeAccent
                  ],
                  stops: <double>[
                    0,
                    0.8,
                    1
                  ],
                  tileMode: TileMode.repeated,
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight),

RadialGradient
一圈一圈的
radius :一个分数,以容器的宽高里小的那个做参考,颜色1.0的位置,

  const RadialGradient({
    this.center = Alignment.center,
    this.radius = 0.5,
    @required List<Color> colors,
    List<double> stops,
    this.tileMode = TileMode.clamp,
    this.focal,
    this.focalRadius = 0.0,
  }) 

下图是tilemode为repeat的效果图,radius默认是0.5,刚好是宽度的一半


image.png

SweepGradient
射线的效果,向外发散

  const SweepGradient({
    this.center = Alignment.center,
    this.startAngle = 0.0,
    this.endAngle = math.pi * 2,
    @required List<Color> colors,
    List<double> stops,
    this.tileMode = TileMode.clamp,
  })

如下图 repeat模式,startAngle为0,end为PI的效果,可以看到0到180度颜色渐变,180到360repeat


image.png
colorFilter

染色,这里是给image染色,主要是看blendMode,根据这个,效果不同

colorFilter: ColorFilter.mode(color, BlendMode.srcOut)

####### BlendMode
这个百度搜一下,有效果图,
简单举例说下,比如这里用到的,
file.png图片就是destination,colorFilter里的颜色,就是source

DecorationImage(
                  image: AssetImage('images/file.png'),
                  colorFilter: ColorFilter.mode(Colors.deepOrangeAccent, BlendMode.dst))),

dst: 只保留destination,source被丢弃, 效果就是显示file图片,染色无效
src:和dst相反,这个值保留source,destination被丢弃,上边代码的效果就是就剩个颜色了
srcOver:先画destination,再画source,都保留,上边代码的情况,如果source这个颜色没有透明度,下边file图片就看不见了,这个属性是默认值
dstOver:同上,不过这次先画source,再画destination
srcIn: 两者取交集,就是下图这种,周围是空白的,到时候中间的树叶就可以染色了


image.png

dstIn:和上边一样,不过这个destination图片在上层,而source在下层,虽然和上边都是取交集,不过最终效果和原图没太大变化,颜色在图片下边了,被挡住了。
srcOut:显示的是rouce,抠除和destination相交的部分,树叶效果就是树叶成透明的了,其他部分有颜色
dstOut:显示的是destination,抠除和source相交的部分,比如上边的树叶,相交部分是树叶,抠除以后就成树叶外边的空白了,所以撒都不显示了
其他的不写了,需要再研究,看多了头疼。

3.2 ShapeDecoration
比boxDecoration可以有更多的形状,其他的参数意思差不多

  const ShapeDecoration({
    this.color,
    this.image,
    this.gradient,
    this.shadows,
    @required this.shape,//上边有讲过ShapeBorder的子类,
  })

3.3 FlutterLogoDecoration
看名字,就是使用了app的logo图片当背景了
FlutterLogoStyle:markOnly 只显示logo,后两种还显示label,一个水平显示一个垂直显示而已

  const FlutterLogoDecoration({
    this.lightColor = const Color(0xFF42A5F5), // Colors.blue[400]
    this.darkColor = const Color(0xFF0D47A1), // Colors.blue[900]
    this.textColor = const Color(0xFF616161),
    this.style = FlutterLogoStyle.markOnly,
    this.margin = EdgeInsets.zero,
  })
enum FlutterLogoStyle {
  /// Show only Flutter's logo, not the "Flutter" label.
  ///
  /// This is the default behavior for [FlutterLogoDecoration] objects.
  markOnly,

  /// Show Flutter's logo on the left, and the "Flutter" label to its right.
  horizontal,

  /// Show Flutter's logo above the "Flutter" label.
  stacked,
}
stacked效果,垂直logo加label

3.4 UnderlineTabIndicator
顾名思义,就是下边画条线,看构造方法,默认是条白线,

  const UnderlineTabIndicator({
    this.borderSide = const BorderSide(width: 2.0, color: Colors.white),
    this.insets = EdgeInsets.zero,
  })

改了下颜色


image.png

Image

image
这个比较坑了,如下图有5种构造方法,其实就是5种获取图片文件的方法

image.png

除了network简单,传个路径完事,其他几个都不简单,只好百度搜下,
image File获取

  1. asset
    在工程目录下新建个文件夹,名字随便起,然后里边放上图片,其实文件夹你建在哪都行,你路径写对就可以
    image.png

    然后打开pubspec.yaml文件,如下图,在flutter标签下添加assets标签
    前边有个空格,反正就是和上边的uses-material-design对齐
    然后下边横杠开头 加上图片的路径,相对当前文件的相对路径,
# The following section is specific to Flutter.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #  - images/a_dot_burr.jpeg
  #  - images/a_dot_ham.jpeg
  assets:
  - images/   整个images目录下的图片,可以这样写,斜杠结尾即可
  - images/set_select.png
  - android/pic/file.png
  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware.

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

代码里使用就简单了,把路径放进来即可

Image.asset('android/pic/file.png',),

上边的assets写的不太好,如果你images目录下多张图片,总不能一个一个的写进去吧,写个文件夹目录就行了,带斜杠

 assets:
  - images/
File

File

使用的时候必须先导入库

import 'dart:io';

然后File的路径咋办,首先得拿到sdcard的目录吧,这里借用第三方库
path_provider
把库添加到pubspec.yaml文件下

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  english_words: ^3.1.0
  path_provider: ^1.1.0

然后dart文件里导入

import 'package:path_provider/path_provider.dart';

之后就可以用了

class LocalImage extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    return LocalState();
  }
}

class LocalState extends State<LocalImage>{
  @override
  Widget build(BuildContext context) {
    _getLocalFile();
    return Image.file(File("$_storageDir/Dollar.png"),width: 50,height: 50);
  }
  String _storageDir = '';
  _getLocalFile() async {
    String appDir = (await getApplicationDocumentsDirectory()).path;
//    String supportDir=(await getApplicationSupportDirectory()).path;//这个是ios用的
    String tempDir = (await getTemporaryDirectory()).path;
    String storageDir = (await getExternalStorageDirectory()).path;
    print('sd path=====$appDir=========$tempDir====$storageDir');
    setState(() {
      _storageDir = storageDir;
    });
    return storageDir;
  }
}

打印路径如下

/data/user/0/com.charlie.flutter_first_app/app_flutter
/data/user/0/com.charlie.flutter_first_app/cache
/storage/emulated/0

TabBar

类似android里的TabLayout

TabBar(
            labelColor: Colors.red,
            controller: TabController(initialIndex: 0,length: choices.length, vsync: TestVSync() ),
            onTap: (index){
              print('bottom onTap===${index}');
            },
            isScrollable: false,
            indicatorColor: Colors.purple,
            tabs: choices.map((Choice choice) {
              return new Tab(
                text: choice.title,
                icon: Icon(choice.icon),
              );
            }).toList()),

看下有用的属性

  const TabBar({
    Key key,
    @required this.tabs,//一个集合widget,这里一般用Tab
    this.controller,//控制器,有3个参数,初始化选中哪个,tabbar的长度,TickerProvider:用那个系统的TestVSync即可
    this.isScrollable = false,//可滚动的tab大小是wrap的,不可滚动tab是平分整个tabbar宽度的
    this.indicatorColor,//游标颜色
    this.indicatorWeight = 2.0,//游标的高度
    this.indicatorPadding = EdgeInsets.zero,//默认游标是在最下边的
    this.indicator,//游标除了默认的线条,这里可以自定义一个Decoration
    this.indicatorSize,//TabBarIndicatorSize枚举类型,2种,一种是整个tab的大小一样,一种是和tab里的lable大小一样
    this.labelColor,//图片文字的颜色
    this.labelStyle,
    this.labelPadding,
    this.unselectedLabelColor,//可以单独设置未选中的颜色,否则就是用上边labelColor变淡一点
    this.unselectedLabelStyle,
    this.dragStartBehavior = DragStartBehavior.start,//没看懂,以后知道了再修改
    this.onTap,//没个tab点击的时候回调,回调里传的就是索引
  })

自定义游标外观:indicator
有以下几种,默认的就是最后昂UnderlineTabIndicator

  1. 矩形
indicator: BoxDecoration(shape: BoxShape.rectangle,border: Border.all(color: Colors.cyan),color: Colors.amberAccent),
image.png
  1. 圆形
indicator: BoxDecoration(shape: BoxShape.circle,border: Border.all(color: Colors.cyan),color: Colors.amberAccent),
image.png
  1. FlutterLogo 这个估计没人用,
indicator: FlutterLogoDecoration()
image.png
  1. ShapeDecoration
indicator: ShapeDecoration(shape: Border.all(color: Colors.deepOrange,)+Border.all(color: Colors.lightGreenAccent),
          image: DecorationImage(image: AssetImage("images/l.png")))
//上边的box也可以加张图片的
indicator: BoxDecoration(image: DecorationImage(image: AssetImage("images/l.png"))),
image.png
TabBarIndicatorSize的lable效果.png

相关的类

TabPageSelector(
                  controller: TabController(
                      initialIndex: selectIndex,
                      length: choices.length,
                      vsync: TestVSync()),
                )),
image.png

属性说明

  const TabPageSelector({
    Key key,
    this.controller,
    this.indicatorSize = 12.0,
    this.color,//这个是中间空心的颜色,默认不设置就是透明的
    this.selectedColor,//这个是选中的那个空心的颜色,以及圈圈外圈的颜色
  })
image.png

TabBarView

和android里的viewpager差不多,要和上边的tabbar互动,两者用同一个controller就可以。

        Container(
          height: 450,
          child: TabBarView(
            children: <Widget>[
              Container(
                width: 300,
                height: 400,
                child: Text('first view'),
                color: Colors.deepOrange,
              ),
              Container(
                width: 400,
                height: 400,
                color: Colors.blue,
                child: FlutterLogo(
                  size: 200,
                ),
              ),
              Container(
                width: 400,
                height: 300,
                color: Colors.pinkAccent,
                child: Icon(Icons.share),
              )
            ],
            controller:
                TabController(initialIndex: index, length: 3, vsync: xxxP()),
          ),
physics: AlwaysScrollableScrollPhysics()
        )

//physics
NeverScrollableScrollPhysics:没法手动滑动了
AlwaysScrollableScrollPhysics:可以滑动
BouncingScrollPhysics:IOS的效果,回弹,android貌似没效果
ClampingScrollPhysics:没看出和always有啥太大的区别

Material Components Widgets

    return new MaterialApp(
      theme: ThemeData(
        brightness: Brightness.light,
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(leading: BackButton(),title: Text("first page"),centerTitle: true,),
        body: lv,
//        floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
//        floatingActionButton: FloatingActionButton(onPressed: (){},child: Icon(Icons.add),),
//        floatingActionButtonAnimator: FloatingActionButtonAnimator.scaling,
      ),
    );
  1. FloatingActionButton
    FloatingActionButton
      floatingActionButton: FloatingActionButton(
        onPressed: null,
        child: Icon(Icons.close),
        backgroundColor: Colors.red,
      ),

另外一种带文字的

      floatingActionButton: FloatingActionButton.extended(
        onPressed: null,
        icon: Icon(Icons.close),
        label: Text("close"),
        backgroundColor: Colors.red,
      ),
image.png

完事相关的还有这个按钮的位置设置,默认是在右下角的

floatingActionButtonLocation: FloatingActionButtonLocation.startTop

miniStartTop:和startTop基本一样,感觉左边的间距稍微小了那么一点点而已
startTop 是左上角,中心在appbar的底部
endTop 是在右上角,和startTop一个右边,一个左边


image.png

centerFloat :是在底部中间的位置,在bottomNavigationBar上边【如果有设置的话】
endFloat:同上,不过是在右边


image.png

endDocked:下图效果,中心线在bottom的顶部
centerDocked:同理,居中而已


image.png
  1. PopupMenuButton
    PopupMenuButton
    其实就是个按钮,点击以后弹出一个弹框里边是列表可以选
    image.png

看下参数
child和icon最多只能设置一个,替换默认的省略号图标,如果这2个你都设置,那代码就挂了。

  const PopupMenuButton({
    Key key,
    @required this.itemBuilder, //这玩意返回一个PopupMenuEntry类型的集合
    this.initialValue,
    this.onSelected,//选中哪个function里返回的就是那个对象
    this.onCanceled,//没有选择,点击外部消失
    this.tooltip,
    this.elevation = 8.0,
    this.padding = const EdgeInsets.all(8.0),
    this.child,//child和icon最多只能设置一个,都不设置的话默认有个三个点的图片
    this.icon,
    this.offset = Offset.zero,
  })

demo如下
首先这玩意需要一个泛型声明数据的类型

// This is the type used by the popup menu below.
enum WhyFarther { harder, smarter, selfStarter, tradingCharter }

// This menu button widget updates a _selection field (of type WhyFarther,
// not shown here).
PopupMenuButton<WhyFarther>(
  onSelected: (WhyFarther result) { setState(() { _selection = result; }); },
  itemBuilder: (BuildContext context) => <PopupMenuEntry<WhyFarther>>[
    const PopupMenuItem<WhyFarther>(
      value: WhyFarther.harder,
      child: Text('Working a lot harder'),
    ),
    const PopupMenuItem<WhyFarther>(
      value: WhyFarther.smarter,
      child: Text('Being a lot smarter'),
    ),
    const PopupMenuItem<WhyFarther>(
      value: WhyFarther.selfStarter,
      child: Text('Being a self-starter'),
    ),
    const PopupMenuItem<WhyFarther>(
      value: WhyFarther.tradingCharter,
      child: Text('Placed in charge of trading charter'),
    ),
  ],
)

常见的PopupMenuEntry
PopupMenuItem
PopupMenuDivider:就是一条线,如下图
CheckedPopupMenuItem:有个checked状态,前边多个对号,child 用的flatbutton,可以看到用的背景色是disable颜色


image.png
image.png

实例体验

image.png

整体分割成4部分:图片,中间的文字,3个按钮文字,最后边的内容
用listview来添加这些view

  1. 图片
    复习下,工程里图片的添加过程
    工程根目录下新建个文件夹xxx,完事把图片放进去
    然后打开pubspec.yaml文件
    打开fultter标签下的assets标签, 添加 - xxx/ 表示xxx目录下的所有图片
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #  - images/a_dot_burr.jpeg
  #  - images/a_dot_ham.jpeg
  assets:
  - images/
  - android/pic/file.png

看下image.asset的一些常用属性

  Image.asset(
    String name, {//图片地址
    Key key,
    AssetBundle bundle,
    this.semanticLabel,//这个就是注释文字了,类似android里的description吧
    this.excludeFromSemantics = false,
    double scale,
    this.width,//宽高
    this.height,
    this.color,//染色的颜色
    this.colorBlendMode,//染色的模式
    this.fit,//图片的展示方式,拉伸,裁剪之类的,下边会有效果
    this.alignment = Alignment.center,//默认居中的,可以修改
    this.repeat = ImageRepeat.noRepeat,//如果图片很小的话,可以repeat
    this.centerSlice,
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
    String package,
    this.filterQuality = FilterQuality.low,
  })

看下repeat的效果

        Image.asset(
          'images/imager.png',
          height: 240,
          fit: BoxFit.none,
          repeat: ImageRepeat.repeatX,
        ),

fit模式是none,所以图片大小就是红框的部分,
然后repeat用的repeatX,所有两边又有了部分重复的图片,因为这里图片很大,高度已经铺满了,如果高度没铺满是下图这种。
下边2种效果图alignment 都 是默认的center,你可以改成left,这样重复就都是右边,下边了,类似地图那种没加载出来的小方框效果


image.png

image.png

4种repeat很好理解,单方向repeat,上下左右重复,不重复

enum ImageRepeat {
  /// Repeat the image in both the x and y directions until the box is filled.
  repeat,

  /// Repeat the image in the x direction until the box is filled horizontally.
  repeatX,

  /// Repeat the image in the y direction until the box is filled vertically.
  repeatY,

  /// Leave uncovered portions of the box transparent.
  noRepeat,
}
  1. Container
    如果要margin或者padding,那么就需要这个容器包裹了


    image.png
  2. Expanded
    有个flex,类似线性布局里的weight,里边包裹一个child
  const Expanded({
    Key key,
    int flex = 1,
    @required Widget child,
  })
  1. crossAxisAlignment
    内容的布局位置,和gravity有点类似。
Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
enum CrossAxisAlignment {
  /// Place the children with their start edge aligned with the start side of
  /// the cross axis.
  ///
  /// For example, in a column (a flex with a vertical axis) whose
  /// [TextDirection] is [TextDirection.ltr], this aligns the left edge of the
  /// children along the left edge of the column.
  ///
  /// If this value is used in a horizontal direction, a [TextDirection] must be
  /// available to determine if the start is the left or the right.
  ///
  /// If this value is used in a vertical direction, a [VerticalDirection] must be
  /// available to determine if the start is the top or the bottom.
  start,

  /// Place the children as close to the end of the cross axis as possible.
  ///
  /// For example, in a column (a flex with a vertical axis) whose
  /// [TextDirection] is [TextDirection.ltr], this aligns the right edge of the
  /// children along the right edge of the column.
  ///
  /// If this value is used in a horizontal direction, a [TextDirection] must be
  /// available to determine if the end is the left or the right.
  ///
  /// If this value is used in a vertical direction, a [VerticalDirection] must be
  /// available to determine if the end is the top or the bottom.
  end,

  /// Place the children so that their centers align with the middle of the
  /// cross axis.
  ///
  /// This is the default cross-axis alignment.
  center,

  /// Require the children to fill the cross axis.
  ///
  /// This causes the constraints passed to the children to be tight in the
  /// cross axis.
  stretch,

  /// Place the children along the cross axis such that their baselines match.
  ///
  /// If the main axis is vertical, then this value is treated like [start]
  /// (since baselines are always horizontal).
  baseline,
}
  1. mainAxisAlignment
Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: <Widget>[
        buildButtonColumn(context, Icons.call, "Call"),
        buildButtonColumn(context, Icons.near_me, "Location"),
        buildButtonColumn(context, Icons.share, "Share"),
      ],
    );

看下枚举的说明

enum MainAxisAlignment {
  /// Place the children as close to the start of the main axis as possible.
  ///
  /// If this value is used in a horizontal direction, a [TextDirection] must be
  /// available to determine if the start is the left or the right.
  ///
  /// If this value is used in a vertical direction, a [VerticalDirection] must be
  /// available to determine if the start is the top or the bottom.
  start,

  /// Place the children as close to the end of the main axis as possible.
  ///
  /// If this value is used in a horizontal direction, a [TextDirection] must be
  /// available to determine if the end is the left or the right.
  ///
  /// If this value is used in a vertical direction, a [VerticalDirection] must be
  /// available to determine if the end is the top or the bottom.
  end,

  /// Place the children as close to the middle of the main axis as possible.
  center,

  /// Place the free space evenly between the children.
  spaceBetween,

  /// Place the free space evenly between the children as well as half of that
  /// space before and after the first and last child.
  spaceAround,

  /// Place the free space evenly between the children as well as before and
  /// after the first and last child.
  spaceEvenly,
}

MainAxisAlignment.spaceBetween
child之间平分空白


image.png

MainAxisAlignment.spaceEvenly
两边和中间,一起平分空白部分,如下图,间隔一样


image.png

MainAxisAlignment.spaceAround
每个child左右的space都一样,
image.png

start,end,center
下图是start的效果,end,center类似,child是挨着的,居中或者左右


image.png

知识

一般来说, app没有使用Scaffold【materila组件】的话,会有一个黑色的背景和一个默认为黑色的文本颜色
所以啊,还是使用scaffold作为根组件比较好
否则,自己修改背景

Container(
      decoration: new BoxDecoration(color: Colors.white)

犯了个致命的错误,害我检查半天不知道咋回事,onPressed后边的方法,就是个方法名字,我不小心加了个括号,它也不提示,我说咋点击没反应,因为onPress后边跟的是个无参无返回的方法

onPressed: _loveClick

页面里添加个组件,想限制下宽高,然后就套个container,设置宽比如100,结果发现无效,宽还是铺满屏幕的,那就搜下咋解决吧
container 宽高无效的问题
按照作者的,container外边再套个column或者row就ok拉,

其他一下系统的show方法

  1. showAboutDialog
            showAboutDialog(
                context: context,
                applicationName: "app name",
                applicationVersion: "app version",
                applicationIcon: Icon(Icons.directions_car),
                applicationLegalese: "legaless..",
            children: <Widget>[
              Text("text1"),
              Text("text2"),
              Text("text3"),
            ]);
image.png
  1. showTimePicker
    弹个时间选择器
    Future<TimeOfDay> showTimePicker()方法返回的是一个Future
          var re=showTimePicker(context: context, initialTime: TimeOfDay(hour: 20, minute: 20));
            re.then((TimeOfDay time){
              print('then====$time');//点击cancel的话这里返回null,点击ok返回的是对应的时间
            });
            re.whenComplete((){
             
            });
image.png
  1. showDatePicker
    日期选择器
            var rrr = showDatePicker(
                context: context,
                initialDate: DateTime.now(),
                firstDate: DateTime.now().subtract(Duration(seconds: 1)),//最小时间得比初始化时间小或者等于
                lastDate: DateTime(DateTime.now().year, 8, 10));

            rrr.then((DateTime date) {
              print('date====$date');//点击cancel,返回date为null
            }).whenComplete(() {
              print('complete=====');
            });

DateTime的几个方法

DateTime.now() //当前时间
.subtract(Duration(seconds: 1)//减去多少时间
.add(Duration(seconds: 1)//加上多少时间

  1. showGeneralDialog
    自定义的对话框,下边这些值好像都必须写,要不就会异常或者不显示
    barrierDismissible:点击外部是否消失
    barrierLabel:类似android里的tab吧,不知道,反正不设置就会异常
    barrierColor:弹框外围的颜色,默认是透明的
    transitionDuration:对话框显示或者隐藏的动画时间
    pageBuilder:返回一个widget用来显示

            showGeneralDialog(
              context: context,
              barrierDismissible: true,
              barrierLabel: "lable....",
              barrierColor: Colors.greenAccent,
              transitionDuration: Duration(seconds: 1),
              pageBuilder: (BuildContext context, Animation<double> animation,
                  Animation<double> secondaryAnimation) {
                return Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Container(
                      width: 200,
                      height: 200,
                      child: Text("content....",style: TextStyle(color: Colors.pinkAccent,),),
                    ),
                    FlatButton.icon(onPressed: (){
                      Navigator.of(context, rootNavigator: true).pop("ok");
                    }, icon: Icon(Icons.looks), label:Text("ok")),
                  ],
                );
              },
            ).then(
              (value) {
                print('then=====$value');//没有值的话返回的就是null
              },
            ).whenComplete(() {
              print('complete=========');
            });
image.png

其实对话框都用的push,所以我们要返回数据或者隐藏对话框,就对应的pop就行了

Future<T> showGeneralDialog<T>({
//.....
return Navigator.of(context, rootNavigator: true).push<T>

隐藏对话框
可以pop一个对象回去,在then方法里就能接收到

Navigator.of(context, rootNavigator: true).pop("ok"); 
  1. showMenu
    下拉菜单选择
            showMenu(context: context, position: RelativeRect.fromLTRB(100, 200, 50, 50), items: choices.map((choice) {
              return CheckedPopupMenuItem<Choice>(
                child: FlatButton.icon(onPressed: null, icon: Icon(choice.icon), label:Text(choice.title),color: Colors.pinkAccent,disabledColor: Colors.purpleAccent,),
                value: choice,
                checked: choice==selectChoice,
              );}
            ).toList()
            ,initialValue: selectChoice).then((choice){
              print('then====$choice');
            }).whenComplete((){
              print('complete==========');
            });

拿到一个集合或者说数组,你想同时获取索引和对应的值,可以如下操作。

            choices.asMap().map((key,value){
              
            });
  1. showDialog
    和showGeneralDialog的区别,这个参数比较少,就个context,以及一个child或者builder返回一个child,默认dismiss为ture,点击外部消失
    外层套个column是为了里用mainAxisAlignment 使内容居中显示,加上一些padding,margin,写起来真费劲,flutter控件本身没有margin,padding一说,要设置就得套个container,累的一比啊
                       showDialog(
                context: context,
                builder: (BuildContext context) {
                  return Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    mainAxisSize: MainAxisSize.min,
                    children: <Widget>[
                      Container(
                        width: 200,
                        height: 100,
                        child: Card(
                          color: Colors.deepOrange,
                          shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(10),
                              side: BorderSide(color: Colors.blue)),
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: <Widget>[
                              Text("dialog content"),
                              MaterialButton(
                                onPressed: () {
                                  Navigator.of(context, rootNavigator: true)
                                      .pop("click ok");
                                },
                                child: Text("ok"),
                              )
                            ],
                          ),
                        ),
                      )
                    ],
                  );
                }).then((value) {
              print('then====$value');
            }).whenComplete(() {
              print('complete======');
            });
image.png
  1. showBottomSheet
    比android里的差很多,唯一相同的就是控件是从底部弹出来的,从底部消失。
    这个顶部最大就在appbar下边,如果widget很小,andoid里点击外部是可以消失的,这里貌似不行。
    下边代码就是直接弹出一个默认的页面
            showBottomSheet(context: context, builder: (BuildContext context){
                                return
                                  MyApp();
            });
  1. showModalBottomSheet
    这个弹出的高度只有父容器高度的一半,上半部分有变暗,点击可以消失。
            showModalBottomSheet(context: context, builder: (BuildContext context){
              return MyApp();
            }).then((value){
              print('then====$value');
            });
  1. showSearch
    会跳到一个搜索页面,系统写好的
Future<T> showSearch<T>({
  @required BuildContext context,
  @required SearchDelegate<T> delegate,//抽象类,需要自己实现4个抽象方法
  String query = '',//默认的搜索文字,可以没有
})

效果


image.png

默认键盘是弹出来的,点击键盘上的search,就会进行查找操作了


image.png

下边是个实例
buildActions方法:返回appbar 右边的操作按钮,也可以是文字
buildLeading方法:就是那个后退箭头,你可以自己设置
buildResults方法:这里返回的是结果widget,query就是当前要查找的字段
buildSuggestions方法:提示信息,上边的search获取焦点的时候会显示这个
而且这个方法返回的是个Future,所以Future的几个方法都可以用,then可以获取到MySearch页面回调的结果数据

            showSearch(context: context, delegate: MySearch(),).then((value){
              print('then====$value');
            }).whenComplete((){

//实现类
class MySearch extends SearchDelegate {
  @override
  List<Widget> buildActions(BuildContext context) {
    return <Widget>[
      IconButton(icon: Icon(Icons.access_alarm), onPressed: () {}),
      IconButton(icon: Icon(Icons.forward), onPressed: () {})
    ];
  }

  @override
  Widget buildLeading(BuildContext context) {
    return BackButton();
  }

  @override
  Widget buildResults(BuildContext context) {
    print('result====${query}');
    return ListView(
      children: choices.map((choice) {
        return GestureDetector(
          child:  Container(
            color: Colors.transparent,
            child: Column(
              children: <Widget>[
                FlatButton.icon(onPressed: null, icon: Icon(choice.icon), label: Text(choice.title)),
                Divider(height: 2,color: Colors.grey,),
              ],
            ),
          ),
          onTap: (){
            Navigator.maybePop(context);
//如果要传递数据,可以使用 Navigator.of(context).pop(choice);
          },
        );
      }).toList(),
    );
  }

  @override
  Widget buildSuggestions(BuildContext context) {
    return Text("suggestions........");
  }

//非必须的方法
//默认的search bar的主题是白色的,重写下边的方法,可以修改主题里的属性,
  @override
  ThemeData appBarTheme(BuildContext context) {
    return super.appBarTheme(context).copyWith(primaryColor: Colors.blue);
  }
}

进一步研究
看源码,默认的实现主题色是白色的,所以我们看到标题栏是白色的,这个是可以修改的

  ThemeData appBarTheme(BuildContext context) {
    assert(context != null);
    final ThemeData theme = Theme.of(context);
    assert(theme != null);
    return theme.copyWith(
      primaryColor: Colors.white,
      primaryIconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
      primaryColorBrightness: Brightness.light,
      primaryTextTheme: theme.textTheme,
    );
  }

可以用到的方法

query 这个可以set,也可以get
void showResults(BuildContext context)//可以手动切换显示结果页还是提示页
void showSuggestions(BuildContext context)
void close(BuildContext context, T result)//关闭search页并返回数据,上边的Navigator方法可以改成这个

animation

animations tutorial
animation widget
进行动画,需要两个条件,一个就是可以动画的widget,以及animation,上边的地址
widget里列出了系统写好的一些,放大缩小,透明度变化,旋转等等,都是继承AnimatedWidget
你也可以自己写,可以实现多种变化,比如放大缩小和旋转一起进行
tutorial 告诉你咋把动画和widget关联起来

比如放大缩小的,用系统提供的ScaleTransition, 里边有个scale参数,就是需要的动画了

new ScaleTransition(
                scale: curve,
                alignment: Alignment.center,
                child: new FlutterLogo(
                  size: 100.0,
                ))),

动画的代码
AnimationController:动画控制

      controller = AnimationController(
          vsync: this,
          duration: const Duration(seconds: 2),);
      curve = CurvedAnimation(parent: controller, curve: Curves.elasticOut);
      curve.addListener(() {
        setState(() {});//刷新ui
      });
      curve.addStatusListener((status) {
//状态监听,有4种
        if (status == AnimationStatus.completed) {
          controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          controller.forward();
        }
      });

vsync:this 这个是个TickerProvider 可以自己实现这个抽象类

  @override
  Ticker createTicker(TickerCallback onTick) {
    return Ticker(onTick);
  }

也可以用系统写好的
SingleTickerProviderStateMixin,,TickerProviderStateMixin

class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin

因为widget比如ScaleTransition, 里边有个scale也就是上边的curvedAnimation,所以value是这个animation决定的。
取值范围在0和1之间,用系统提供的AnimationController默认lower是0,upper是1,以及CurvedAnimation 取值也是0到1之间的,具体的由Curves这个类引用的变量决定的,点进去可以看到value咋算的

如果取值不是0到1.可以用
class Tween<T extends dynamic> extends Animatable<T>

构造方法里传个begin和end即可
Tween({ this.begin, this.end });
我们可以看下它value的算法,那个t的值就是上边AnimationController的值,默认是0到1,如果你不改的话,那么可以看到这个tween的值也就是从begin到end拉。

  T transform(double t) {
    if (t == 0.0)
      return begin;
    if (t == 1.0)
      return end;
    return lerp(t);
  }
  T lerp(double t) {
    return begin + (end - begin) * t;
  }
常见的AnimatedWidget

初始化的animation

class TempWidgetState extends State<TempWidget> with TickerProviderStateMixin {
  Animation decoration;
  AnimationController controller;
  Animation<double> doubleA;
  Animation<Color> modalBarrier;
  @override
  void initState() {
 
    controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    );
    doubleA = Tween<double>(begin: 0, end: 1).animate(controller);
    doubleA.addListener((){
      setState(() {
      });
    });
controller.repeat(reverse: true);//可以点击某个按钮再开启动画
    super.initState();
  }
  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
  1. RotationTransition
    可以看到拿到value 乘以2π,也就是从0到1就是0到360度
  const RotationTransition({
    Key key,
    @required Animation<double> turns,
    this.alignment = Alignment.center,
    this.child,
  })

//
    final double turnsValue = turns.value;
    final Matrix4 transform = Matrix4.rotationZ(turnsValue * math.pi * 2.0);

如下

RotationTransition(turns: doubleA,child: FlutterLogo(size: 150,),),
  1. ScaleTransition
  const ScaleTransition({
    Key key,
    @required Animation<double> scale,
    this.alignment = Alignment.center,
    this.child,
  })
//
    final double scaleValue = scale.value;
    final Matrix4 transform = Matrix4.identity()
      ..scale(scaleValue, scaleValue, 1.0);

如下

ScaleTransition(scale: doubleA,child: FlutterLogo(size: 100,),alignment: Alignment.topLeft,),
  1. SizeTransition
    咋说了,很像一个画轴的展开过程,axis 默认垂直方向,还可以水平方向
    axisAlignment :这个值从-1到1比较合适,当然也可以比1大
    说下效果,主要就是展开以后显示的控件从哪里开始,
    0的话,从控件正中心往两边展开,
    -1的话,从控件start或者top位置展开
    1的话,从控件的end或者bottom位置展开,下边给几张图看下
  const SizeTransition({
    Key key,
    this.axis = Axis.vertical,//展开的方向,水平或者垂直
    @required Animation<double> sizeFactor,
    this.axisAlignment = 0.0,
    this.child,
  })
//
  Widget build(BuildContext context) {
    AlignmentDirectional alignment;
    if (axis == Axis.vertical)
      alignment = AlignmentDirectional(-1.0, axisAlignment);
    else
      alignment = AlignmentDirectional(axisAlignment, -1.0);
    return ClipRect(
      child: Align(
        alignment: alignment,
        heightFactor: axis == Axis.vertical ? math.max(sizeFactor.value, 0.0) : null,
        widthFactor: axis == Axis.horizontal ? math.max(sizeFactor.value, 0.0) : null,
        child: child,
      ),
    );
  }
image.png
  1. SlideTransition
    上下左右滑动child,offset里的x和y分别对应水平,垂直方向的,0是初始位置,1表示移动child宽或高的一倍距离。
  const SlideTransition({
    Key key,
    @required Animation<Offset> position,
    this.transformHitTests = true,
    this.textDirection,
    this.child,
  }) 

  Widget build(BuildContext context) {
    Offset offset = position.value;
    if (textDirection == TextDirection.rtl)
      offset = Offset(-offset.dx, offset.dy);
    return FractionalTranslation(
      translation: offset,
      transformHitTests: transformHitTests,
      child: child,
    );
  }

使用这样
Offset(0, 0) 参数dx和dy是百分比,和child的大小来说的,dx:0表示原始位置,1表示移动往右移动容器宽度的一倍距离。-1就是左移了,LTR模式。上下移动的话修改第二个参数dy。

            child: new SlideTransition(
                position: Tween<Offset>(begin: Offset(0, 0),end: Offset(1, 0)).animate(controller),
                child: new FlutterLogo(
                  size: 100.0,
                ))),
  1. PositionedTransition
    Positioned widgets must be placed directly inside Stack widgets.
  const PositionedTransition({
    Key key,
    @required Animation<RelativeRect> rect,
    @required this.child,
  })

这玩意比较特殊,必须放在一个Stack控件里,否则就挂了

        new Container(
          height: 300,
            width: 500,
            child: Stack(
              children: <Widget>[
                new PositionedTransition(
                    rect: rectAnim,
                    child: new FlutterLogo(
                      size: 100.0,
                    ))
              ],
            )),

我们简单写个rect,其实就是提供四个顶点的坐标,相对stack容器而言

rectAnim=Tween<RelativeRect>(begin: RelativeRect.fromLTRB(0, 0, 100, 100),end: RelativeRect.fromLTRB(200, 0, 400, 200)).animate(controller)
  1. FadeTransition
    透明度变化的控件
final Animation<double> opacity;
  const FadeTransition({
    Key key,
    @required this.opacity,
    this.alwaysIncludeSemantics = false,
    Widget child,
  })
  1. DecoratedBoxTransition
    看名字,就是两种Decoration的渐变,和AnimatedContainer很类似,那个是动态修改里边的属性,这个是一次提供两个,我给你圆滑过渡。
    position: DecorationPosition.background //第三个参数意思是这个decoration是在上层展示还是下层展示,
    上层的意思就是画在child上边
    这里的decoration变化,并不影响child的大小,比如decoration变成圆了,child就会显示在圆外边。
//final Animation<Decoration> decoration;
DecoratedBoxTransition(decoration: decoration, child: FlutterLogo(size: 150,)),

decoration如下

controller = AnimationController(
        vsync: this,
        duration: const Duration(seconds: 2),
      );
      decoration = DecorationTween(
          begin: BoxDecoration(
              color: Colors.purple,
              borderRadius: BorderRadius.circular(10),
              border: Border.all(
                color: Colors.black,
                width: 5.0,
              )),
          end: BoxDecoration(
              color: Colors.blue,
              borderRadius: BorderRadius.circular(55),
              border: Border.all(
                color: Colors.black,
                width: 5.0,
              ))).animate(controller);

------------------上边7个是一类,都是需要一个Animation,根据Animation的value变化自动修改属性-----

  1. Hero
    使用起来也很简单,在两个跳转的页面,要进行动画的widget外边套一层Hero,加个一样的tag就行,tag是个object,啥都行,一样就行。
Hero(tag: 'xxx', child: Image.asset(
        "images/imager.png",
        width: 100,
        height: 100,
      ))

因为这个hero是页面跳转用到的转场动画,所以先学下页面跳转
和android不同,android的页面都是activity,而flutter里一个页面就是个widget,所谓的跳转页面,就是另外一个widget.
跳转页面用的是Navigator这个类,用如下的方法

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

不过这context还是有讲究的,不是所有的context都可以用,这不,下边就出错了。

Navigator operation requested with a context that does not include a Navigator.
The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.

错误代码是这样的,Navigator事件是写在State类里的,这个context不符合要求,
不过最终用到的widget都是放在Scaffold里的,这个控件是满足要求的。
所以解决办法,自定义一个widget,然后Navigator事件写在自定义的里边,这时候这个widget拿到的context是Scaffold里的,复合要求的

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

class MyApp extends StatefulWidget

class MyAppState extends State<MyApp>

    return new MaterialApp(
      theme: ThemeData(
        brightness: Brightness.light,
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: lv,
      ),
    );

其他一些push方法的解释
navigator说明

------下边是一类,是widget的某些属性变化的时候,有一个渐变的过程----

  1. AnimatedContainer
    它的作用是当里边的属性发生改变的时候,有一个渐变的过程
    比如宽高,颜色,margin,padding,位置等
  AnimatedContainer({
    Key key,
    this.alignment,
    this.padding,
    Color color,
    Decoration decoration,
    this.foregroundDecoration,
    double width,
    double height,
    BoxConstraints constraints,
    this.margin,
    this.transform,
    this.child,
    Curve curve = Curves.linear,
    @required Duration duration,
  })

如下,简单的改变大小和颜色
初始有个值,当点击某个按钮以后修改这些值,然后setState以后,这个容器会在duration时间里从老的变化成新的。

        AnimatedContainer(
          duration: Duration(seconds: 2),
          child: new FlutterLogo(
            size: 100,
          ),
          color: bgColor,
          width: bgW,
          height: bgH,
        ),
  1. AnimatedCrossFade
    里边有2个child,交替变淡,根据crossFadeState反正只显示一个,所以,我们动态修改这个值就可以在这两个child之间渐变了。
  const AnimatedCrossFade({
    Key key,
    @required this.firstChild,
    @required this.secondChild,
    this.firstCurve = Curves.linear,
    this.secondCurve = Curves.linear,
    this.sizeCurve = Curves.linear,
    this.alignment = Alignment.topCenter,
    @required this.crossFadeState,
    @required this.duration,
    this.layoutBuilder = defaultLayoutBuilder,
  })
  1. AnimatedBuilder
    简单点讲,就是你提供的animation的value发生变化的时候,它会自动调用build重建,把child当参数给你,你可以用,可以不用。
    和AnimatedWidget差不多,这个也是当提供的Listenable发生改变的时候自动rebuild该widget。
    animation就是之前用到的AnimationController ,CurvedAnimation ,继承Animation就行
    build是一个2个参数的方法,返回一个widget,我们这里返回一个Transform,方便动画,比如下边的rotate,我们动态修改angle即可。
        AnimatedBuilder(
          animation: controller,
          builder: (context, child) {
            return Transform.rotate(
              angle: controller.value*2*3.1415926,
              child: child,
            );
          },
          child: Image.asset(
            "images/imager.png",
            width: 100,
            height: 100,
          ),
        )

当然也可以这样用,反正就是animation的value改变的话,builder就会执行,至于想返回啥,随便由你决定。

        AnimatedBuilder(
          animation: controller,
          builder: (context, child) {
            return Text("value:${controller.value}");
          },
        )

其他动画

            Transform.scale(scale: null);
            Transform.translate(offset: Offset(100, 0));

AnimatedWidget的实现,如下,简单实现,build里返回啥widget都可以,只要listenalbe发生变化了,下边就会build

class SpinningContainer extends AnimatedWidget {
  const SpinningContainer({Key key, AnimationController controller})
      : super(key: key, listenable: controller);

  Animation<double> get _progress => listenable;

  @override
  Widget build(BuildContext context) {
    return Transform.rotate(
      angle: _progress.value * 2.0 * math.pi,
      child: Container(width: 200.0, height: 200.0, color: Colors.green),
    );
  }
}
  1. AnimatedDefaultTextStyle
    就是一个textstyle动态变化的过程,也就是第二个参数style发生变化的时候,会在第三个参数duration设置的时间里转变完成。
        AnimatedDefaultTextStyle(child: Text("text style"),
            style: big?TextStyle(fontWeight: FontWeight.w100,color:Colors.purple):TextStyle(fontWeight: FontWeight.w900,color: Colors.red,fontStyle: FontStyle.italic),
            duration: Duration(seconds: 3)),
  1. AnimatedListState
    The state for a scrolling container that animates items when they are inserted or removed.
    就看API,根本不知道咋用,还是找个demo看看,代码太多了,有空再细看
    animated-list

  1. AnimatedModalBarrier
    是给ModalRoute<T> 用的,不能直接添加在页面里
    可以理解为背景图层,阻止用户与下层页面交互,最常见的,就是弹个对话框,对话框的背景是灰色的,点击事件不会传递到dialog下层的页面
    完全不知道这个玩意哪里用的,showGeneralDialog里有个参数pageBuilder,我试着返回这个,没问题,背景动画都有,可这个随便放到别的容器里就没效果了,而且其他widget也不见了。总不能这玩意只能单独显示,就为了一个变化的背景颜色吗?
    待以后研究。。。。。。。。。。。。。
  const AnimatedModalBarrier({
    Key key,
    Animation<Color> color,
    this.dismissible = true,
    this.semanticsLabel,
    this.barrierSemanticsDismissible,
  }) 
  1. AnimatedOpacity
    这个比较简单,就是透明度的变化,当opacity发生改变的时候,会在duration时间内完成
AnimatedOpacity(opacity: opacity, duration: Duration(seconds: 4),child: FlutterLogo(),)
  1. AnimatedPhysicalModel
    AnimatedPhysicalModel
    对这两个属性进行渐变 borderRadius 和elevation
    如果animateColor:true为真的话,那么也对color进行渐变
    如果animateShadowColor为true的话,那么对shadowColor也进行渐变
 AnimatedPhysicalModel(child: FlutterLogo(size: 80,),animateColor:true,borderRadius: BorderRadius.circular(radius),
 shape: BoxShape.circle, elevation: eleva, color: color, shadowColor: Colors.red, duration: Duration(seconds: 5)),
  1. AnimatedPositioned
    Only works if it's the child of a Stack
    就是位置发生变化的时候会进行一个渐变的过程。必须放在Stack里。
    和PositionedTransition 差不多,那边是提供一个Animation<RelativeRect>。
    使用
Stack(
          children: <Widget>[
            AnimatedPositioned.fromRect(
              duration: Duration(seconds: 4),
              child: FlutterLogo(
                size: 69,
              ),
              rect: big
                  ? Rect.fromLTRB(0, 0, 50, 50)
                  : Rect.fromLTRB(110, 40, 250, 250),
            ),
          ],
        ),

构造方法

  const AnimatedPositioned({
    Key key,
    @required this.child,
    this.left,
    this.top,
    this.right,
    this.bottom,
    this.width,
    this.height,
    Curve curve = Curves.linear,
    @required Duration duration,
  })

  AnimatedPositioned.fromRect({
    Key key,
    this.child,
    Rect rect,
    Curve curve = Curves.linear,
    @required Duration duration,
  })
  1. AnimatedSize
    当widget大小变化的时候,有个动画的过程,如下,我们改变size的大小,就能看到效果
 AnimatedSize(duration: Duration(seconds: 5), vsync: this,child: FlutterLogo(size: size,),),
  1. AnimatedWidget
    一个抽象类,A widget that rebuilds when the given Listenable changes value.
    需要提供一个listenable,这样当listenable的value变化的时候,这个widget会rebuild,属性可以动态变了,也就有动画效果了。
    前边用的AnimationController,Animation<T> 都可以用
  final Listenable listenable;

这个也可以用AnimatedBuilder来写,差不多
下边是个demo

class SpinningContainer extends AnimatedWidget {
  const SpinningContainer({Key key, AnimationController controller})
      : super(key: key, listenable: controller);

  Animation<double> get _progress => listenable;

  @override
  Widget build(BuildContext context) {
    return Transform.rotate(
      angle: _progress.value * 2.0 * math.pi,
      child: Container(width: 200.0, height: 200.0, color: Colors.green),
    );
  }
}
  1. AnimatedWidgetBaseState

AnimatedWidgetBaseState<T extends ImplicitlyAnimatedWidget> class

abstract class ImplicitlyAnimatedWidget extends StatefulWidget

demo:如下,简单的修改颜色

//use
TempX(duration: Duration(seconds: 5), c: color),

//class
class TempX extends ImplicitlyAnimatedWidget {
  Color c = Colors.amber;
  @override
  ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState() {
    return TempState();
  }

  TempX({
    Key key,
    Curves curve,
    this.c,
    @required Duration duration,
  })  : assert(duration != null),
        super(
            key: key,
            curve: curve == null ? Curves.linear : curve,
            duration: duration);
}

class TempState extends AnimatedWidgetBaseState<TempX> {
  ColorTween _colorTween;
  @override
  Widget build(BuildContext context) {
    return Icon(Icons.directions_car, color: _colorTween.evaluate(animation));
  }

  @override
  void forEachTween(TweenVisitor<dynamic> visitor) {
    _colorTween = visitor(
      // The latest tween value. Can be `null`.
      _colorTween,
      // The color value toward which we are animating.
      widget.c,
      // A function that takes a color value and returns a tween
      // beginning at that value.
      (value) => ColorTween(begin: value),
    );
  }
}

other

上边用到Tween的类,有个泛型,系统已经写好了一些


image.png

页面跳转以及数据的传递

比较坑爹的问题,context不好弄啊,这玩意咋只有State里有,我简单测试随便跳个widget,拿不到context,也没法点击按钮回来,擦。
Navigator operation requested with a context that does not include a Navigator.
解决:https://blog.csdn.net/nimeghbia/article/details/84388725

前边说过,flutter没有activity一说,所谓的页面,就是个widget,所以你定义一堆widget,就可以互相跳转了
打开下个页面有两种方式

  1. 先注册
    key,value的形式,key名字你自己起一个,后边跳转就用这个key,value就是要跳转的页面,也就是个widget
final Map<String, WidgetBuilder> routes;
typedef WidgetBuilder = Widget Function(BuildContext context);

//注册
    return new MaterialApp(
      routes: {
        "nextPageAnyName": (context) { return Text("xxxx");},
        "thirdPageAnyName":(BuildContext context)=>gl,
      },

//跳转页面这样
Navigator.pushNamed(context, "nextPageAnyName",arguments: "argument is a object");

//push返回的是个Future,可以通过then方法接收下个页面到时候传递回来的数据
Navigator.pushNamed(context, "nextPageAnyName",arguments: "argument is a object").then((value){
                    
                  });
//打开新的页面的同事关闭自己咋办
        Navigator.pushNamedAndRemoveUntil(context, "thirdPageAnyName", (Route<dynamic> route){
          route.dispose();
          return true;
        });
  1. 参数里直接返回要跳转的widget,其实和上边的差不多,上边只是用key替换了这个而已
      Navigator.push(context, MaterialPageRoute(builder: (context){
        return Text("next page widget");
      }))

一些button的简单介绍

  1. MaterialButton
    可以设置文字的各种状态下的颜色,背景的颜色,波纹的颜色,背景等
  const MaterialButton({
    Key key,
    @required this.onPressed,
    this.onHighlightChanged,
    this.textTheme,
    this.textColor,
    this.disabledTextColor,
    this.color,
    this.disabledColor,
    this.highlightColor,
    this.splashColor,
    this.colorBrightness,
    this.elevation,
    this.highlightElevation,
    this.disabledElevation,
    this.padding,
    this.shape,
    this.clipBehavior = Clip.none,
    this.materialTapTargetSize,
    this.animationDuration,
    this.minWidth,
    this.height,
    this.child,
  })
  1. FlatButton
    父类就是materialButton,它有个icon的方法,可以传2个widget,如下最后组合成一个Row,传给父类的child
    就是左边一个icon,右边一个lable,当然了其实这个widget没限制的,你都弄成icon也没问题

         child: Row(
           mainAxisSize: MainAxisSize.min,
           children: <Widget>[
             icon,
             const SizedBox(width: 8.0),
             label,
           ],
         )
  1. RaisedButton

默认有个阴影,点击阴影效果更明显

  1. OutlineButton
    默认有个边框,点击边框颜色改变,这些颜色都是可以自定义的


    image.png

生命周期的监听

用到的代码如下
WidgetsBinding 添加observer,删除observer
WidgetsBindingObserver 重写didChangeAppLifecycleState方法,参数就是当前的状态
目前android就用到onResume和onPause,和activity不太一样,这玩意整个app好像就一个状态,因为flutter里页面也是widget,就算不可见的widget和可见的wiget声明周期也是一样的

class _LifecycleWatcherState extends State<LifecycleWatcher> with WidgetsBindingObserver {
  AppLifecycleState _lastLifecyleState;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    setState(() {
      _lastLifecyleState = state;
    });
  }

其他控件

  1. android里的edittext
    TextField(
    decoration: InputDecoration(hintText: "hint...."),
    ),

InputDecoration 属性比较多了,和android里的TextInputLayout很相似,error word,help word,count都有

  1. PageView
    相当于android里的ViewPager
  PageView({
    Key key,
    this.scrollDirection = Axis.horizontal,//可以垂直水平滚动
    this.reverse = false,
    PageController controller,
    this.physics,//这个上边有解释过,主要可以设置是否可以触摸滑动
    this.pageSnapping = true,
    this.onPageChanged,//监听页面改变
    List<Widget> children = const <Widget>[],//添加child
    this.dragStartBehavior = DragStartBehavior.start,
  })

滚动处理

  PageController({
    this.initialPage = 0,
    this.keepPage = true,
    this.viewportFraction = 1.0,//child页面占比吧,比1小你就能看到第二个了
  }) 

页面手动跳转

//              _pageController.animateToPage(index, duration: Duration(milliseconds: 333), curve: Curves.linear);//带动画的
//              _pageController.jumpToPage(index);//不带动画,直接修改view
//下一页,上一页,当没有下一页或者上一页的时候,无反应
            _pageController.nextPage(duration: Duration(milliseconds: 222), curve: Curves.easeInOut);
//            _pageController.previousPage(duration: Duration(milliseconds: 222), curve: Curves.easeIn);

实际中的问题

  1. flutter里边控件的宽高真是个问题

  2. 我弄个column,第一个widget放了张图片,第二个widget是个GridView,完事啥也不显示,提示错误。
    可以给GridView弄个容器,限定宽高就ok了,垂直滚动的需要确定宽,水平滚动的需要确定高

边学边记录

1.侧滑菜单
使用scaffold就可以实现

var globalkey = GlobalKey<ScaffoldState>();
Scaffold(
        key: globalkey,
        appBar: AppBar(
          leading: FlatButton(
              onPressed: () {
                globalkey.currentState.openDrawer();
              },
              child: Icon(Icons.menu)),
          title: Text("first page"),
        ),
        body: lv,
        drawer: Drawer(
          child: Column(
            children: <Widget>[
              Text("first......."),
              Icon(Icons.access_alarm),
            ],
          ),
        ),
        endDrawer: Drawer(
          child: Column(
            children: <Widget>[Text("right...")],
          ),
        ),

drawer:左侧的
endDrawer:右侧的,当然都是对于LTR而言的
可以看到都套了个Drawer,这个好处是宽度固定,背景默认白色,比较省事,否则,你也可以自己写的widget,自己处理背景,宽度。
除了手指在屏幕边界把侧滑页面弄出来,还可以通过点击按钮实现。
主要用到了Scaffold的ScaffoldState,那咋获取这个玩意
Scaffold.of(context)可以拿到这个state,不过这个context要求比较高,上边的代码里直接用就不行了,这个context要求是Scaffold里context,不能是scaffold外层的context。
我们可以弄个自定义widget放在scaffold里,这个自定义widget里的context就符合要求,不过我们这里的用个button也没必要自定义啊,所以用另外一种办法,就是代码里的
声明一个GolbalKey,完事把这个key指定给Scaffold的key,之后就可以通过globalkey.currentState拿到这个state,就可以用了。

var globalkey = GlobalKey<ScaffoldState>();
key: globalkey,
globalkey.currentState
  1. ShapeBorder自定义
    系统提供了一些常用的,矩形,圆形,棱形等,如果还不满足,可以自定义实现
    如下自定义了一个
import 'package:flutter/material.dart';

class SixLinesBorder extends ShapeBorder{
  @override
  EdgeInsetsGeometry get dimensions => null;//文字描述太多,没咋看懂,不管了先,好像是和边框那线条有关的

  /// To obtain a [Path] that describes the area of the border itself, set the
  /// [Path.fillType] of the returned object to [PathFillType.evenOdd], and add
  /// to this object the path returned from [getOuterPath] (using
  /// [Path.addPath]).
//简单解释下,内边界生效的方法,设置fillType为evenOdd,然后把outerPath加进来就能一起生效了
//这个方法默认不执行的,得手动调用,我们在outerPath里把这加进去
  @override
  Path getInnerPath(Rect rect, {TextDirection textDirection}) {
//简单的范围缩进20,弄个椭圆
    Rect rect=rect2.deflate(20);
    Path path=new Path();
    path.fillType=PathFillType.evenOdd;
    path.addOval(rect);
    return path;
  }

//感觉整个shape是由这个path来决定的
  @override
  Path getOuterPath(Rect rect, {TextDirection textDirection}) {
 Path path=getInnerPath(rect);//
    path.addOval(rect);//简单测试加个椭圆
    return path;
  }

  @override
  void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
//这里边可以用canvas画边框,随便你画啥了。
    Paint paint=new Paint();
    paint.isAntiAlias=true;
    paint.color=Colors.deepPurple;
    paint.style=PaintingStyle.stroke;
    canvas.drawPath(getOuterPath(rect), paint);//我们这里就按照outerPath画个边框
    canvas.drawLine(rect.topCenter,rect.bottomCenter, paint);//测试,再加条线
  }

  @override
  ShapeBorder scale(double t) {
//放大的时候咋处理,有边框的话,把边框放大,没有的话,感觉啥也不干就行
    return null;
   // this.scale(t);
   // return this;
  }
}
效果图.png

GridView

  GridView({
    Key key,
    Axis scrollDirection = Axis.vertical,//滚动方向,默认垂直的
    bool reverse = false,//反转数据
    ScrollController controller,//滑动监听控制
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    @required this.gridDelegate,//控制item的显示逻辑的
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
  })

下边举例都是以垂直方向说明的

gridDelegate的作用,目前系统提供了2种实现
  1. SliverGridDelegateWithMaxCrossAxisExtent
    这个就是限制下item的横轴最大值,然后横轴能放几个就放几个,效果图如下
GridView(
        gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 70,),
        children: <Widget>[
          Text("search",style: TextStyle(fontSize: 33 ,backgroundColor: Colors.cyan,)),
          FlatButton(onPressed: _click, child: Icon(Icons.ac_unit)),
          FlatButton(onPressed: _click, child: Icon(Icons.calendar_view_day)),
          FlatButton(onPressed: _click, child: Icon(Icons.eject)),
          FlatButton(onPressed: _click, child: Icon(Icons.inbox)),
        ],
      );
image.png
  1. SliverGridDelegateWithFixedCrossAxisCount
    这个就和我们android里的一样,规定下一行显示几个,
    高度和宽度一样了,不知道高度能调整不,待研究
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2,),
image.png

高度的控制
childAspectRatio:就是item的宽高比
其他两个参数看名字就知道了,主轴和横轴方向item的间距

SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, mainAxisSpacing: 10,crossAxisSpacing: 20,childAspectRatio: 2)

GridView的几种创建过程

  1. 直接new一个,上边有
  2. builder
    主要就是通过itemBuilder来创建每个item,其他参数看名字就知道干啥了
    var gridView = GridView.builder(
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 4,
          mainAxisSpacing: 10,
          crossAxisSpacing: 10,
          childAspectRatio: 2),
      itemBuilder: (context, index) {
        return GestureDetector(
          child: Container(
            color: Colors.amberAccent,
            child: Text(
              "text...$index",
              style: TextStyle(backgroundColor: Colors.lightGreenAccent),
            ),
          ),
          onTap: () {
            _itemClick(index);
          },
        );
      },
      itemCount: 20,
      scrollDirection: Axis.vertical,
      reverse: false,
      controller: null,
    );
  1. count
    关键字段crossAxisCount,其实就相当于gridDelegate用了SliverGridDelegateWithFixedCrossAxisCount
    gridView=GridView.count(crossAxisCount: 5,children: <Widget>[
      
    ],);
  1. extent
    关键字段maxCrossAxisExtent 也就是item的最大宽,其实相当于gridDelegate用了SliverGridDelegateWithMaxCrossAxisExtent
    gridView = GridView.extent(
      maxCrossAxisExtent: 200,
      children: <Widget>[],
    );
  1. custom
    参数childrenDelegate,其实里边就是用到了itemBuilder来创建item
    或者用SliverChildListDelegate()参数就是children
    gridView = GridView.custom(
        gridDelegate:
            SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
        childrenDelegate: SliverChildBuilderDelegate(
          (context, index) {
            return Container(
              color: Colors.cyan,
              child: Text("text&&$index"),
            );
          },
          childCount: 22,
        ));

日常记录

  1. 获取屏幕宽高
var size=MediaQuery.of(context).size;

或者

import 'dart:ui';
 
final width = window.physicalSize.width;
final height = window.physicalSize.height;

  1. 类似android里的比重
    Expanded 里边有个参数flex
return Row(
      children: <Widget>[
        CustomLayout(),
        Expanded(child: gridView),
      ],
    );

PageStoreKey

比如一个TabBarView有3个页面,里边是listview或者girdview,你来回切换页面的时候发现数据又跑到第一条去了,要解决这个问题,很简单,加上key即可,如下,
目前没搞懂,①里边的参数到底啥类型,好像啥类型都行,奇怪
还有个问题,②如果有3个页面里有2个GridView,发现一个滚多少距离,另一个好像也跟着滚了。

已解决:
在同一个页面里的key,那个value的类型一样的话,就会跟着一起滚,所以如果有3个滚动页面的话,大家的value类型必须不同

    var lv = ListView.builder(
      key: PageStorageKey(""),

GlobalKey

widget里的key还可以用这个,如下可以拿到widget的大小,当然也可以直接拿到widget

( value.key as GlobalKey).currentContext.size

滚动监听

可以滚动的控件都带有一个controller参数,如下,可以监听滚动,获取滚动的距离

    var _controller=ScrollController(initialScrollOffset: initOffset);
    _controller.addListener((){
//      print('listener==========${_controller.offset}');
    });

还有下边两种,手动设置滚动位置,一个带动画,一个不带
curve:动画的差值器,类似android里的interpolator,系统提供了很多实现,也可以自定义

 _controller.jumpTo(100);
  _controller.animateTo(200, duration: Duration(seconds: 1), curve: Curves.ease);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容