Cupertino (iOS风格) Widgets
https://flutterchina.club/widgets/cupertino/
- CupertinoActivityIndicator
一个iOS风格的loading指示器。显示一个圆形的转圈菊花
const CupertinoActivityIndicator({
Key key,
this.animating = true,//是否动画
this.radius = _kDefaultIndicatorRadius,//圈圈的半径
})
- CupertinoAlertDialog
iOS风格的alert dialog.当然了这个控件是个widget而已,
const CupertinoAlertDialog({
Key key,
this.title,
this.content,
this.actions = const [],
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: [
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;
});
}
- 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),
紅色那個按钮是按压效果
-
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的线明显断了,如果你需要矩形圆角控件的话可以用这个
- CupertinoDialogAction
2里边那几个action控件就是,这里就不研究了,这玩意就和CupertinoAlertDialog一起用吧,要不单独的没有点击效果,不好看
const CupertinoDialogAction({
this.onPressed,
this.isDefaultAction = false,
this.isDestructiveAction = false,
this.textStyle,
@required this.child,
})
- 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给个具体的宽
- 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,
),
- 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 = [Icons.four_k, Icons.build, Icons.access_alarm];
List 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,
)
Material Components Widgets
https://flutterchina.club/widgets/material/
- 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,
})
- 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 items,
) {
if (type != null) {
return type;
}
return items.length <= 3 ? BottomNavigationBarType.fixed : BottomNavigationBarType.shifting;
}
fixed:图片文字都显示,item大小一致,选中颜色默认是材料主题色,BottomNavigationBarItem的背景参数无效,
shifting:只有选中的那个显示文字,并且比较大,另外也没有选中颜色一说了,而且backgroundColor无效,
实际看到的是BottomNavigationBarItem的背景颜色
注意:
下边两个都是设置选中的item的颜色的,不过只能设置其中一个,否则异常
assert(
selectedItemColor == null || fixedColor == null,
'Either selectedItemColor or fixedColor can be specified, but not both'
)
- MaterialApp
const MaterialApp({
Key key,
this.navigatorKey,
this.home,
this.routes = const {},
this.initialRoute,
this.onGenerateRoute,
this.onUnknownRoute,
this.navigatorObservers = const [],
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('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: {
"/": (context) {
return XXX();
},
},
home: Scaffold()
②:
this.home,
this.routes = const
this.initialRoute,
以上3个属性都设置了,home页先展示,如果你设置了initialRoute,那么会自动跳到initialRoute页面,后退可以看到home页
- 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;
- 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个参数是一样的
- 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);
},
),
- 只有title的部分可以点击,如下图
- content默认不显示,只有currentStep对应的那个index才能显示,索引按照顺序从0开始
- state: StepState,indexed圈圈里显示数字1,2,3..,error是感叹号,complete是对号,edit是个铅笔
- continue和cancel2个按钮是否可用取决于onStepContinue/onStepCancel这两个参数是否设置,没设置就不可用,设置了就可用
另外continue和cancel2个button是可以自定义的,如下参数
controlsBuilder: (BuildContext context,
{VoidCallback onStepContinue, VoidCallback onStepCancel}) {
return Row(
children: [
FlatButton(
onPressed: onStepContinue,
child: const Text('xxx'),
),
FlatButton(
onPressed: onStepCancel,
child: const Text('yyy'),
),
],
);
},
- 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,),
- 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,
),
- LinearProgressIndicator
一个线性进度条,另外还有一个圆形进度条CircularProgressIndicator
const LinearProgressIndicator({
Key key,
double value,//进度从0到1
Color backgroundColor,//背景色
Animation valueColor,//进度条的颜色,不设置的话默认用的主题色
String semanticsLabel,
String semanticsValue,
})
单色可以设置valueColor为AlwaysStoppedAnimation只有一种颜色
多色的,估计得自定义了,然后根据进度来返回不同的颜色,我是想学线性渐变的,可不知道咋混色
- CircularProgressIndicator
const CircularProgressIndicator({
Key key,
double value,//不设置这个值的话,显示的是那种自动转圈的进度条,有值的话就是正常的圆形进度条
Color backgroundColor,
Animation valueColor,
this.strokeWidth = 4.0,
String semanticsLabel,
String semanticsValue,
})
https://material.io/design/components/progress-indicators.html#circular-progress-indicators
- 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以及效果
列数据List
var column = ["column1", "column2", "column3"];
List 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();//在initState里初始话下,默认都为false或者ture,存储每行的选中状态
for (int i = 0; i < rows.length; i++) {
rowsSelected.add(false);
}
List 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(),
),
- 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"),),
),
-
Chip
一个圆角的容器,效果图如下,可以添加3个widget,最后一个deleteIcon可以添加点击事件
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,
),
- 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,//动画,必须的参数
})
效果:默认背景是黑色的
class ComponentLearnState extends State with SingleTickerProviderStateMixin {
AnimationController controller;
Animation 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);
});
}),
);
其他
- 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
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: [
FlatButton(onPressed: () {}, child: Text("ok")),
RaisedButton(
onPressed: () {},
child: Text(
"cancel",
style: TextStyle(color: Colors.red),
),
)
],
)
可以通过showDialog显示成dialog
- 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);
}
- 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 [],
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我们设置可以点的
简单看下源码
header和icon是放在一个Row里的,高度也有个最小值64
Widget header = Row(
children: [
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,
],
);