老孟導讀:Flutter 1.20 更新了 Slider、RangeSlider、日期選擇器組件、時間選擇器組件的樣式,新增了交換組件:InteractiveViewer,下面詳細介紹其用法。
滑塊
Flutter 1.20 版本將 Slider 和 RangeSlider 小部件更新為最新的 Material 準則。新的滑塊在設計時考慮到了更好的可訪問性:軌道更高,滑塊帶有陰影,並且值指示器具有新的形狀和改進的文本縮放支持。
Slider
基礎用法:
<code>class SliderDemo extends StatefulWidget {
@override
_SliderDemoState createState() => _SliderDemoState();
}
class _SliderDemoState extends State<SliderDemo> {
double _sliderValue = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('值:$_sliderValue'),
Slider(
value: _sliderValue,
onChanged: (v){
setState(() {
_sliderValue = v;
});
},
)
],
),
),
);
}
}/<code>
- value:當前值。
- onChanged:滑塊值改變時回調。
看看 Flutter 1.20 版本以前的樣式(我的珍藏):
明顯的感覺就是滑塊軌道變粗了,滑塊變的更有立體感(加了陰影)了。
Slider 默認滑動範圍是 0-1,修改為 1-100:
<code>Slider(
value: _sliderValue,
min: 1,
max: 100,
onChanged: (v){
setState(() {
_sliderValue = v;
});
},
)/<code>
設置滑塊的滑動為 離散的,即滑動值為 0、25 、50、75 100:
<code>Slider(
value: _sliderValue,
min: 0,
max: 100,
divisions: 4,
onChanged: (v){
setState(() {
_sliderValue = v;
});
},
)/<code>
設置標籤,滑動過程中在其上方顯示:
<code>Slider(
value: _sliderValue,
label: '$_sliderValue',
min: 0,
max: 100,
divisions: 4,
onChanged: (v){
setState(() {
_sliderValue = v;
});
},
)/<code>
看看 Flutter 1.20 版本以前的樣式(依然是我的珍藏):
個人感覺以前的更好看。
下面是官方給的 Slider 結構圖:
- 1 :軌道(Track),1 和 4 是有區別的,1 指的是底部整個軌道,軌道顯示了可供用戶選擇的範圍。對於從左到右(LTR)的語言,最小值出現在軌道的最左端,而最大值出現在最右端。對於從右到左(RTL)的語言,此方向是相反的。
- 2:滑塊(Thumb),位置指示器,可以沿著軌道移動,顯示其位置的選定值。
- 3:標籤(label),顯示與滑塊的位置相對應的特定數字值。
- 4:刻度指示器(Tick mark),表示用戶可以將滑塊移動到的預定值。
自定義滑塊 激活的顏色 和 未激活的顏色:
<code>Slider(
activeColor: Colors.red,
inactiveColor: Colors.blue,
value: _sliderValue,
label: '$_sliderValue',
min: 0,
max: 100,
divisions: 4,
onChanged: (v){
setState(() {
_sliderValue = v;
});
},
)/<code>
這個自定義比較籠統,下面來一個更細緻的自定義:
<code>SliderTheme(
data: SliderTheme.of(context).copyWith(
activeTrackColor: Color(0xff404080),
thumbColor: Colors.blue,
overlayColor: Colors.green,
valueIndicatorColor: Colors.purpleAccent),
child: Slider(
value: _sliderValue,
label: '$_sliderValue',
min: 0,
max: 100,
divisions: 4,
onChanged: (v) {
setState(() {
_sliderValue = v;
});
},
),
)/<code>
這個基本可以完全自定義樣式了。
如何在 Flutter 1.20 版本使用以前的標籤樣式呢?
<code>SliderTheme(
data: SliderTheme.of(context).copyWith(
valueIndicatorShape: PaddleSliderValueIndicatorShape(),
),
child: Slider(
value: _sliderValue,
label: '$_sliderValue',
min: 0,
max: 100,
divisions: 4,
onChanged: (v) {
setState(() {
_sliderValue = v;
});
},
),
)/<code>
RectangularSliderValueIndicatorShape 表示矩形樣式:
RangeSlider
RangeSlider 和 Slider 幾乎一樣,RangeSlider 是範圍滑塊,想要選擇一段值,可以使用 RangeSlider。
<code>RangeValues _rangeValues = RangeValues(0, 25);
RangeSlider(
values: _rangeValues,
labels: RangeLabels('${_rangeValues.start}','${_rangeValues.end}'),
min: 0,
max: 100,
divisions: 4,
onChanged: (v) {
setState(() {
_rangeValues = v;
});
},
),/<code>
滑塊狀態
ios風格的 Slider
ios風格的 Slider,使用 CupertinoSlider:
<code>double _sliderValue = 0;
CupertinoSlider(
value: _sliderValue,
onChanged: (v) {
setState(() {
_sliderValue = v;
});
},
)
/<code>
當然也可以根據平臺顯示不同風格的Slider,ios平臺顯示CupertinoSlider效果,其他平臺顯示Material風格,用法如下:
<code>Slider.adaptive(
value: _sliderValue,
onChanged: (v) {
setState(() {
_sliderValue = v;
});
},
)
/<code>
Material風格日期選擇器
Flutter 1.20 版本更新了 日期 類組件的樣式,加入了新的緊湊設計以及對日期範圍的支持。
showDatePicker
結構圖
- 標題
- 選中的日期
- 切換到輸入模式
- 年選擇菜單
- 月份分頁
- 當前時間
- 選中日期
輸入模式 結構圖:
- 標題
- 選中日期
- 切換 日曆模式
- 輸入框
基礎用法
點擊按鈕彈出日期組件:
<code> RaisedButton(
child: Text('彈出日期組件'),
onPressed: () async {
await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2010),
lastDate: DateTime(2025),
);/<code>
- initialDate:初始化時間,通常情況下設置為當前時間。
- firstDate:表示開始時間,不能選擇此時間前面的時間。
- lastDate:表示結束時間,不能選擇此時間之後的時間。
設置日期選擇器對話框的模式:
<code>var result = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2010),
lastDate: DateTime(2025),
initialEntryMode: DatePickerEntryMode.input,
);/<code>
直接顯示 輸入模式,默認是日曆模式。
設置日曆日期選擇器的初始顯示,包含 day 和 year:
<code>var result = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2010),
lastDate: DateTime(2025),
initialDatePickerMode: DatePickerMode.year,
);/<code>
和以前的版本對比:
設置頂部標題、取消按鈕、確定按鈕 文案:
<code>var result = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2010),
lastDate: DateTime(2025),
helpText: '選則日期',
cancelText: '取消',
confirmText: '確定',
);/<code>
修改 輸入模式 下文案:
<code>var result = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2010),
lastDate: DateTime(2025),
errorFormatText: '錯誤的日期格式',
errorInvalidText: '日期格式非法',
fieldHintText: '月/日/年',
fieldLabelText: '填寫日期',
);/<code>
設置可選日期範圍
<code>var result = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2010),
lastDate: DateTime(2025),
selectableDayPredicate: (date) {
return date.difference(DateTime.now()).inMilliseconds < 0;
},
);/<code>
今天以後的日期全部為灰色,不可選狀態。
設置深色主題
設置深色主題使 <code>builder/<code> ,其用於包裝對話框窗口小部件以添加繼承的窗口小部件,例如<code>Theme/<code>,設置深色主題如下:
<code>var result = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2010),
lastDate: DateTime(2025),
builder: (context,child){
return Theme(
data: ThemeData.dark(),
child: child,
);
}
);/<code>
獲取選中的日期
showDatePicker 方法是 Future 方法,點擊日期選擇控件的確定按鈕後,返回選擇的日期。
<code>var result = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2010),
lastDate: DateTime(2025),
);
print('$result');/<code>
result 為選擇的日期。
CalendarDatePicker
日期組件直接顯示在頁面上,而不是彈出顯示:
<code>CalendarDatePicker(
initialDate: DateTime.now(),
firstDate: DateTime(2010),
lastDate: DateTime(2025),
onDateChanged: (d) {
print('$d');
},
)/<code>
其參數和 showDatePicker 一樣。
範圍日期
選擇範圍日期使用 showDateRangePicker:
<code>RaisedButton(
child: Text('範圍日期'),
onPressed: () async {
var date = showDateRangePicker(context: context, firstDate: DateTime(2010), lastDate: DateTime(2025));
},
),/<code>
其參數和 showDatePicker 一樣。
範圍日期結構圖:
- 標題
- 選定的日期範圍
- 切換到輸入模式
- 月和年標籤
- 當前時間
- 開始時間
- 選中時間範圍
- 結束時間
國際化
國際化都是一個套路,下面以 showDatePicker 為例:
在 pubspec.yaml 中引入:
<code>dependencies:
flutter_localizations:
sdk: flutter/<code>
在頂級組件 MaterialApp 添加支持:
<code>MaterialApp(
title: 'Flutter Demo',
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('zh'),
const Locale('en'),
],
.../<code>
彈出日期組件:
<code>var result = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2010),
lastDate: DateTime(2025),
);/<code>
此時將系統語音調整為
中文:此組件只支持中文,不管系統設置語言:
<code>var result = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2010),
lastDate: DateTime(2025),
locale: Locale('zh')
);/<code>
Material風格時間選擇器
Flutter 1.20 版本更新了
時間 類組件的樣式。基礎使用
彈出時間組件:
<code>RaisedButton(
child: Text('彈出時間選擇器'),
onPressed: () async {
var result =
showTimePicker(context: context, initialTime: TimeOfDay.now());
},
)/<code>
1.20 版以前的效果:
設置 交互模式,交互模式包含 時鐘模式(默認)和 輸入模式。
<code>var result = showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
initialEntryMode: TimePickerEntryMode.input);/<code>
時鐘模式(TimePickerEntryMode.dial):
輸入模式(TimePickerEntryMode.input):
設置頂部標題、取消按鈕、確定按鈕 文案:
<code>var result = showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
initialEntryMode: TimePickerEntryMode.input,
helpText: '選擇時間',
cancelText: '取消',
confirmText: '確定');/<code>
24小時 制:
<code>var result = showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
builder: (BuildContext context, Widget child) {
return MediaQuery(
data: MediaQuery.of(context)
.copyWith(alwaysUse24HourFormat: true),
child: child,
);
},
);/<code>
黑暗模式
<code>var result = showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
builder: (BuildContext context, Widget child) {
return Theme(
data: ThemeData.dark(),
child: child,
);
},
);/<code>
國際化
在 pubspec.yaml 中引入:
<code>dependencies:
flutter_localizations:
sdk: flutter/<code>
在頂級組件 MaterialApp 添加支持:
<code>MaterialApp(
title: 'Flutter Demo',
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('zh'),
const Locale('en'),
],
.../<code>
彈出時間組件:
<code>RaisedButton(
child: Text('彈出時間選擇器'),
onPressed: () async {
var result =
showTimePicker(context: context, initialTime: TimeOfDay.now());
},
)/<code>
切換系統語言為中文:
不跟隨系統語言,直接指定,比如當前系統語言為中文,指定為英文:
<code>var result = showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
builder: (BuildContext context, Widget child) {
return Localizations(
locale: Locale('en'),
delegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
child: child,
);
},
);/<code>
iOS風格日期選擇器
基礎使用
CupertinoDatePicker 是 iOS風格的日期選擇器。
<code>class CupertinoDatePickerDemo extends StatefulWidget {
@override
_CupertinoDatePickerDemoState createState() => _CupertinoDatePickerDemoState();
}
class _CupertinoDatePickerDemoState extends State<CupertinoDatePickerDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Container(
height: 200,
color: Colors.grey.withOpacity(.5),
child: CupertinoDatePicker(
initialDateTime: DateTime.now(),
onDateTimeChanged: (date) {
print('$date');
},
),
),
),
);
}
}/<code>
設置最大/小時間:
<code>CupertinoDatePicker(
initialDateTime: DateTime.now(),
minimumDate: DateTime.now().add(Duration(days: -1)),
maximumDate: DateTime.now().add(Duration(days: 1)),
onDateTimeChanged: (date) {
print('$date');
},
)/<code>
最大時間為明天,最小時間為昨天:
設置模式為時間:
<code>CupertinoDatePicker(
mode: CupertinoDatePickerMode.time,
initialDateTime: DateTime.now(),
onDateTimeChanged: (date) {
print('$date');
},
)/<code>
設置模式為日期:
<code>CupertinoDatePicker(
mode: CupertinoDatePickerMode.date,
initialDateTime: DateTime.now(),
onDateTimeChanged: (date) {
print('$date');
},
)/<code>
設置模式為日期和時間:
<code>CupertinoDatePicker(
mode: CupertinoDatePickerMode.dateAndTime,
initialDateTime: DateTime.now(),
onDateTimeChanged: (date) {
print('$date');
},
)/<code>
- time:只顯示時間,效果:<code>4 | 14 | PM/<code>
- date:只顯示日期,效果:<code>July | 13 | 2012/<code>
- dateAndTime:時間和日期都顯示,效果: <code>Fri Jul 13 | 4 | 14 | PM/<code>
使用24小時制:
<code>CupertinoDatePicker(
use24hFormat: true,
initialDateTime: DateTime.now(),
onDateTimeChanged: (date) {
print('$date');
},
)/<code>
國際化
在 pubspec.yaml 中引入:
<code>dependencies:
flutter_localizations:
sdk: flutter/<code>
在頂級組件 MaterialApp 添加支持:
<code>MaterialApp(
title: 'Flutter Demo',
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('zh'),
const Locale('en'),
],
.../<code>
組件使用:
<code>CupertinoDatePicker(
initialDateTime: DateTime.now(),
onDateTimeChanged: (date) {
print('$date');
},
)/<code>
組件語言跟隨系統語言,當前系統語言為英文,效果:
不跟隨系統語言,直接指定,比如當前系統語言為英文,指定為中文:
<code>Localizations(
locale: Locale('zh'),
delegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
child: CupertinoDatePicker(
initialDateTime: DateTime.now(),
onDateTimeChanged: (date) {
print('$date');
},
),
)/<code>
iOS風格時間選擇器
基礎使用
CupertinoTimerPicker 是 iOS風格的時間選擇器。
<code>CupertinoTimerPicker(onTimerDurationChanged: (time) {
print('$time');
})/<code>
設置顯示模式:
- CupertinoTimerPickerMode.hm :顯示 小時 | 分鐘,英文效果<code>16 hours | 14 min/<code>
- CupertinoTimerPickerMode.ms: 顯示 分鐘 | 秒,英文效果<code>14 min | 43 sec/<code>
- CupertinoTimerPickerMode.hms:顯示 小時 | 分鐘 | 秒,英文效果<code>16 hours | 14 min | 43 sec/<code>
<code>CupertinoTimerPicker(
mode: CupertinoTimerPickerMode.hm,
onTimerDurationChanged: (time) {
print('$time');
})/<code>
默認情況下,CupertinoTimerPicker顯示0:0:0,設置顯示當前時間:
<code>CupertinoTimerPicker(
initialTimerDuration: Duration(
hours: DateTime.now().hour,
minutes: DateTime.now().minute,
seconds: DateTime.now().second),
onTimerDurationChanged: (time) {
print('$time');
})/<code>
設置 分/秒 的間隔:
<code>CupertinoTimerPicker(
minuteInterval: 5,
secondInterval: 5,
onTimerDurationChanged: (time) {
print('$time');
})/<code>
國際化
在 pubspec.yaml 中引入:
<code>dependencies:
flutter_localizations:
sdk: flutter/<code>
在頂級組件 MaterialApp 添加支持:
<code>MaterialApp(
title: 'Flutter Demo',
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('zh'),
const Locale('en'),
],
.../<code>
組件使用:
<code>CupertinoTimerPicker(onTimerDurationChanged: (time) {
print('$time');
})/<code>
組件語言跟隨系統語言,當前系統語言為英文,效果:
不跟隨系統語言,直接指定,比如當前系統語言為英文,指定為中文:
<code>Localizations(
locale: Locale('zh'),
delegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
child: CupertinoTimerPicker(onTimerDurationChanged: (time) {
print('$time');
}),
)/<code>
InteractiveViewer
InteractiveViewer 是 Flutter 1.20 新增的組件,用戶可以通過拖動以平移、縮放和拖放子組件。
<code>InteractiveViewer(
child: Image.asset('assets/images/go_board_09x09.png'),
)/<code>
alignPanAxis 參數表示是否只在水平和垂直方向上拖拽,默認為false,設置為true,無法沿著對角線(斜著)方向移動。
<code>InteractiveViewer(
alignPanAxis: true,
child: Image.asset('assets/images/go_board_09x09.png'),
)/<code>
maxScale 、minScale、scaleEnabled 是縮放相關參數,分別表示最大縮放倍數、最小縮放倍數、是否可以縮放:
<code>InteractiveViewer(
maxScale: 2,
minScale: 1,
scaleEnabled: true,
child: Image.asset('assets/images/go_board_09x09.png'),
)/<code>
constrained 參數表示組件樹中的約束是否應用於子組件,默認為true,如果設為true,表示子組件是無限制約束,這對子組件的尺寸比 InteractiveViewer 大時非常有用,比如子組件為滾動系列組件。
如下的案例,子組件為 Table,Table 尺寸大於屏幕,必須將<code>constrained/<code>設置為 false 以便將其繪製為完整尺寸。超出的屏幕尺寸可以平移到視圖中。
<code>class InteractiveViewerDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
const int _rowCount = 20;
const int _columnCount = 10;
return Scaffold(
appBar: AppBar(),
body: Center(
child: Container(
height: 300,
width: 300,
child: InteractiveViewer(
constrained: false,
child: Table(
columnWidths: <int, TableColumnWidth>{
for (int column = 0; column < _columnCount; column += 1)
column: const FixedColumnWidth(100.0),
},
children: <TableRow>[
for (int row = 0; row < _rowCount; row += 1)
TableRow(
children: <Widget>[
for (int column = 0; column < _columnCount; column += 1)
Container(
height: 50,
color: row % 2 + column % 2 == 1
? Colors.red
: Colors.green,
),
],
),
],
),
),
),
),
);
}
}/<code>
回調事件:
- onInteractionStart:當用戶開始平移或縮放手勢時調用。
- onInteractionUpdate:當用戶更新組件上的平移或縮放手勢時調用。
- onInteractionEnd:當用戶在組件上結束平移或縮放手勢時調用。
<code>InteractiveViewer(
child: Image.asset('assets/images/go_board_09x09.png'),
onInteractionStart: (ScaleStartDetails scaleStartDetails){
print('onInteractionStart:$scaleStartDetails');
},
onInteractionUpdate: (ScaleUpdateDetails scaleUpdateDetails){
print('onInteractionUpdate:$scaleUpdateDetails');
},
onInteractionEnd: (ScaleEndDetails endDetails){
print('onInteractionEnd:$endDetails');
},
)/<code>
通過 Matrix4 矩陣對其進行變換,比如左移、放大等,添加變換控制器:
<code>final TransformationController _transformationController =
TransformationController();
InteractiveViewer(
child: Image.asset('assets/images/go_board_09x09.png'),
transformationController: _transformationController,
)/<code>
放大變換:
<code>var matrix = _transformationController.value.clone();
matrix.scale(1.5, 1.0, 1.0);
_transformationController.value = matrix;/<code>
完整代碼:
<code>import 'dart:math';
import 'package:flutter/material.dart';
///
/// desc:
///
class InteractiveViewerDemo extends StatefulWidget {
@override
_InteractiveViewerDemoState createState() => _InteractiveViewerDemoState();
}
class _InteractiveViewerDemoState extends State<InteractiveViewerDemo> {
final TransformationController _transformationController =
TransformationController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: Center(
child: InteractiveViewer(
child: Image.asset('assets/images/go_board_09x09.png'),
transformationController: _transformationController,
),
),
),
Expanded(
child: Container(),
),
Row(
children: [
RaisedButton(
child: Text('重置'),
onPressed: () {
_transformationController.value = Matrix4.identity();
},
),
RaisedButton(
child: Text('左移'),
onPressed: () {
var matrix = _transformationController.value.clone();
matrix.translate(-5.0);
_transformationController.value = matrix;
},
),
RaisedButton(
child: Text('放大'),
onPressed: () {
var matrix = _transformationController.value.clone();
matrix.scale(1.5, 1.0, 1.0);
_transformationController.value = matrix;
},
),
],
),
],
),
);
}
}/<code>
交流
老孟Flutter博客地址(330個控件用法):http://laomengit.com
歡迎加入Flutter交流群(微信:laomengit)、關注公眾號【老孟Flutter】
閱讀更多 老孟程序員 的文章