【Flutter 實戰】1.20版本更新及新增組件


老孟導讀:Flutter 1.20 更新了 Slider、RangeSlider、日期選擇器組件、時間選擇器組件的樣式,新增了交換組件:InteractiveViewer,下面詳細介紹其用法。

滑塊

Flutter 1.20 版本將 SliderRangeSlider 小部件更新為最新的 Material 准則。新的滑塊在設計時考慮到了更好的可訪問性:軌道更高,滑塊帶有陰影,並且值指示器具有新的形狀和改進的文本縮放支持。

Slider

基礎用法:

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;
                });
              },
            )
          ],
        ),
      ),
    );
  }
}
  • value:當前值。
  • onChanged:滑塊值改變時回調。

看看 Flutter 1.20 版本以前的樣式(我的珍藏):

明顯的感覺就是滑塊軌道變粗了,滑塊變的更有立體感(加了陰影)了。

Slider 默認滑動范圍是 0-1,修改為 1-100:

Slider(
  value: _sliderValue,
  min: 1,
  max: 100,
  onChanged: (v){
    setState(() {
      _sliderValue = v;
    });
  },
)

設置滑塊的滑動為 離散的,即滑動值為 0、25 、50、75 100:

Slider(
  value: _sliderValue,
  min: 0,
  max: 100,
  divisions: 4,
  onChanged: (v){
    setState(() {
      _sliderValue = v;
    });
  },
)

設置標簽,滑動過程中在其上方顯示:

Slider(
  value: _sliderValue,
  label: '$_sliderValue',
  min: 0,
  max: 100,
  divisions: 4,
  onChanged: (v){
    setState(() {
      _sliderValue = v;
    });
  },
)

看看 Flutter 1.20 版本以前的樣式(依然是我的珍藏):

個人感覺以前的更好看。

下面是官方給的 Slider 結構圖:

  • 1 :軌道(Track),1 和 4 是有區別的,1 指的是底部整個軌道,軌道顯示了可供用戶選擇的范圍。對於從左到右(LTR)的語言,最小值出現在軌道的最左端,而最大值出現在最右端。對於從右到左(RTL)的語言,此方向是相反的。
  • 2:滑塊(Thumb),位置指示器,可以沿着軌道移動,顯示其位置的選定值。
  • 3:標簽(label),顯示與滑塊的位置相對應的特定數字值。
  • 4:刻度指示器(Tick mark),表示用戶可以將滑塊移動到的預定值。

自定義滑塊 激活的顏色未激活的顏色

Slider(
  activeColor: Colors.red,
  inactiveColor: Colors.blue,
  value: _sliderValue,
  label: '$_sliderValue',
  min: 0,
  max: 100,
  divisions: 4,
  onChanged: (v){
    setState(() {
      _sliderValue = v;
    });
  },
)

這個自定義比較籠統,下面來一個更細致的自定義:

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;
      });
    },
  ),
)

這個基本可以完全自定義樣式了。

如何在 Flutter 1.20 版本使用以前的標簽樣式呢?

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;
      });
    },
  ),
)

RectangularSliderValueIndicatorShape 表示矩形樣式:

RangeSlider

RangeSliderSlider 幾乎一樣,RangeSlider 是范圍滑塊,想要選擇一段值,可以使用 RangeSlider。

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;
    });
  },
),

滑塊狀態

ios風格的 Slider

ios風格的 Slider,使用 CupertinoSlider:

double _sliderValue = 0;
CupertinoSlider(
  value: _sliderValue,
  onChanged: (v) {
    setState(() {
      _sliderValue = v;
    });
  },
)

當然也可以根據平台顯示不同風格的Slider,ios平台顯示CupertinoSlider效果,其他平台顯示Material風格,用法如下:

Slider.adaptive(
  value: _sliderValue,
  onChanged: (v) {
    setState(() {
      _sliderValue = v;
    });
  },
)

Material風格日期選擇器

Flutter 1.20 版本更新了 日期 類組件的樣式,加入了新的緊湊設計以及對日期范圍的支持。

showDatePicker

結構圖

  1. 標題
  2. 選中的日期
  3. 切換到輸入模式
  4. 選擇菜單
  5. 月份分頁
  6. 當前時間
  7. 選中日期

輸入模式 結構圖:

  1. 標題
  2. 選中日期
  3. 切換 日歷模式
  4. 輸入框

基礎用法

點擊按鈕彈出日期組件:

 RaisedButton(
          child: Text('彈出日期組件'),
          onPressed: () async {
            await showDatePicker(
              context: context,
              initialDate: DateTime.now(),
              firstDate: DateTime(2010),
              lastDate: DateTime(2025),
            );

  • initialDate:初始化時間,通常情況下設置為當前時間。
  • firstDate:表示開始時間,不能選擇此時間前面的時間。
  • lastDate:表示結束時間,不能選擇此時間之后的時間。

設置日期選擇器對話框的模式:

var result = await showDatePicker(
  context: context,
  initialDate: DateTime.now(),
  firstDate: DateTime(2010),
  lastDate: DateTime(2025),
  initialEntryMode: DatePickerEntryMode.input,
);

直接顯示 輸入模式,默認是日歷模式

設置日歷日期選擇器的初始顯示,包含 dayyear

var result = await showDatePicker(
  context: context,
  initialDate: DateTime.now(),
  firstDate: DateTime(2010),
  lastDate: DateTime(2025),
  initialDatePickerMode: DatePickerMode.year,
);

和以前的版本對比:

設置頂部標題、取消按鈕、確定按鈕 文案:

var result = await showDatePicker(
  context: context,
  initialDate: DateTime.now(),
  firstDate: DateTime(2010),
  lastDate: DateTime(2025),
  helpText: '選則日期',
  cancelText: '取消',
  confirmText: '確定',
);

修改 輸入模式 下文案:

var result = await showDatePicker(
  context: context,
  initialDate: DateTime.now(),
  firstDate: DateTime(2010),
  lastDate: DateTime(2025),
  errorFormatText: '錯誤的日期格式',
  errorInvalidText: '日期格式非法',
  fieldHintText: '月/日/年',
  fieldLabelText: '填寫日期',
);

設置可選日期范圍

var result = await showDatePicker(
  context: context,
  initialDate: DateTime.now(),
  firstDate: DateTime(2010),
  lastDate: DateTime(2025),
  selectableDayPredicate: (date) {
    return date.difference(DateTime.now()).inMilliseconds < 0;
  },
);

今天以后的日期全部為灰色,不可選狀態。

設置深色主題

設置深色主題使 builder ,其用於包裝對話框窗口小部件以添加繼承的窗口小部件,例如Theme,設置深色主題如下:

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,
    );
  }
);

獲取選中的日期

showDatePicker 方法是 Future 方法,點擊日期選擇控件的確定按鈕后,返回選擇的日期。

var result = await showDatePicker(
              context: context,
              initialDate: DateTime.now(),
              firstDate: DateTime(2010),
              lastDate: DateTime(2025),
            );

print('$result');

result 為選擇的日期。

CalendarDatePicker

日期組件直接顯示在頁面上,而不是彈出顯示:

CalendarDatePicker(
  initialDate: DateTime.now(),
  firstDate: DateTime(2010),
  lastDate: DateTime(2025),
  onDateChanged: (d) {
    print('$d');
  },
)

其參數和 showDatePicker 一樣。

范圍日期

選擇范圍日期使用 showDateRangePicker

RaisedButton(
  child: Text('范圍日期'),
  onPressed: () async {
    var date = showDateRangePicker(context: context, firstDate: DateTime(2010), lastDate: DateTime(2025));
  },
),

其參數和 showDatePicker 一樣。

范圍日期結構圖:

  1. 標題
  2. 選定的日期范圍
  3. 切換到輸入模式
  4. 月和年標簽
  5. 當前時間
  6. 開始時間
  7. 選中時間范圍
  8. 結束時間

國際化

國際化都是一個套路,下面以 showDatePicker 為例:

pubspec.yaml 中引入:

dependencies:
  flutter_localizations:
    sdk: flutter

在頂級組件 MaterialApp 添加支持:

MaterialApp(
  title: 'Flutter Demo',

  localizationsDelegates: [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [
    const Locale('zh'),
    const Locale('en'),
  ],
  ...

彈出日期組件:

var result = await showDatePicker(
  context: context,
  initialDate: DateTime.now(),
  firstDate: DateTime(2010),
  lastDate: DateTime(2025),
);

此時將系統語音調整為中文

此組件只支持中文,不管系統設置語言:

var result = await showDatePicker(
  context: context,
  initialDate: DateTime.now(),
  firstDate: DateTime(2010),
  lastDate: DateTime(2025),
  locale: Locale('zh')
);

Material風格時間選擇器

Flutter 1.20 版本更新了 時間 類組件的樣式。

基礎使用

彈出時間組件:

RaisedButton(
  child: Text('彈出時間選擇器'),
  onPressed: () async {
    var result =
        showTimePicker(context: context, initialTime: TimeOfDay.now());
  },
)

1.20 版以前的效果:

設置 交互模式,交互模式包含 時鍾模式(默認)和 輸入模式

var result = showTimePicker(
    context: context,
    initialTime: TimeOfDay.now(),
    initialEntryMode: TimePickerEntryMode.input);

時鍾模式(TimePickerEntryMode.dial):

輸入模式(TimePickerEntryMode.input):

設置頂部標題、取消按鈕、確定按鈕 文案:

var result = showTimePicker(
    context: context,
    initialTime: TimeOfDay.now(),
    initialEntryMode: TimePickerEntryMode.input,
    helpText: '選擇時間',
    cancelText: '取消',
    confirmText: '確定');

24小時 制:

var result = showTimePicker(
  context: context,
  initialTime: TimeOfDay.now(),
  builder: (BuildContext context, Widget child) {
    return MediaQuery(
      data: MediaQuery.of(context)
          .copyWith(alwaysUse24HourFormat: true),
      child: child,
    );
  },
);

黑暗模式

var result = showTimePicker(
  context: context,
  initialTime: TimeOfDay.now(),
  builder: (BuildContext context, Widget child) {
    return Theme(
      data: ThemeData.dark(),
      child: child,
    );
  },
);

國際化

pubspec.yaml 中引入:

dependencies:
  flutter_localizations:
    sdk: flutter

在頂級組件 MaterialApp 添加支持:

MaterialApp(
  title: 'Flutter Demo',

  localizationsDelegates: [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [
    const Locale('zh'),
    const Locale('en'),
  ],
  ...

彈出時間組件:

RaisedButton(
  child: Text('彈出時間選擇器'),
  onPressed: () async {
    var result =
        showTimePicker(context: context, initialTime: TimeOfDay.now());
  },
)

切換系統語言為中文:

不跟隨系統語言,直接指定,比如當前系統語言為中文,指定為英文:

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,
    );
  },
);

iOS風格日期選擇器

基礎使用

CupertinoDatePicker 是 iOS風格的日期選擇器。

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');
            },
          ),
        ),
      ),
    );
  }

}

設置最大/小時間:

CupertinoDatePicker(
  initialDateTime: DateTime.now(),
  minimumDate: DateTime.now().add(Duration(days: -1)),
  maximumDate: DateTime.now().add(Duration(days: 1)),
  onDateTimeChanged: (date) {
    print('$date');
  },
)

最大時間為明天,最小時間為昨天:

設置模式為時間

CupertinoDatePicker(
  mode: CupertinoDatePickerMode.time,
  initialDateTime: DateTime.now(),
  onDateTimeChanged: (date) {
    print('$date');
  },
)

設置模式為日期

CupertinoDatePicker(
  mode: CupertinoDatePickerMode.date,
  initialDateTime: DateTime.now(),
  onDateTimeChanged: (date) {
    print('$date');
  },
)

設置模式為日期和時間

CupertinoDatePicker(
  mode: CupertinoDatePickerMode.dateAndTime,
  initialDateTime: DateTime.now(),
  onDateTimeChanged: (date) {
    print('$date');
  },
)

  • time:只顯示時間,效果:4 | 14 | PM
  • date:只顯示日期,效果:July | 13 | 2012
  • dateAndTime:時間和日期都顯示,效果: Fri Jul 13 | 4 | 14 | PM

使用24小時制:

CupertinoDatePicker(
  use24hFormat: true,
  initialDateTime: DateTime.now(),
  onDateTimeChanged: (date) {
    print('$date');
  },
)

國際化

pubspec.yaml 中引入:

dependencies:
  flutter_localizations:
    sdk: flutter

在頂級組件 MaterialApp 添加支持:

MaterialApp(
  title: 'Flutter Demo',

  localizationsDelegates: [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [
    const Locale('zh'),
    const Locale('en'),
  ],
  ...

組件使用:

CupertinoDatePicker(
  initialDateTime: DateTime.now(),
  onDateTimeChanged: (date) {
    print('$date');
  },
)

組件語言跟隨系統語言,當前系統語言為英文,效果:

不跟隨系統語言,直接指定,比如當前系統語言為英文,指定為中文:

Localizations(
  locale: Locale('zh'),
  delegates: [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  child: CupertinoDatePicker(
    initialDateTime: DateTime.now(),
    onDateTimeChanged: (date) {
      print('$date');
    },
  ),
)

iOS風格時間選擇器

基礎使用

CupertinoTimerPicker 是 iOS風格的時間選擇器。

CupertinoTimerPicker(onTimerDurationChanged: (time) {
  print('$time');
})

設置顯示模式:

  • CupertinoTimerPickerMode.hm:顯示 小時 | 分鍾,英文效果16 hours | 14 min
  • CupertinoTimerPickerMode.ms: 顯示 分鍾 | 秒,英文效果14 min | 43 sec
  • CupertinoTimerPickerMode.hms:顯示 小時 | 分鍾 | 秒,英文效果16 hours | 14 min | 43 sec
CupertinoTimerPicker(
    mode: CupertinoTimerPickerMode.hm,
    onTimerDurationChanged: (time) {
      print('$time');
    })

默認情況下,CupertinoTimerPicker顯示0:0:0,設置顯示當前時間:

CupertinoTimerPicker(
    initialTimerDuration: Duration(
        hours: DateTime.now().hour,
        minutes: DateTime.now().minute,
        seconds: DateTime.now().second),
    onTimerDurationChanged: (time) {
      print('$time');
    })

設置 分/秒 的間隔:

CupertinoTimerPicker(
    minuteInterval: 5,
    secondInterval: 5,
    onTimerDurationChanged: (time) {
      print('$time');
    })

國際化

pubspec.yaml 中引入:

dependencies:
  flutter_localizations:
    sdk: flutter

在頂級組件 MaterialApp 添加支持:

MaterialApp(
  title: 'Flutter Demo',

  localizationsDelegates: [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [
    const Locale('zh'),
    const Locale('en'),
  ],
  ...

組件使用:

CupertinoTimerPicker(onTimerDurationChanged: (time) {
  print('$time');
})

組件語言跟隨系統語言,當前系統語言為英文,效果:

不跟隨系統語言,直接指定,比如當前系統語言為英文,指定為中文:

Localizations(
  locale: Locale('zh'),
  delegates: [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  child: CupertinoTimerPicker(onTimerDurationChanged: (time) {
    print('$time');
  }),
)

InteractiveViewer

InteractiveViewer 是 Flutter 1.20 新增的組件,用戶可以通過拖動以平移、縮放和拖放子組件。

InteractiveViewer(
  child: Image.asset('assets/images/go_board_09x09.png'),
)

alignPanAxis 參數表示是否只在水平和垂直方向上拖拽,默認為false,設置為true,無法沿着對角線(斜着)方向移動。

InteractiveViewer(
  alignPanAxis: true,
  child: Image.asset('assets/images/go_board_09x09.png'),
)

maxScaleminScalescaleEnabled 是縮放相關參數,分別表示最大縮放倍數、最小縮放倍數、是否可以縮放:

InteractiveViewer(
  maxScale: 2,
  minScale: 1,
  scaleEnabled: true,
  child: Image.asset('assets/images/go_board_09x09.png'),
)

constrained 參數表示組件樹中的約束是否應用於子組件,默認為true,如果設為true,表示子組件是無限制約束,這對子組件的尺寸比 InteractiveViewer 大時非常有用,比如子組件為滾動系列組件。

如下的案例,子組件為 Table,Table 尺寸大於屏幕,必須將constrained設置為 false 以便將其繪制為完整尺寸。超出的屏幕尺寸可以平移到視圖中。

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,
                        ),
                    ],
                  ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

回調事件:

  • onInteractionStart:當用戶開始平移或縮放手勢時調用。
  • onInteractionUpdate:當用戶更新組件上的平移或縮放手勢時調用。
  • onInteractionEnd:當用戶在組件上結束平移或縮放手勢時調用。
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');
  },
)

通過 Matrix4 矩陣對其進行變換,比如左移、放大等,添加變換控制器:

final TransformationController _transformationController =
      TransformationController();

InteractiveViewer(
  child: Image.asset('assets/images/go_board_09x09.png'),
  transformationController: _transformationController,
)

放大變換:

var matrix = _transformationController.value.clone();
matrix.scale(1.5, 1.0, 1.0);
_transformationController.value = matrix;

完整代碼:

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;
                },
              ),
            ],
          ),
        ],
      ),
    );
  }
}

交流

老孟Flutter博客地址(330個控件用法):http://laomengit.com

歡迎加入Flutter交流群(微信:laomengit)、關注公眾號【老孟Flutter】:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM