• 【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,
               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】:

  • 相关阅读:
    Python13_安装、解释器
    Python12_关于文件概念的讨论与序列化
    Python11_文件的读写
    which | whereis |locate |find
    tail命令 | head命令
    cat 命令|more命令|less命令
    数据库模型设计,第一范式、第二范式、第三范式简单例子理解
    json
    正则表达式
    SFTP相关命令
  • 原文地址:https://www.cnblogs.com/mengqd/p/13649555.html
Copyright © 2020-2023  润新知