Flutter 开关和切换高级指南

我们都熟悉家用开关来打开和关闭我们的照明系统和其他电器。 我们也熟悉切换按钮; 如果您家里有电炊具或电磁炉,您可以在其电压和烹饪功能之间切换。

同样,我们的移动应用程序也有开关和拨动开关来打开/关闭 Wi-Fi、蓝牙等。

今天,我们正在深入了解 Flutter 开关和切换的参数和属性。

目录

  • 什么是开关小部件?

  • 什么是切换小部件?

  • 开关和拨动之间的关键区别

  • 切换小部件示例

    • 安卓

    • iOS

    • 带图片的安卓开关

  • 切换小部件示例

    • 单个和必需的拨动开关

    • 单个且不需要的拨动开关

    • 需要多选

    • 不需要的多项选择

  • 创建自定义动画开关按钮

  • 用于开关和切换的流行 Flutter 包

什么是开关小部件?

开关按钮是一个 Flutter 小部件,只有两种状态,真/假或开/关。 通常,开关是一个带有拇指滑块的按钮,用户可以将其从左拖动到右,反之亦然以在状态之间切换。 它不会自行维护其状态。 您必须致电 onChanged属性以保持按钮打开或关闭。

什么是切换小部件?

同样,切换小部件只有两种状态:真/假或开/关。 但是切换小部件会创建多个按行排列的按钮,允许用户在它们之间切换。

开关和拨动之间的关键区别

这是移动应用程序中的用例问题。 在大多数情况下,这些小部件用于设置页面。 如果您向下拖动移动应用程序的通知面板,您将看到一个切换按钮网格。 但是当您进入设置页面时,这些按钮将更改为开关。

你一定明白其中的区别。 在您的移动应用程序中,如果您有一个只需要两种状态的控件列表,您应该使用开关。 如果一行或网格中有多个控件,则应使用切换。

切换小部件示例

Flutter 提供了三种类型的 switch 小部件:


超过 20 万开发人员使用 LogRocket 来创造更好的数字体验 了解更多 →


  • 开关(安卓)

  • CupertinoSwitch (iOS)

  • Switch.adaptive(根据平台适配)

让我们看一下用于自定义小部件的最常用属性:

Switch(安卓)

Switch(
  // thumb color (round icon)
  activeColor: Colors.amber,
  activeTrackColor: Colors.cyan,
  inactiveThumbColor: Colors.blueGrey.shade600,
  inactiveTrackColor: Colors.grey.shade400,
  splashRadius: 50.0,
  // boolean variable value
  value: forAndroid,
  // changes the state of the switch
  onChanged: (value) => setState(() => forAndroid = value),
),

CupertinoSwitch(iOS)

CupertinoSwitch(
  // overrides the default green color of the track
  activeColor: Colors.pink.shade200,
  // color of the round icon, which moves from right to left
  thumbColor: Colors.green.shade900,
  // when the switch is off
  trackColor: Colors.black12,
  // boolean variable value
  value: forIos,
  // changes the state of the switch
  onChanged: (value) => setState(() => forIos = value),
),

自适应开关小部件没有任何独特或不同的属性。 但是安卓 Switch如果您想要图像或图标而不是通常的拇指颜色,可以进一步自定义小部件。 您需要使用资产图像定义拇指图像属性。 请看下面的代码。

安卓 Switch带图像

Switch(
  trackColor: MaterialStateProperty.all(Colors.black38),
  activeColor: Colors.green.withOpacity(0.4),
  inactiveThumbColor: Colors.red.withOpacity(0.4),
// when the switch is on, this image will be displayed
  activeThumbImage: const AssetImage('assets/happy_emoji.png'),
// when the switch is off, this image will be displayed
  inactiveThumbImage: const AssetImage('assets/sad_emoji.png'),
  value: forImage,
  onChanged: (value) => setState(() => forImage = value),
),

这就是代码在运行中的样子:

目前,我们不保存开关小部件的状态; 我们只是在改变它。 接下来是创建一个小应用程序,我们将在其中将主题从浅色更改为深色,反之亦然,当您关闭应用程序时,它的状态将被保存。

它是一个简单的单页应用程序,在 appBar,这将改变主题。

我已经使用 Flutter Hive 来保存应用程序的状态。 您可以使用 SharedPreferences,但我选择了 Hive,因为它是一个快速、轻量级的 NoSQL 数据库,适用于 Flutter 和 Dart 应用程序。 如果您需要一个没有大量关系的简单键值数据库,Hive 会很有帮助。 它使用起来毫不费力,并且是一个离线数据库(在本地存储数据)。

我们先来看代码……

我们正在使用 ValueListenableBuilder更新用户界面。 每次它侦听的值发生变化时,它都会构建特定的小部件。 它的值与听众保持同步; 即,每当值发生变化时, ValueListenable监听它并在不使用的情况下更新 UI setState()或任何其他状态管理技术:

const themeBox = 'hiveThemeBox';
void main() async {
 await Hive.initFlutter();
 await Hive.openBox(themeBox);
 runApp(const MyApp());
}
​
class MyApp extends StatelessWidget {
 const MyApp({Key? key}) : super(key: key);
​
 @override
 Widget build(BuildContext context) {
   //to update the UI without using setState()
   return ValueListenableBuilder(
     valueListenable: Hive.box(themeBox).listenable(),
     builder: (context, box, widget) {
       //saving the value inside the hive box,
       var darkMode = Hive.box(themeBox).get('darkMode', defaultValue: false);
       return MaterialApp(
           debugShowCheckedModeBanner: false,
           //switching between light and dark theme,
           themeMode: darkMode ? ThemeMode.dark : ThemeMode.light,
           title: 'Flutter Demo',
           darkTheme: ThemeData.dark(),
           home: HomePage(
             value: darkMode,
           ));
     },
   );
 }
}
​
class HomePage extends StatelessWidget {
 final bool value;
 const HomePage({Key? key, required this.value}) : super(key: key);
​
 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: Text(value ? 'Hive Dark Mode' : 'Hive Light Mode'),
       actions: [
         Switch(
           value: value,
           onChanged: (val) {
             Hive.box(themeBox).put('darkMode', !value);
           },
         ),
       ],
     ),
     body: Padding(
       padding: const EdgeInsets.all(8.0),
       child: Column(
         crossAxisAlignment: CrossAxisAlignment.stretch,
         children: [ ],
       ),
     ),
   );
 }
}

切换小部件示例

在这里,我们将看看在我们的应用程序中实现切换小部件的四种不同方法。

  • 单一且必需:用户必须从两个选项中选择至少一个

  • 单一且不需要:用户无需选择任何选项

  • Multiple and required:用户必须至少选择一个给定选项,但也可以选择多个选项

  • 多选且非必选:用户可以根据需要选择或取消选择,也可以选择多个选项

首先,让我们看一下切换小部件的标准属性以对其进行自定义,然后我们将查看每个小部件的代码及其插图:

ToggleButtons(
  // list of booleans
  isSelected: isSelected,
  // text color of selected toggle
  selectedColor: Colors.white,
  // text color of not selected toggle
  color: Colors.blue,
  // fill color of selected toggle
  fillColor: Colors.lightBlue.shade900,
  // when pressed, splash color is seen
  splashColor: Colors.red,
  // long press to identify highlight color
  highlightColor: Colors.orange,
  // if consistency is needed for all text style
  textStyle: const TextStyle(fontWeight: FontWeight.bold),
  // border properties for each toggle
  renderBorder: true,
  borderColor: Colors.black,
  borderWidth: 1.5,
  borderRadius: BorderRadius.circular(10),
  selectedBorderColor: Colors.pink,
// add widgets for which the users need to toggle
   children: [ ],
// to select or deselect when pressed
  onPressed: (int newIndex) { }
);

单个和必需的拨动开关

首先,我们必须初始化一个布尔变量列表:

// one must always be true, means selected.
List isSelected = [true, false, false];

由于我们强制系统始终选择至少一个选项,因此我们将一个值初始化为 true.

我们已经讨论了自定义切换小部件的其他属性。 现在我们将子小部件添加到它的 children财产。

注意,您必须添加与布尔值列表相同数量的子小部件。 否则会抛出错误。

// add widgets for which the users need to toggle
children: const [
  Padding(
    padding: EdgeInsets.symmetric(horizontal: 12),
    child: Text('MALE', style: TextStyle(fontSize: 18)),
  ),
  Padding(
    padding: EdgeInsets.symmetric(horizontal: 12),
    child: Text('FEMALE', style: TextStyle(fontSize: 18)),
  ),
  Padding(
    padding: EdgeInsets.symmetric(horizontal: 12),
    child: Text('OTHER', style: TextStyle(fontSize: 18)),
  ),
],

接下来,我们必须将逻辑添加到 onPressed()内部切换小部件的属性 setState()功能。

  • 使用 for循环,我们将遍历布尔值列表

  • 使用

    if

    声明,我们将检查

    index

    值并始终将其设置为

    true

    . 其他按钮将设置为

    false
    onPressed: (int newIndex) {
      setState(() {
        // looping through the list of booleans values
        for (int index = 0; index < isSelected.length; index++) {
          // checking for the index value
          if (index == newIndex) {
            // one button is always set to true
            isSelected[index] = true;
          } else {
            // other two will be set to false and not selected
            isSelected[index] = false;
          }
        }
      });
    },

这就是我们最终产品的外观。


来自 LogRocket 的更多精彩文章:

  • 不要错过 The Replay 来自 LogRocket 的精选时事通讯

  • 了解 LogRocket 的 Galileo 如何消除噪音以主动解决应用程序中的问题

  • 使用 React 的 useEffect 优化应用程序的性能

  • 之间切换 在多个 Node 版本

  • 了解如何 使用 AnimXYZ 为您的 React 应用程序制作动画

  • 探索 Tauri ,一个用于构建二进制文件的新框架

  • 比较 NestJS 与 Express.js


单个且不需要的拨动开关

我们只需要进行两项更改即可使其正常工作。 用户只能从三个选项中选择一个,但不是必须选择它。

布尔变量列表中的值都初始化为 false:

// all values are false
List isSelected = [false, false, false];

在 - 的里面 if statement in the onPressed() function, we only toggle between buttons to set it to true:

onPressed: (int newIndex) {
  setState(() {
    // looping through the list of booleans values
    for (int index = 0; index < isSelected.length; index++) {
      if (index == newIndex) {
        // toggling between the button to set it to true
        isSelected[index] = !isSelected[index];
      } else {
        // other two buttons will not be selected and are set to false
        isSelected[index] = false;
      }
    }
  });
},

需要多选

如前所述,用户可以选择多个选项,但系统将始终保持至少一个选项处于选中状态。

是的,您猜对了,布尔值列表中的一个值将是 true:

List isSelected = [true, false, false];

里面的事情变得有点有趣 onPressed功能。

首先,我们添加一个变量来循环布尔值列表,并确保值为真; 因此,始终至少选择一个按钮:

final isOneSelected = isSelected.where((element) => element).length == 1;

如果只选择了一个按钮,则用户无法将其切换到 false直到选择另一个选项:

if (isOneSelected && isSelected[newIndex]) return;

接下来,内 setState()函数,我们再次遍历我们的列表,检查新的索引值,并在新旧索引之间切换:

setState(() {
  // looping through the list of booleans
  for (int index = 0; index < isSelected.length; index++) {
    // checking for the index value
    if (index == newIndex) {
      // toggle between the old index and new index value
      isSelected[index] = !isSelected[index];
    }
  }
});

不需要的多项选择

这很简单。 我制作了一行文本编辑选项,您通常在任何文本编辑器中都会看到这些选项来格式化书面文本。 有四个选项,所以我们的列表中有四个值,并且都设置为 false:

List isSelected = [false, false, false, false];

在我们的 onPressed()功能,我们只需在 true和 false价值观:

onPressed: (int index) {
  setState(() {
    // simply toggling buttons between true and false state
    isSelected[index] = !isSelected[index];
  });

我们已经完成了对开关和切换小部件以及如何以通常方式使用它的解释。 现在,让我们通过创建一个自定义动画开关按钮来进行一些复杂的编程,在实现下一个代码集后,该按钮将如下图所示。

创建自定义动画开关按钮

我们将此按钮分为两部分。 第一个是我命名为的无状态小部件 CustomAnimatedSwitch. 在这个无状态小部件中,我们将创建自定义开关。 稍后,我们会将它添加到有状态小部件中以使用 setState()功能来打开和关闭。

简壁纸App,全是4K壁纸超给力,极品内容已全部解锁!

第一步:添加依赖

simple_animations: ^5.0.0+2

第 2 步:定义变量

首先,我们将使用枚举和布尔值定义命名常量变量:

enum _CustomSwitchParams { paddingLeft, color, text, rotation }

final bool toggle;

其次,由于我们使用的是带有级联符号(双点运算符)的简单动画包,因此我们在 MovieTween我们为访问其属性而创建的对象。 基本上,我们正在向我们之前添加的枚举添加动画:

var customTween = MovieTween()
  ..scene(duration: const Duration(seconds: 1))
      .tween(_CustomSwitchParams.paddingLeft, 0.0.tweenTo(60.0))
  ..scene(duration: const Duration(seconds: 1))
      .tween(_CustomSwitchParams.color, Colors.red.tweenTo(Colors.green))
  ..scene(duration: const Duration(milliseconds: 500))
      .tween(_CustomSwitchParams.text, ConstantTween('OFF'))
      .thenTween(_CustomSwitchParams.text, ConstantTween('ON'),
          duration: const Duration(milliseconds: 500))
  ..scene(duration: const Duration(seconds: 1))
      .tween(_CustomSwitchParams.rotation, (-2 * pi).tweenTo(0.0));

第 3 步: CustomAnimationBuilder

接下来,我们将构建我们的 CustomAnimationBuilder小部件并定义其所需的属性来组装开关动画:

CustomAnimationBuilder(
   // control of the animation
   control: toggle ? Control.play : Control.playReverse,
   // the relative position where animation will start
   startPosition: toggle ? 1.0 : 0.0,
   // define unique key
   key: const Key('0'),
   duration: customTween.duration * 1.2,
   // movie tween object
   tween: customTween,
   curve: Curves.easeInOut,
   builder: (context, value, child) {
     return Container(
       decoration:
           _outerDecoration(color: value.get(_CustomSwitchParams.color)),
       width: 100.0,
       height: 40.0,
       padding: const EdgeInsets.all(4.0),
       child: Stack(
         children: [
           Positioned(
             child: Padding(
               padding: EdgeInsets.only(
                 left: value.get(_CustomSwitchParams.paddingLeft),
               ),
               child: Transform.rotate(
                 angle: value.get(_CustomSwitchParams.rotation),
                 child: Container(
                   decoration: _innerDecoration(
                     color: value.get(_CustomSwitchParams.color),
                   ),
                   width: 30.0,
                   child: Center(
                     child: Text(
                       value.get(_CustomSwitchParams.text),
                       style: const TextStyle(
                           height: 1.5,
                           fontSize: 12,
                           fontWeight: FontWeight.bold,
                           color: Colors.white),
                     ),
                   ),
                 ),
               ),
             ),
           ),
         ],
       ),
     );
   },
 );
}

第4步: CustomSwitchButton(有状态小部件)

来到创建自定义开关按钮的第二部分,我们必须添加另一个包含有状态小部件的 Dart 文件,我们将其称为 CustomSwitchButton.

首先,定义一个布尔变量并将其值设置为 false:

bool _switched = false;

其次,创建一个方法 setState()切换功能 true和 false:

void toggleSwitch() {
  setState(() {
    _switched = !_switched;
  });
}

最后,我们添加我们的 CustomAnimatedSwitch到这个包裹着的 Dart 文件 GestureDetector,添加 toggleSwitch的方法 onTap()功能。

而已! 我们拥有功能齐全的定制动画开关按钮。 请查看下面的代码和图像:

@override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: const Text('Custom Animated Switch'),
     ),
     body: GestureDetector(
       onTap: toggleSwitch,
       child: Center(
         child: Column(
           crossAxisAlignment: CrossAxisAlignment.center,
           mainAxisAlignment: MainAxisAlignment.center,
           children: [
             const Padding(
               padding: EdgeInsets.all(10.0),
               child: Text('Tap to Check Custom Animated Switch'),
             ),
             const SizedBox(
               height: 20.0,
             ),
             CustomAnimatedSwitch(toggle: _switched),
           ],
         ),
       ),
     ),
   );
 }
}

用于开关和切换的流行 Flutter 包

如果您不想创建自己的开关按钮,您可以随时使用以下任何包,它们的功能与我们制作自己的自定义动画开关完全相同。

  1. AnimatedToggleSwitch:用于多种选择的简单和动画切换开关。 如果您不想使用下拉菜单之类的东西,这是一个不错的选择  

  2. FlutterSwitch:为 Flutter 创建的易于实现的自定义开关。 给它一个自定义的高度和宽度,开关和切换的边框,边框半径,颜色,切换大小,显示 开 和 关 文本的选择,并能够在切换内添加一个图标

  3. ToggleSwitch:一个简单的切换开关小部件。 它可以完全自定义所需的图标、宽度、颜色、文本、圆角半径、动画等。它还保持选择状态

我留下了 整个项目的链接 ,你可以在我的 GitHub 页面上找到它。 如果有任何问题或者您可以改进代码,请告诉我,我会让您访问我的项目。

非常感谢并保持安全!

LogRocket :全面了解您的网络和移动应用程序

LogRocket 是一个前端应用程序监控解决方案,可让您重现问题,就好像它们发生在您自己的浏览器中一样。 无需猜测错误发生的原因,或要求用户提供屏幕截图和日志转储,LogRocket 可让您重播会话以快速了解问题所在。 无论框架如何,它都可以完美地与任何应用程序配合使用,并且具有用于记录来自 Redux、Vuex 和 @ngrx/store 的附加上下文的插件。

除了记录 Redux 操作和状态之外,LogRocket 还记录控制台日志、JavaScript 错误、堆栈跟踪、带有标头 + 正文的网络请求/响应、浏览器元数据和自定义日志。 它还检测 DOM 以记录页面上的 HTML 和 CSS,即使是最复杂的单页和移动应用程序也能重新创建像素完美的视频。

你可能感兴趣的:(flutter)