Flutter入门学习记录【三】

Cupertino (iOS风格) Widgets

https://flutterchina.club/widgets/cupertino/

  1. CupertinoActivityIndicator
    一个iOS风格的loading指示器。显示一个圆形的转圈菊花
  const CupertinoActivityIndicator({
    Key key,
    this.animating = true,//是否动画
    this.radius = _kDefaultIndicatorRadius,//圈圈的半径
  })
image.png
  1. CupertinoAlertDialog
    iOS风格的alert dialog.当然了这个控件是个widget而已,
  const CupertinoAlertDialog({
    Key key,
    this.title,
    this.content,
    this.actions = const <Widget>[],
    this.scrollController,//内容太长会滚动的
    this.actionScrollController,//action太多也会滚动的,而controller是可以添加listener的
  })

actions:系统建议用CupertinoDialogAction,当然了你用其他widget也是可以的
isDefaultAction:Default buttons have bold text,蓝色的yes,字体加粗
isDestructiveAction:Whether this action destroys an object.红色的delete,主要用来提示一些操作

demo

  void _showDia() {
    var scrollController = ScrollController();
    var actionScrollController = ScrollController();
    var dia = CupertinoAlertDialog(
      title: Text("title"),
      content: Text("content............"),
      actions: <Widget>[
        CupertinoDialogAction(
          child: Text("yes"),
          onPressed: () {},
          isDefaultAction: true,
          isDestructiveAction: false,
        ),
        CupertinoDialogAction(
          child: Text("delete"),
          onPressed: () {},
          isDefaultAction: false,
          isDestructiveAction: true,
        ),
        CupertinoDialogAction(
          child: Text("cancel"),
          onPressed: () {
            Navigator.of(context, rootNavigator: true).pop();
          },
          isDefaultAction: true,
          isDestructiveAction: true,
        ),
      ],
      scrollController: scrollController,
      actionScrollController: actionScrollController,
    );
    showDialog(
        context: context,
        barrierDismissible: false,//点击dialog外部是否消失,默认为true
        builder: (context) {
          return dia;
        });
  }
image.png
  1. CupertinoButton
  const CupertinoButton({
    Key key,
    @required this.child,//必须有textDirection这种设置,也就是必须有direction,否则异常
    this.padding,
    this.color,//button的颜色
    this.disabledColor,//不可用的颜色
    this.minSize = kMinInteractiveDimensionCupertino,//默认44
    this.pressedOpacity = 0.1,//点击的时候控件透明度,0就完全看不见拉
    this.borderRadius = const BorderRadius.all(Radius.circular(8.0)),
    @required this.onPressed,
  }) 

demo以及效果图

        CupertinoButton(
            child: Text(
              "register",
              textDirection: TextDirection.ltr,//必须属性
            ),
            color: Colors.redAccent,
            disabledColor: Colors.blueGrey,
            minSize: 50,
            pressedOpacity: 0.7,
            padding: EdgeInsets.only(left: 20, right: 20),
            onPressed: _showDia),
        CupertinoButton(
            child: Icon(
              Icons.ac_unit,
              textDirection: TextDirection.ltr,//必须属性
            ),
            onPressed: _showDia),

紅色那個按钮是按压效果


image.png
  1. CupertinoDialog
    好像不建议使用了,就不研究了,看下另外一个控件
    CupertinoPopupSurface:其实就是个圆角矩形容器
CupertinoPopupSurface(
      child: Container(
        decoration: BoxDecoration(border: Border.all()),
        alignment: Alignment.center,
        width: 200,
        height: 50,
        child: Text("every body"),
      ),
      isSurfacePainted: true,//是否有白色背景,为false就是透明背景
    )

看图可以看到4个角都被切了,我们child的线明显断了,如果你需要矩形圆角控件的话可以用这个


image.png
  1. CupertinoDialogAction
    2里边那几个action控件就是,这里就不研究了,这玩意就和CupertinoAlertDialog一起用吧,要不单独的没有点击效果,不好看
  const CupertinoDialogAction({
    this.onPressed,
    this.isDefaultAction = false,
    this.isDestructiveAction = false,
    this.textStyle,
    @required this.child,
  })
  1. CupertinoSlider
    android里的seekBar
  const CupertinoSlider({
    Key key,
    @required this.value,
    @required this.onChanged,
    this.onChangeStart,
    this.onChangeEnd,
    this.min = 0.0,
    this.max = 1.0,
    this.divisions,//max-min 分成多少份,也就是移动的最小单位
    this.activeColor,//拖动条颜色
  })

比如max为100,min为0,divisions为100的话,滑动每次最小单位是1,如果divisions为50,那么滑动每次最小单位为2
demo以及效果

  var _value = 0.2;
  void changeValue(double value) {
    setState(() {
      _value = value;
    });
  }
//----------------
        CupertinoSlider(
          value: _value,
          onChanged: (current) {
            print('change=======$current');
            changeValue(current);
          },
          onChangeStart: (start) {
            print('start=====$start');//这里start固定就是那个min
          },
          onChangeEnd: (end) {
            print('end=========$end');
          },
          activeColor: Colors.red,
          min: 0.0,
          max: 100.0,
          divisions: 200,
        ),

默认的宽度很小,如果要改变宽度,可以外边套一个SizedBox给个具体的宽


image.png
  1. CupertinoSwitch
    开关
  const CupertinoSwitch({
    Key key,
    @required this.value,//默认的选中状态,true/false
    @required this.onChanged,
    this.activeColor,
    this.dragStartBehavior = DragStartBehavior.start,//start的话可以左右拖动切换,down的话就是点击切换
  })

demo以及效果,这个控件的长度好像无法修改,最多通过Transform整体scale

        CupertinoSwitch(
          value: _on,
          onChanged: (value) {
            setOnOff(value);
          },
          activeColor: Colors.deepPurple,
          dragStartBehavior: DragStartBehavior.down,
        ),
image.png
  1. CupertinoTabBar
    好像高度padding都固定的啊,也没找到地方可以改
  const CupertinoTabBar({
    Key key,
    @required this.items,
    this.onTap,
    this.currentIndex = 0,
    this.backgroundColor,整个控件的背景色
    this.activeColor,//选中后的颜色
    this.inactiveColor = CupertinoColors.inactiveGray,
    this.iconSize = 30.0,
    this.border = const Border(//默认的border是上边有条线
      top: BorderSide(
        color: _kDefaultTabBarBorderColor,
        width: 0.0, // One physical pixel.
        style: BorderStyle.solid,
      ),
    ),
  })

demo

  var icons = <IconData>[Icons.four_k, Icons.build, Icons.access_alarm];
  List<BottomNavigationBarItem> getTabs() {
    return icons.map((icon) {
      return BottomNavigationBarItem(
          icon: Icon(icon),
          title: Text(icon.toString()),
          backgroundColor: Colors.amberAccent);
    }).toList();
  }
//
  var _currentTab = 0;
  void changeTab(int tab) {
    setState(() {
      _currentTab = tab;
    });
  }
//
        CupertinoTabBar(
          items: getTabs(),
          activeColor: Colors.deepPurple,
          currentIndex: _currentTab,
          onTap: (index) {
            changeTab(index);
          },
//          backgroundColor: Colors.deepOrange,
        )
image.png

Material Components Widgets

https://flutterchina.club/widgets/material/

  1. Scaffold
    Material Design布局结构的基本实现。此类提供了用于显示drawer、snackbar和底部sheet的API。
  const Scaffold({
    Key key,
    this.appBar,
    this.body,
    this.floatingActionButton,
    this.floatingActionButtonLocation,
    this.floatingActionButtonAnimator,
    this.persistentFooterButtons,
    this.drawer,
    this.endDrawer,
    this.bottomNavigationBar,
    this.bottomSheet,
    this.backgroundColor,
    this.resizeToAvoidBottomPadding,
    this.resizeToAvoidBottomInset,
    this.primary = true,
    this.drawerDragStartBehavior = DragStartBehavior.start,
    this.extendBody = false,
    this.drawerScrimColor,
    this.drawerEdgeDragWidth,
  }) 
  1. BottomNavigationBar
  BottomNavigationBar({
    Key key,
    @required this.items,
    this.onTap,
    this.currentIndex = 0,
    this.elevation = 8.0,
    BottomNavigationBarType type,
    Color fixedColor,
    this.backgroundColor,
    this.iconSize = 24.0,
    Color selectedItemColor,
    this.unselectedItemColor,
    this.selectedIconTheme = const IconThemeData(),
    this.unselectedIconTheme = const IconThemeData(),
    this.selectedFontSize = 14.0,
    this.unselectedFontSize = 12.0,
    this.selectedLabelStyle,
    this.unselectedLabelStyle,
    this.showSelectedLabels = true,
    bool showUnselectedLabels,
  })

type: BottomNavigationBarType两种效果
如果你设置了type,就按照你的来,如果没设置,<=3个item的是fixed模式,否则是shifting模式

  static BottomNavigationBarType _type(
      BottomNavigationBarType type,
      List<BottomNavigationBarItem> items,
  ) {
    if (type != null) {
      return type;
    }
    return items.length <= 3 ? BottomNavigationBarType.fixed : BottomNavigationBarType.shifting;
  }

fixed:图片文字都显示,item大小一致,选中颜色默认是材料主题色,BottomNavigationBarItem的背景参数无效,


fixed.png

shifting:只有选中的那个显示文字,并且比较大,另外也没有选中颜色一说了,而且backgroundColor无效,
实际看到的是BottomNavigationBarItem的背景颜色


shifting.png

注意:
下边两个都是设置选中的item的颜色的,不过只能设置其中一个,否则异常

       assert(
         selectedItemColor == null || fixedColor == null,
         'Either selectedItemColor or fixedColor can be specified, but not both'
       )
  1. MaterialApp
  const MaterialApp({
    Key key,
    this.navigatorKey,
    this.home,
    this.routes = const <String, WidgetBuilder>{},
    this.initialRoute,
    this.onGenerateRoute,
    this.onUnknownRoute,
    this.navigatorObservers = const <NavigatorObserver>[],
    this.builder,
    this.title = '',
    this.onGenerateTitle,
    this.color,
    this.theme,
    this.darkTheme,
    this.themeMode = ThemeMode.system,
    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,
  })

属性太多,说些注意事项
①:If the home property is specified, the routes table cannot include an entry for "/", since it would be redundant.
设置了home属性,那么routes里不能写“/”的key
比如,下边写法是错误的,那个“/”改个名字

MaterialApp(
      routes: <String, WidgetBuilder>{
        "/": (context) {
          return XXX();
        },
      },
      home: Scaffold()

②:
this.home,
this.routes = const <String, WidgetBuilder>{},
this.initialRoute,
以上3个属性都设置了,home页先展示,如果你设置了initialRoute,那么会自动跳到initialRoute页面,后退可以看到home页

  1. TextField
    android 里的edittext,第一篇好像研究过
    不过这个父容器需要是Material widget
  const TextField({
    Key key,
    this.controller,
    this.focusNode,
    this.decoration = const InputDecoration(),//lable,hint,error,count等都是这个控制的
    TextInputType keyboardType,//字母,数字这种
    this.textInputAction,//android里的next,done
    this.textCapitalization = TextCapitalization.none,//有首字母大写,所有字母大写等,好像没啥用
    this.style,
    this.strutStyle,
    this.textAlign = TextAlign.start,
    this.textAlignVertical,
    this.textDirection,
    this.readOnly = false,
    ToolbarOptions toolbarOptions,
    this.showCursor,
    this.autofocus = false,
    this.obscureText = false,
    this.autocorrect = true,
    this.maxLines = 1,
    this.minLines,
    this.expands = false,
    this.maxLength,//文字个数上限
    this.maxLengthEnforced = true,
    this.onChanged,监听内容的改变,返回string
    this.onEditingComplete,
    this.onSubmitted,
    this.inputFormatters,
    this.enabled,
    this.cursorWidth = 2.0,
    this.cursorRadius,
    this.cursorColor,
    this.keyboardAppearance,
    this.scrollPadding = const EdgeInsets.all(20.0),
    this.dragStartBehavior = DragStartBehavior.start,
    this.enableInteractiveSelection = true,
    this.onTap,
    this.buildCounter,
    this.scrollController,
    this.scrollPhysics,
  })
textInputAction如何生效

这个需要配合 focusNode以及onEditingComplete来使用

  final FocusNode _nameFocus = FocusNode();

  final FocusNode _pswFocus = FocusNode();

   TextField(textInputAction: TextInputAction.done,focusNode: _nameFocus,onEditingComplete: (){
                _nameFocus.nextFocus();
//也可以具体让某个TextField获取焦点 _pswFocus.requestFocus();
              },),

FoucsNode 还有一些别的方法可以参考,比如取消自己的焦点 unFocus() , hasFocus;

  1. RadioListTile
  const RadioListTile({
    Key key,
    @required this.value,
    @required this.groupValue,
    @required this.onChanged,
    this.activeColor,
    this.title,
    this.subtitle,
    this.isThreeLine = false,
    this.dense,
    this.secondary,
    this.selected = false,
    this.controlAffinity = ListTileControlAffinity.platform,
  })

demo以及效果

            RadioListTile(
              value: "third",
              groupValue: _groupValue,
              onChanged: (value) {
                _changeRadioValue(value);
              },
              title: Text("title"),
              subtitle: Text("subtitle"),
              secondary: Icon(Icons.do_not_disturb),
            ),

上边那个是普通的Radio,就个圈圈,两者必须的3个参数是一样的


image.png
  1. Stepper
  const Stepper({
    Key key,
    @required this.steps,
    this.physics,
    this.type = StepperType.vertical,
    this.currentStep = 0,
    this.onStepTapped,
    this.onStepContinue,
    this.onStepCancel,
    this.controlsBuilder,//自定义continue和cancel2个button的
  })
  const Step({
    @required this.title,
    this.subtitle,
    @required this.content,
    this.state = StepState.indexed,
    this.isActive = false,
  })

demo

            Stepper(
              steps: [
                Step(
                  title: Text("title1"),
                  content: Text("content1"),
                  subtitle: Text("subtitle1"),
                  state: StepState.indexed,
                ),
                Step(
                  title: Text("title2"),
                  content: Text("content1"),
                  subtitle: Text("subtitle1"),
                  state: StepState.error,
                ),
                Step(
                  title: Text("title3"),
                  content: Text("content1"),
                  subtitle: Text("subtitle1"),
                  state: StepState.editing,
                ),
                Step(
                  title: Text("title4"),
                  content: Text("content1"),
                  subtitle: Text("subtitle1"),
                  state: StepState.complete,
                ),
              ],
              currentStep: _index,
              onStepTapped: (index) {
                print('step tapped====$index');
                _next(index);
              },
              onStepContinue: () {
                print('continur=========');
                if (_index == 3) {
                  return;
                }
                _next(_index + 1);
              },
              onStepCancel: () {
                print('cancel===========');
                _next(-1);
              },
            ),
  1. 只有title的部分可以点击,如下图
  2. content默认不显示,只有currentStep对应的那个index才能显示,索引按照顺序从0开始
  3. state: StepState,indexed圈圈里显示数字1,2,3..,error是感叹号,complete是对号,edit是个铅笔
  4. continue和cancel2个按钮是否可用取决于onStepContinue/onStepCancel这两个参数是否设置,没设置就不可用,设置了就可用
image.png

另外continue和cancel2个button是可以自定义的,如下参数

              controlsBuilder: (BuildContext context,
                  {VoidCallback onStepContinue, VoidCallback onStepCancel}) {
                return Row(
                  children: <Widget>[
                    FlatButton(
                      onPressed: onStepContinue,
                      child: const Text('xxx'),
                    ),
                    FlatButton(
                      onPressed: onStepCancel,
                      child: const Text('yyy'),
                    ),
                  ],
                );
              },
  1. Divider
    一个逻辑1像素厚的水平分割线,两边都有填充
  const Divider({
    Key key,
    this.height,//占用的高度,画笔默认是1像素的,和这个高度无关
    this.thickness,//画笔的粗细,可以不设置,默认1像素
    this.indent,//左边缩进
    this.endIndent,//结尾缩进
    this.color,//画笔颜色
  })

demo

Divider(height: 20,thickness: null,color: Colors.deepOrange,indent: 20,endIndent: 20,),
image.png
  1. ListTile
    一个固定高度的行,通常包含一些文本,以及一个行前或行尾图标。
  const ListTile({
    Key key,
    this.leading,//头
    this.title,//
    this.subtitle,
    this.trailing,//尾
    this.isThreeLine = false,//默认是两行高度,为true就成了三行高度了
    this.dense,//true的话里边view的间距稍微变大了一点
    this.contentPadding,//字面意思,四周的padding
    this.enabled = true,
    this.onTap,
    this.onLongPress,
    this.selected = false,//为true以后下边的文字颜色啥的都成了主题色了
  })

demo

            ListTile(
              leading: Text("leading"),
              title: Text("title"),
              subtitle: Text("subtitle"),
              trailing: Icon(Icons.done),
              onTap: () {},
              selected: false,
              dense: false,
              isThreeLine: false,
            ),
image.png
  1. LinearProgressIndicator
    一个线性进度条,另外还有一个圆形进度条CircularProgressIndicator
  const LinearProgressIndicator({
    Key key,
    double value,//进度从0到1
    Color backgroundColor,//背景色
    Animation<Color> valueColor,//进度条的颜色,不设置的话默认用的主题色
    String semanticsLabel,
    String semanticsValue,
  })

单色可以设置valueColor为AlwaysStoppedAnimation只有一种颜色
多色的,估计得自定义了,然后根据进度来返回不同的颜色,我是想学线性渐变的,可不知道咋混色

  1. CircularProgressIndicator
  const CircularProgressIndicator({
    Key key,
    double value,//不设置这个值的话,显示的是那种自动转圈的进度条,有值的话就是正常的圆形进度条
    Color backgroundColor,
    Animation<Color> valueColor,
    this.strokeWidth = 4.0,
    String semanticsLabel,
    String semanticsValue,
  })

https://material.io/design/components/progress-indicators.html#circular-progress-indicators

  1. DataTable
    比table更复杂点而已,多了个多选,编辑的功能
  DataTable({
    Key key,
    @required this.columns,//最上边一行,也就是每列的名字
    this.sortColumnIndex,//按照哪里一列排序
    this.sortAscending = true,
    this.onSelectAll,//点击全选按钮的时候的回调
    this.dataRowHeight = kMinInteractiveDimension,
    this.headingRowHeight = 56.0,
    this.horizontalMargin = 24.0,
    this.columnSpacing = 56.0,
    @required this.rows,//行数据
  })

demo以及效果

带编辑和选择的效果.png

无选择以及编辑按钮的效果.png

列数据List<DataColumn>

  var column = ["column1", "column2", "column3"];
  List<DataColumn> createColumn() {
    return column.map((value) {
      return DataColumn(
          label: Text(value),//显示的widget
          tooltip: "sort by $value",//长按的提示文字
          numeric: false,
          onSort: (int columnIndex, bool ascending) {//点击列名的时候会走这里,然后修改下排序条件就好
            _sortBy(columnIndex);
          });
    }).toList();
  }

每行的数据生成

  static var rows = [
    "first",
    "second",
    "third",
    "fourth",
    "fifth",
    "sixth",
  ];
  var rowsSelected = new List<bool>();//在initState里初始话下,默认都为false或者ture,存储每行的选中状态
      for (int i = 0; i < rows.length; i++) {
        rowsSelected.add(false);
      }

  List<DataRow> createRow() {
    var list = rows.map((value) {
      int index = rows.indexOf(value);
      return DataRow(
          cells: createCell(value),//每行的列数据
          selected: rowsSelected[index],//选中状态
          onSelectChanged: (bool selected) {//为null的话此行不可选择,否则可以选择
            print('onSelectChanged===$selected');
            setState(() {
              rowsSelected[index] = selected;
            });
          }
          );
    }).toList();
    list.sort((row1, row2) {
      return getCompare(row1).compareTo(getCompare(row2));
    });
    return list;
  }

  String getCompare(DataRow row) {
    ValueKey key = row.cells[_sortColumnIndex].child.key;
    return key.value;
  }

//每一行的列数和最上边的column的个数是一样的
  List createCell(String value) {
    return column.map((row) {
      return DataCell(
          Text(
            "$value=$row",
            key: Key("$value=$row"),
          ),
          placeholder: false,
          showEditIcon: false, //是否显示那个编辑按钮
onTap: () {
        print('on tap====');
      });
    }).toList();
  }

最后整体使用

            DataTable(
              columns: createColumn(),
              sortColumnIndex: _sortColumnIndex,
              sortAscending: false,
              onSelectAll: (value) {
             //如果可以选中的话,第一行左边有个全选框,这里是回调
                setState(() {
                  for (int i = 0; i < rowsSelected.length; i++) {
                    rowsSelected[i] = value;
                  }
                });
              },
              rows: createRow(),
            ),
  1. Tooltip
    就是长按一个widget给个提示文字
                  Tooltip(
                    message: "message",//提示文字
                    height: 80,
                    padding: EdgeInsets.all(5),
                    margin: EdgeInsets.all(10),
                    verticalOffset: 10,//和child的偏移量,上边或者下边
                    preferBelow: true,//tip在child的下边,上边的offset就是往下的距离,相反在child上边
                    decoration: BoxDecoration(border: Border.all(color:Colors.red)),//tip的背景设置
                    textStyle: TextStyle(fontSize: 22),
                    waitDuration: Duration(seconds: 1),
                    showDuration: Duration(seconds: 3),//字面意思,tip显示多久
                    child: SizedBox(width: 100,child: Text("toolTip"),),
                  ),
image.png
  1. Chip
    一个圆角的容器,效果图如下,可以添加3个widget,最后一个deleteIcon可以添加点击事件


    image.png

    top和bottom建议写在lable的padding上,

                  Chip(
                    avatar: Icon(Icons.map),
                    label: Text("lable..."),
                    labelPadding: EdgeInsets.only(top: 10, bottom: 10),
                    deleteIcon: Icon(Icons.clear),
                    onDeleted: () {
                      print('delete===');
                    },
                    deleteIconColor: Colors.red,
                    padding: EdgeInsets.only(
                      left: 20,
                      right: 20,
                    ),
                    backgroundColor: Colors.brown,
                  ),
  1. SnackBar
    和android一样的,底部弹出的一个提示框,有一个从上往下展开的动画
  const SnackBar({
    Key key,
    @required this.content,//widget 提示内容
    this.backgroundColor,//
    this.elevation,
    this.shape,//背景形状
    this.behavior,
    this.action,//多个文本按钮
    this.duration = _snackBarDisplayDuration,
    this.animation,//动画,必须的参数
  })

效果:默认背景是黑色的


默认样式.png
behavior为floating的效果,多了个阴影.png
class ComponentLearnState extends State with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> anim;//取值只能在0和1之间
  @override
  void initState() {
    super.initState();
    //
    controller =
        AnimationController(vsync: this, duration: Duration(seconds: 2));
    var tween = Tween(begin: 0.0, end: 1.0);
    anim = tween.animate(controller);
  }

      controller.reset();
      controller.forward();
      return SnackBar(
        content: Text("snack bar content..."),
        animation: anim,
        backgroundColor: Colors.deepPurple,
        behavior: SnackBarBehavior.floating,
        action: SnackBarAction(
            label: "close",
            textColor: Colors.white,
            onPressed: () {
              setState(() {
                _changeValue(!_value);
              });
            }),
      );

其他

  1. SimpleDialog
  const SimpleDialog({
    Key key,
    this.title,
    this.titlePadding = const EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 0.0),
    this.children,
    this.contentPadding = const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 16.0),
    this.backgroundColor,
    this.elevation,
    this.semanticLabel,
    this.shape,
  })

可以通过showDialog方法显示成dialog


image.png
  const AlertDialog({
    Key key,
    this.title,
    this.titlePadding,
    this.titleTextStyle,
    this.content,
    this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),
    this.contentTextStyle,
    this.actions,
    this.backgroundColor,
    this.elevation,
    this.semanticLabel,
    this.shape,
  })

demo

            AlertDialog(
              title: Text("title"),
              content: Text("content widget"),
              actions: <Widget>[
                FlatButton(onPressed: () {}, child: Text("ok")),
                RaisedButton(
                  onPressed: () {},
                  child: Text(
                    "cancel",
                    style: TextStyle(color: Colors.red),
                  ),
                )
              ],
            )

可以通过showDialog显示成dialog


image.png
  1. BottomSheet
    BottomSheet是一个从屏幕底部滑起的列表(以显示更多的内容)。你可以调用showBottomSheet()或showModalBottomSheet弹出
  const BottomSheet({
    Key key,
    this.animationController,//不知道有啥用,感觉设置的时间也没用
    this.enableDrag = true,
    this.backgroundColor,//颜色
    this.elevation,
    this.shape,//背景
    @required this.onClosing,//对话框关闭的回调
    @required this.builder,//弹出的内容
  })

demo
方法1:showModalBottomSheet
这种点击BottomSheet外部区域就可以关闭弹框的

    showModalBottomSheet(
        context: context,
        backgroundColor: Colors.transparent,//控制dialog的背景颜色
        builder: (context) {
          return BottomSheet(
            animationController:controller,
              shape: RoundedRectangleBorder(side:BorderSide(color: Colors.red,width: 4),borderRadius: BorderRadius.circular(20)),
              backgroundColor: Colors.blue,
              onClosing: () {
                print('onClosing....');
              },
              builder: (context) {
                return Center(
                 heightFactor: 15,
                  child: Text("test..."),
                );
              });
        });

方式2:showBottomSheet
这种点击外部不会消失的,只有点击后退键才会消失,或者自己手动调用pop方法【也就是模拟后退键的功能】

    showBottomSheet(
        context: context,
        backgroundColor: Colors.transparent,
        builder:builder );

注意事项:这个context不是随便一个就行的,必须最顶层有个scaffold,好像自己也得有一个?反正自身本身就是scaffold,外层再套一个就是没问题的
flutter每个页面或者route就是个widget,指的就是这个,这个需要是Scaffold

    if (context.widget is! Scaffold && context.ancestorWidgetOfExactType(Scaffold) == null) {
      final Element element = context;
      throw FlutterError(
          'No Scaffold widget found.\n'
          '${context.widget.runtimeType} widgets require a Scaffold widget ancestor.\n'
          'The Specific widget that could not find a Scaffold ancestor was:\n'
          '  ${context.widget}\n'
          'The ownership chain for the affected widget is:\n'
          '  ${element.debugGetCreatorChain(10)}\n'
          'Typically, the Scaffold widget is introduced by the MaterialApp or '
          'WidgetsApp widget at the top of your application widget tree.'
      );
    }

如下,原本最外层就是ComponentLearn,结果不行,最后把它放到Scaffold里才ok

class ComponentLearnWrap extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ComponentLearn(),
    );
  }
}

题外话

class ComponentLearnState extends State with SingleTickerProviderStateMixin

SingleTickerProviderStateMixin的作用,就是简单提供给你一个TickerProvier,而且
AnimationController(vsync: this,)这个this只能用一次,不能给2个AnimationController用
如果你需要多个不同的AnimationController,那么就自己继承TickerProvider
如下

class ComponentLearnState extends State implements TickerProvider {
  @override
  Ticker createTicker(onTick) {
    return Ticker(onTick);
  }
  1. ExpansionPanel【还有一个类似的ExpansionPanelRadio,单选的】
    就是一个可以展开的面板,有个header一直显示的,还有个body根据状态显示或隐藏
    不可单独使用,只能放在ExpansionPanelList里用
  ExpansionPanel({
    @required this.headerBuilder,//header一直显示的部分
    @required this.body,//body 展开的部分
    this.isExpanded = false,//是否显示body
    this.canTapOnHeader = false,//false只有点击那个箭头才可以,true的话整个header可点击
  })

  const ExpansionPanelList({
    Key key,
    this.children = const <ExpansionPanel>[],
    this.expansionCallback,//回调,panel上的箭头或者header点击的时候会回调,告诉你点的哪个以及当前状态
    this.animationDuration = kThemeAnimationDuration,
  })

demo以及效果

  var state = [false, false];
//
            ExpansionPanelList(
              children: [
                ExpansionPanel(
                    headerBuilder: (context, isExpand) {
                      return Text("header 1");
                    },
                    body: Text("first........"),
                    isExpanded: state[0],
                    canTapOnHeader: true),
                ExpansionPanel(
                    headerBuilder: (context, isExpand) {
                      return Text("header 2");
                    },
                    body: Text("second........"),
                    isExpanded: state[1],
                    canTapOnHeader: false),
              ],
              expansionCallback: (int panelIndex, bool isExpanded) {
                print('callback====$panelIndex===$isExpanded');
                setState(() {
                  state[panelIndex] = !isExpanded;
                });
              },
            ),

按压的效果,header1我们设置可以点的


image.png

简单看下源码
header和icon是放在一个Row里的,高度也有个最小值64

      Widget header = Row(
        children: <Widget>[
          Expanded(
            child: AnimatedContainer(
              duration: widget.animationDuration,
              curve: Curves.fastOutSlowIn,
              margin: _isChildExpanded(index) ? kExpandedEdgeInsets : EdgeInsets.zero,
              child: ConstrainedBox(
                constraints: const BoxConstraints(minHeight: _kPanelHeaderCollapsedHeight),
                child: headerWidget,
              ),
            ),
          ),
          expandIconContainer,
        ],
      );
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容