1

flutter 非常用组件整理 第一篇

视频

https://youtu.be/SbLVevHYA94

https://www.bilibili.com/video/BV1i4421D7Ck/

前言

原文 https://ducafecat.com/blog/lesser-known-flutter-widgets-01

Flutter 是一个功能强大的跨平台移动开发框架,其组件系统支持丰富的可定制性和灵活性。本文深入探讨了 Flutter 中一些鲜为人知但却极具潜力的组件,包括 InheritedWidget、FractionallySizedBox、FittedBox 等。通过学习这些独特的组件,Flutter 开发者可以构建更出色、更有创意的应用程序,优化用户体验,提升应用性能。希望本文对您的 Flutter 开发之路有所启发和帮助。

Flutter, 冷门组件, 高阶组件, 组件设计模式, 用户体验优化

参考

正文

InheritedWidget

InheritedWidget 的主要功能是:

  1. 状态共享: 它允许上层组件向下层组件传递数据,而无需通过繁琐的参数传递。这对于构建全局状态管理非常有用。
  2. 性能优化: InheritedWidget 会自动跟踪它的子组件,只有当 InheritedWidget 的数据发生变化时,子组件才会重建,从而提高性能。
  3. 依赖管理: 子组件可以依赖于 InheritedWidget,当 InheritedWidget 的数据变化时,依赖它的子组件会自动更新。

使用 InheritedWidget 的一般步骤如下:

  1. 创建一个继承自 InheritedWidget 的自定义组件,在其中封装需要共享的数据和逻辑。
  2. 在应用的根部包裹一个 InheritedWidget,让其子树都能访问到共享数据。
  3. 子组件通过 of() 静态方法获取 InheritedWidget 实例,并读取或修改共享的数据。

下面是一个简单的 InheritedWidget 使用示例:

// 1. 定义一个继承自 InheritedWidget 的共享数据组件
class CountProvider extends InheritedWidget {
  final int count;
  final VoidCallback onIncrement;

  CountProvider({
    required this.count,
    required this.onIncrement,
    required Widget child,
  }) : super(child: child);

  static CountProvider of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CountProvider>()!;
  }

  @override
  bool updateShouldNotify(CountProvider old) => count != old.count;
}

// 2. 在应用的根部包裹 CountProvider
MaterialApp(
  home: CountProvider(
    count: 0,
    onIncrement: () => setState(() => count++),
    child: HomePage(),
  ),
);

// 3. 子组件通过 of() 方法获取共享数据
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Count: ${CountProvider.of(context).count}'),
    );
  }
}

通过 InheritedWidget,我们可以方便地在 Flutter 应用中实现全局状态管理、主题切换、国际化等功能,大大简化开发复杂 UI 的难度。它是一个非常强大的 Flutter 组件,值得我们深入学习和掌握。

FractionallySizedBox

FractionallySizedBox 是一个很有用的 Flutter 布局组件,它允许我们根据父容器的大小来设置子组件的大小。与 Expanded 或 Flexible 不同,FractionallySizedBox 使用一个分数来确定子组件的大小,而不是占用剩余空间。

FractionallySizedBox 的主要属性包括:

  1. widthFactor: 设置子组件宽度占父容器宽度的比例,取值范围为 0.0 到 1.0。
  2. heightFactor: 设置子组件高度占父容器高度的比例,取值范围为 0.0 到 1.0。
  3. alignment: 设置子组件在父容器中的对齐方式。

使用 FractionallySizedBox 的一些典型场景包括:

  1. 自适应布局: 在不同屏幕尺寸下保持 UI 元素的相对大小。
  2. 构建比例布局: 如相册页面中的图片缩略图。
  3. 实现宽高比固定的组件: 如视频播放器、头像等。

下面是一个简单的 FractionallySizedBox 使用示例:

Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: Container(
        width: 300,
        height: 500,
        color: Colors.grey[200],
        child: FractionallySizedBox(
          widthFactor: 0.8,
          heightFactor: 0.6,
          child: Container(
            color: Colors.blue,
          ),
        ),
      ),
    ),
  );
}

FractionallySizedBox

在这个例子中,我们创建了一个灰色的容器,然后在其中放置了一个 FractionallySizedBox。FractionallySizedBox 的 widthFactor 和 heightFactor 分别设置为 0.8 和 0.6,意味着它的子组件 (蓝色容器) 的宽度为父容器宽度的 80%,高度为父容器高度的 60%。

FittedBox

FittedBox 的主要作用是:

  1. 缩放子组件: FittedBox 可以根据父容器的大小,对子组件进行缩放,使其完全填充或适应父容器。
  2. 对齐子组件: 通过 fit 属性,FittedBox 可以控制子组件在父容器中的对齐方式,如 BoxFit.containBoxFit.cover 等。
  3. 避免溢出: 当子组件过大无法完全容纳在父容器中时,FittedBox 会自动对其进行缩放,避免溢出。

FittedBox 的主要属性包括:

  • fit: 设置子组件的缩放方式,取值为 BoxFit 枚举。
  • alignment: 设置子组件在父容器中的对齐方式。

下面是一个简单的 FittedBox 使用示例:

Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: Container(
        width: 300,
        height: 300,
        color: Colors.grey[200],
        child: FittedBox(
          fit: BoxFit.contain,
          child: Text("FittedBox 是一个很有用的布局组件,它可以帮助我们控制子组件的大小和位置,使其能够适应父容器的大小。"),
        ),
      ),
    ),
  );
}

FittedBox

在这个例子中,我们创建了一个灰色的容器,然后在其中放置了一个 FittedBox。FittedBox 的 fit 属性被设置为 BoxFit.contain,这意味着子组件 (一个网络图片) 将被缩放以适应父容器的大小,同时保持原有的宽高比。

ConstrainedBox

ConstrainedBox 是一个非常实用的布局组件,它可以对其子组件施加额外的约束条件。这些约束条件可以是最小尺寸、最大尺寸或特定的宽高比等。

ConstrainedBox 的主要作用包括:

  1. 设置最小尺寸: 通过 minWidthminHeight 属性,可以确保子组件至少有指定的尺寸。
  2. 设置最大尺寸: 通过 maxWidthmaxHeight 属性,可以限制子组件的最大尺寸。
  3. 设置宽高比: 通过 aspectRatio 属性,可以为子组件设置特定的宽高比。
  4. 组合使用: minWidthminHeightmaxWidthmaxHeightaspectRatio 属性可以组合使用,为子组件施加多重约束条件。

下面是一个简单的 ConstrainedBox 使用示例:

Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: ConstrainedBox(
        constraints: BoxConstraints(
          minWidth: 100.0,
          minHeight: 100.0,
          maxWidth: 300.0,
          maxHeight: 300.0,
        ),
        child: Container(
          color: Colors.blue,
        ),
      ),
    ),
  );
}

ConstrainedBox

在这个例子中,我们创建了一个 ConstrainedBox,并为其设置了一些约束条件:

  • minWidthminHeight 都被设置为 100.0,确保子组件至少为 100x100 的尺寸。
  • maxWidthmaxHeight 都被设置为 300.0,限制子组件的最大尺寸为 300x300。

ConstrainedBox 的子组件是一个蓝色的容器。由于应用了上述约束条件,这个容器的尺寸将被限制在 100x100 到 300x300 之间。

ConstrainedBox 在实现自适应布局、确保组件最小可见性以及处理尺寸不确定的子组件时非常有用。它是 Flutter 布局工具箱中一个非常强大和灵活的组件。

LimitedBox

LimitedBox 是一种特殊的 ConstrainedBox 组件,它的作用是限制子组件的最大尺寸,但允许子组件的最小尺寸小于限制值。

LimitedBox 的主要特点包括:

  1. 限制最大尺寸: 与 ConstrainedBox 类似,LimitedBox 可以设置子组件的最大宽度和高度。
  2. 允许小于限制值: 不同于 ConstrainedBox,LimitedBox 允许子组件的尺寸小于限制值。这意味着,如果子组件本身就小于限制值,它们不会被放大到限制值。
  3. 灵活性: LimitedBox 提供了一种更灵活的方式来限制子组件的尺寸,适用于那些尺寸不确定的子组件。

下面是一个简单的 LimitedBox 使用示例:

  Widget _mainView() {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          LimitedBox(
            maxWidth: 200,
            maxHeight: 100,
            child: Container(
              color: Colors.blue,
              width: 300, // 宽度超过 LimitedBox 的 maxWidth
              height: 300, // 高度超过 LimitedBox 的 maxHeight
              child: const Center(
                child: Text(
                  'LimitedBox',
                  style: TextStyle(color: Colors.white),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

LimitedBox

在这个例子中,我们创建了一个 LimitedBox,并将其最大宽度和高度都设置为 300。作为 LimitedBox 的子组件,我们有一个蓝色的容器,里面包含一些文本。

由于 LimitedBox 的限制,即使文本内容很长,导致容器的尺寸超过 100x200,容器也不会变得更大。相反,它会保持在 100x200 的最大尺寸,并且会根据需要自动溢出或截断文本内容。

LimitedBox 在处理动态内容或尺寸不确定的子组件时非常有用。它可以确保子组件不会超过指定的最大尺寸,同时保持灵活性,允许子组件小于限制值。

InteractiveViewer

InteractiveViewer 是一个强大的组件,它提供了一个可缩放和可平移的视图,使用户能够与其中的内容进行交互。它主要用于以下场景:

  1. 缩放和平移: InteractiveViewer 允许用户通过手势(如双击、捏合和拖动)缩放和平移其子组件。这在显示地图、图像或其他需要缩放和平移功能的内容时非常有用。
  2. 限制缩放和平移: InteractiveViewer 可以限制缩放和平移的范围,防止用户将内容缩放或平移到不合适的程度。这有助于确保内容的可见性和可用性。
  3. 自定义行为: InteractiveViewer 提供了一系列属性和回调,使开发者能够自定义缩放和平移的行为,如最小/最大缩放比例、缩放步长等。

下面是一个简单的 InteractiveViewer 使用示例:

Widget build(BuildContext context) {
  return InteractiveViewer(
    boundaryMargin: EdgeInsets.all(20.0),
    minScale: 0.1,
    maxScale: 4.0,
    child: Image.network('https://ducafecat.oss-cn-beijing.aliyuncs.com/podcast/2024/07/1754ca341970848fa7d943285d18a5b3.png'),
  );
}

InteractiveViewer

在这个例子中, InteractiveViewer 包裹了一个网络图像。用户可以通过手势缩放和平移这个图像,但缩放比例范围被限制在 0.1 到 4.0 之间。boundaryMargin 属性设置了图像周围的边距,以防止图像被完全平移出视图。

InteractiveViewer 还提供了一些有用的回调,如 onInteractionStart、onInteractionUpdate 和 onInteractionEnd,开发者可以使用这些回调来监听用户的交互事件,并根据需要进行自定义处理。

它在显示地图、图像、3D 模型等内容时非常有用。

Flow

Flutter 中的 Flow 组件是一个非常强大和灵活的布局方式,它允许开发者自定义元素的位置和大小,并根据特定的规则动态地更新它们。

Flow 组件的主要特点包括:

  1. 动态布局: Flow 组件可以根据其子元素的大小和位置动态地调整它们的布局。这对于需要根据屏幕大小或其他因素进行自适应布局的应用程序非常有用。
  2. 自定义定位: Flow 组件允许开发者编写自定义的定位逻辑,使用户能够完全控制元素的位置和大小。这为创建复杂的、交互式的用户界面提供了极大的灵活性。
  3. 动画支持: Flow 组件支持在布局变化时添加动画效果,使界面更加平滑和生动。
  4. 性能优化: Flow 组件采用了一些性能优化技术,如在元素大小或位置发生变化时仅重新渲染必要的部分,从而提高了整体性能。

下面是一个简单的 Flow 组件示例:

Widget build(BuildContext context) {
  return Flow(
    delegate: MyFlowDelegate(),
    children: [
      Container(width: 100, height: 100, color: Colors.red),
      Container(width: 80, height: 80, color: Colors.green),
      Container(width: 120, height: 120, color: Colors.blue),
    ],
  );
}

class MyFlowDelegate extends FlowDelegate {
  @override
  void paintChildren(FlowPaintingContext context) {
    double dx = 0.0;
    double dy = 0.0;

    for (int i = 0; i < context.childCount; i++) {
      double width = context.getChildSize(i)!.width;
      double height = context.getChildSize(i)!.height;

      context.paintChild(
        i,
        transform: Matrix4.translationValues(dx, dy, 0.0),
      );

      dx += width + 16.0;
      if (dx + width + 16.0 > context.size.width) {
        dx = 0.0;
        dy += height + 16.0;
      }
    }
  }

  @override
  bool shouldRepaint(covariant FlowDelegate oldDelegate) {
    return false;
  }
}

Flow

在这个示例中, Flow 组件包裹了三个容器,它们的大小和位置由自定义的 MyFlowDelegate 类控制。这个委托类负责计算每个子元素的位置,并在 paintChildren 方法中绘制它们。

通过 Flow 组件和自定义 FlowDelegate 类,开发者可以创建各种复杂的布局,如瀑布流、网格布局、层叠布局等。这使 Flutter 应用程序能够实现富有创意和动态的用户界面。

CustomMultiChildLayout

Flutter 中的 CustomMultiChildLayout 组件是一个非常强大的布局组件,它允许开发者完全自定义多个子组件的布局方式。与 Flow 组件不同,CustomMultiChildLayout 不仅可以控制子组件的位置,还可以控制它们的大小。

CustomMultiChildLayout 的主要特点包括:

  1. 灵活的布局逻辑: 开发者可以通过实现 MultiChildLayoutDelegate 接口来自定义子组件的布局逻辑。这使得CustomMultiChildLayout 能够适用于各种复杂的布局需求,如自适应布局、层叠布局、网格布局等。
  2. 动态布局: 当子组件的大小或位置发生变化时,CustomMultiChildLayout 会自动重新布局。这使得它能够应对动态变化的场景,如屏幕旋转或内容更新。
  3. 性能优化: CustomMultiChildLayout 采用了一些性能优化技术,如只对发生变化的子组件进行重新布局,从而提高了整体性能。

下面是一个简单的 CustomMultiChildLayout 示例:

好的,我来给你一个使用 CustomMultiChildLayout 实现简单色块布局的例子:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Color Block Layout',
      home: ColorBlockLayout(),
    );
  }
}

class ColorBlockLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[200],
      body: CustomMultiChildLayout(
        delegate: ColorBlockLayoutDelegate(),
        children: [
          LayoutId(
            id: 'red_block',
            child: ColorBlock(color: Colors.red),
          ),
          LayoutId(
            id: 'green_block',
            child: ColorBlock(color: Colors.green),
          ),
          LayoutId(
            id: 'blue_block',
            child: ColorBlock(color: Colors.blue),
          ),
          LayoutId(
            id: 'yellow_block',
            child: ColorBlock(color: Colors.yellow),
          ),
        ],
      ),
    );
  }
}

class ColorBlockLayoutDelegate extends MultiChildLayoutDelegate {
  @override
  void performLayout(Size size) {
    final double blockSize = 100;
    final double padding = 16;

    layoutChild('red_block', BoxConstraints.tight(Size(blockSize, blockSize)));
    positionChild('red_block', Offset(padding, padding));

    layoutChild('green_block', BoxConstraints.tight(Size(blockSize, blockSize)));
    positionChild('green_block', Offset(size.width - blockSize - padding, padding));

    layoutChild('blue_block', BoxConstraints.tight(Size(blockSize, blockSize)));
    positionChild('blue_block', Offset(padding, size.height - blockSize - padding));

    layoutChild('yellow_block', BoxConstraints.tight(Size(blockSize, blockSize)));
    positionChild('yellow_block', Offset(size.width - blockSize - padding, size.height - blockSize - padding));
  }

  @override
  bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) {
    return false;
  }
}

class ColorBlock extends StatelessWidget {
  final Color color;

  ColorBlock({required this.color});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: color,
    );
  }
}

CustomMultiChildLayout

在这个例子中,我们使用 CustomMultiChildLayout 来布局四个不同颜色的方块。

ColorBlockLayoutDelegate 实现了 MultiChildLayoutDelegate 接口,在 performLayout 方法中定义了每个方块的大小和位置。

layoutChild 方法用于测量子组件的大小,positionChild 方法用于设置子组件的位置。

ColorBlock 是一个简单的 StatelessWidget,它只是显示一个指定颜色的方块。

ShaderMask

Flutter 中的 ShaderMask 组件提供了一种在 Widget 上应用 Shader 效果的方式。Shader 是一种用于计算像素颜色的程序,它可以用于实现各种图形特效,如渐变、模糊、光照等。

ShaderMask 组件接受两个主要参数:

  1. Shader: 要应用的 Shader 对象。它可以是 LinearGradientRadialGradientSweepGradient 或者自定义的 Shader 实现。
  2. BlendMode: 指定 Shader 和原始 Widget 颜色之间的混合模式。

让我们看一个简单的例子,在一个 Text 组件上应用线性渐变:

ShaderMask(
  shaderCallback: (bounds) {
    return LinearGradient(
      blendMode: BlendMode.srcIn,
      colors: [Colors.red, Colors.blue],
      begin: Alignment.topCenter,
      end: Alignment.bottomCenter,
    ).createShader(bounds);
  },
  child: Text(
    'Gradient Text',
    style: TextStyle(
      fontSize: 48.0,
      fontWeight: FontWeight.bold,
    ),
  ),
)

ShaderMask

在这个例子中,我们使用 ShaderMask 将一个线性渐变应用到 Text 组件上。shaderCallback 方法返回一个 Shader 对象,它根据组件的大小创建渐变。

ReorderableListView

好的,Flutter 中的 ReorderableListView 组件是一个非常有用的小部件,它允许用户通过拖放来重新排序列表项。这是一个非常常见的需求,比如在社交应用程序中对联系人列表进行个性化排序,或在任务管理应用程序中对任务列表进行重新排序。

下面是一个使用 ReorderableListView 的简单示例:

import 'package:flutter/material.dart';

class WidgetPage extends StatefulWidget {
  const WidgetPage({super.key});

  @override
  State<WidgetPage> createState() => _WidgetPageState();
}

class _WidgetPageState extends State<WidgetPage> {
  final List<String> _items = ['Item 1', 'Item 2', 'Item 3'];

  Widget _buildItem(BuildContext context, int index) {
    return ListTile(
      key: ValueKey(_items[index]),
      title: Text(_items[index]),
    );
  }

  void _addItem() {
    setState(() {
      _items.add('Item ${_items.length + 1}');
    });
  }

  Widget _mainView() {
    return ReorderableListView.builder(
      itemCount: _items.length,
      itemBuilder: _buildItem,
      onReorder: (int oldIndex, int newIndex) {
        setState(() {
          if (oldIndex < newIndex) {
            newIndex -= 1;
          }
          String item = _items.removeAt(oldIndex);
          _items.insert(newIndex, item);
        });
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('非常用组件'),
        actions: [
          IconButton(
            icon: const Icon(Icons.add),
            onPressed: _addItem,
          ),
        ],
      ),
      body: _mainView(),
    );
  }
}

ReorderableListView

在这个示例中:

  1. 我们定义了一个 _items 列表来存储实际的列表项。
  2. _buildItem 函数中,我们使用 _items[index] 来获取每个列表项的标题文本。
  3. onReorder 回调中,我们使用 _items.removeAt()_items.insert() 方法来更新列表项的顺序。
  4. 我们仍然调用 setState() 来通知 Flutter 更新 UI。

    ReorderableListView 是一个非常强大的小部件,可以轻松实现用户可重排的列表界面。它广泛应用于各种类型的移动应用程序中。

Offstage

Flutter 中的 Offstage 组件是一个非常有用的小部件,它可以控制它的子组件是否显示在屏幕上。当 Offstageoffstage 属性为 true 时,其子组件将不会被渲染,也不会占用任何屏幕空间。这对于实现一些复杂的交互效果非常有帮助。

下面是 Offstage 组件的一些常见用途:

  1. 延迟加载: 当你有一些不重要的内容,只有在用户需要时才显示时,可以使用 Offstage 来延迟加载这些内容,从而提高应用的性能。
  2. 切换显示: 你可以使用 Offstage 来切换不同的 UI 组件,而不需要完全重建它们。这在实现复杂的用户界面时非常有帮助。
  3. 动画效果: Offstage 可以与动画结合使用,实现一些复杂的过渡效果。例如,你可以让一个组件从屏幕外缓慢移动到屏幕内部。
  4. 测试和调试: 在测试或调试 Flutter 应用程序时,Offstage 可以帮助你隐藏一些不需要的组件,从而简化界面并专注于需要测试的部分。

下面是一个简单的 Offstage 示例:

  bool _isOffstage = false;

  Widget _mainView() {
    return Center(
      child: Column(
        children: [
          ElevatedButton(
            onPressed: () {
              setState(() {
                _isOffstage = !_isOffstage;
              });
            },
            child: const Text('Toggle Offstage'),
          ),
          Offstage(
            offstage: _isOffstage,
            child: Container(
              color: Colors.blue,
              height: 200,
              width: 200,
              child: const Center(
                child: Text('This is the hidden content'),
              ),
            ),
          ),
        ],
      ),
    );
  }

Offstage

在这个示例中,我们有一个 Offstage 组件包裹着一个蓝色的容器。当点击 "Toggle Offstage" 按钮时,_isOffstage 变量的值会被反转,从而控制 Offstage 组件是否显示其子组件。

Offstage 是一个非常有用的 Flutter 组件,它可以帮助你实现各种复杂的 UI 效果和交互。

ListWheelScrollView

ListWheelScrollView 是 Flutter 中一个非常强大的滚动视图组件,它可以创建类似于"滚轮"样式的滚动列表。这个组件在需要展示有限数量的项目,并且用户需要能够快速查看和选择这些项目时非常有用。

以下是 ListWheelScrollView 的一些主要特点:

  1. 圆形滚动效果: ListWheelScrollView 会将其子项按照一定的弧度排列,形成一种类似于"滚轮"的视觉效果。这种效果可以帮助用户更直观地浏览和选择列表项。
  2. 自动居中: 当用户停止滚动时,ListWheelScrollView 会自动将当前选中的项目居中显示。这种交互方式非常适合于选择类型的场景,如时间选择器、日期选择器等。
  3. 可配置性强: ListWheelScrollView 提供了丰富的属性和回调函数,可以让开发者自定义滚动视图的外观和行为,如项目之间的间距、倾斜角度、透明度等。
  4. 性能良好: ListWheelScrollView 采用了一些优化技术,如只渲染可见区域的项目,从而确保即使列表项数量很多时也能保持良好的滚动性能。

下面是一个简单的 ListWheelScrollView 示例:

ListWheelScrollView(
  itemExtent: 100, // 每个项目的高度
  diameterRatio: 1.5, // 滚轮直径与项目高度的比率
  offAxisFraction: 0.5, // 滚轮的倾斜角度
  children: List.generate(
    20,
    (index) => Container(
      color: Colors.primaries[index % Colors.primaries.length],
      child: Center(
        child: Text('Item $index'),
      ),
    ),
  ),
  onSelectedItemChanged: (index) {
    // 当选中项目发生变化时的回调
    print('Selected item: $index');
  },
);

ListWheelScrollView

在这个示例中,我们创建了一个包含 20 个彩色容器的 ListWheelScrollView。我们设置了项目高度、滚轮直径、倾斜角度等属性,并添加了 onSelectedItemChanged 回调函数来监听选中项目的变化。

ListWheelScrollView 是一个非常强大和灵活的 Flutter 组件,它可以帮助开发者创建出高度交互性和美观度的滚动列表视图。无论是时间选择器、日期选择器还是其他需要快速浏览和选择项目的场景,都可以考虑使用 ListWheelScrollView

Draggable、DragTarget

Flutter 中的 DraggableDragTarget 组件是一对非常强大的组件,它们可以实现拖放功能,为应用程序添加丰富的交互体验。

  1. Draggable 组件:

    • Draggable 组件表示一个可以被拖拽的 UI 元素。
    • 当用户开始拖拽 Draggable 组件时,它会创建一个可视化的拖拽预览,并向系统发送拖拽事件。
    • Draggable 组件有几个重要属性:

      • data: 拖拽操作携带的数据。
      • child: 实际显示在屏幕上的拖拽元素。
      • feedback: 拖拽过程中显示的预览元素。
      • childWhenDragging: 拖拽时原始元素的显示方式。
  2. DragTarget 组件:

    • DragTarget 组件表示一个可以接受拖拽的目标区域。
    • 当拖拽的 Draggable 组件进入 DragTarget 区域时,DragTarget 会触发一系列回调,开发者可以在这些回调中处理相应的拖放逻辑。
    • DragTarget 组件有几个重要属性:

      • onAccept: 当拖拽数据被放置在 DragTarget 上时调用的回调。
      • onWillAccept: 当拖拽数据进入 DragTarget 时调用的回调,用于决定是否接受该数据。
      • onLeave: 当拖拽数据离开 DragTarget 时调用的回调。

下面是一个简单的拖拽示例:

import 'package:flutter/material.dart';

// 定义一个可拖拽的小球
class Draggableball extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Draggable<String>(
      data: 'ball',
      child: Container(
        width: 50,
        height: 50,
        decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: Colors.blue,
        ),
      ),
      feedback: Container(
        width: 50,
        height: 50,
        decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: Colors.blue.withOpacity(0.5),
        ),
      ),
    );
  }
}

// 定义一个可接受拖拽的目标区域
class DragTargetWidget extends StatefulWidget {
  @override
  _DragTargetWidgetState createState() => _DragTargetWidgetState();
}

class _DragTargetWidgetState extends State<DragTargetWidget> {
  Color _color = Colors.grey;

  @override
  Widget build(BuildContext context) {
    return DragTarget<String>(
      onAccept: (data) {
        setState(() {
          _color = Colors.green;
        });
      },
      onLeave: (data) {
        setState(() {
          _color = Colors.grey;
        });
      },
      builder: (context, candidateData, rejectedData) {
        return Container(
          width: 200,
          height: 200,
          color: _color,
          child: Center(
            child: Text('Drop the ball here'),
          ),
        );
      },
    );
  }
}

// 主页面
class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Draggable and DragTarget Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Draggableball(),
            SizedBox(height: 32),
            DragTargetWidget(),
          ],
        ),
      ),
    );
  }
}

void main() {
  runApp(
    MaterialApp(
      title: 'Draggable and DragTarget Example',
      home: MyHomePage(),
    ),
  );
}

Draggable and DragTarget

我们将 Draggableball 组件添加到了主页面的布局中。现在,当你运行这个应用程序时,你应该能看到一个可拖拽的小球和一个可接受拖拽的目标区域。尝试将小球拖拽到目标区域中,你会看到目标区域的颜色会变成绿色。

通过组合使用 DraggableDragTarget 组件,开发者可以在 Flutter 应用中实现丰富的拖放交互功能,为用户提供更加直观和有趣的体验。

KeyedSubtree

Flutter 中的 KeyedSubtree 组件是一个非常有用的组件,它可以帮助开发者解决一些常见的问题。

  1. 什么是 KeyedSubtree?

    • KeyedSubtree 是一个包装组件,它会为其子组件添加一个唯一的 Key
    • Key 是 Flutter 中用于标识 widget 的一个重要属性,它可以帮助 Flutter 跟踪和更新 widget 的状态。
  2. 为什么需要 KeyedSubtree?

    • 在某些情况下,Flutter 无法正确地更新或重建 widget 树,这可能会导致一些问题,例如动画中断、状态丢失等。
    • KeyedSubtree 可以帮助 Flutter 更好地跟踪和管理 widget 的生命周期,从而解决这些问题。
  3. KeyedSubtree 的使用场景

    • 在使用 AnimatedBuilderLayoutBuilder 时,可以使用 KeyedSubtree 来确保子组件能够正确地更新和重建。
    • 在使用 ListView.builderGridView.builder 时,可以使用 KeyedSubtree 来确保列表项能够正确地更新和重建。
    • 在使用 AnimatedListAnimatedSwitcher 时,可以使用 KeyedSubtree 来确保动画能够正确地执行。

下面是一个使用 KeyedSubtree 的示例:

import 'package:flutter/material.dart';

class WidgetPage extends StatefulWidget {
  const WidgetPage({super.key});

  @override
  State<WidgetPage> createState() => _WidgetPageState();
}

class _WidgetPageState extends State<WidgetPage> {
  final List<int> _items = [1, 2, 3, 4, 5];
  final _listKey = GlobalKey<AnimatedListState>();

  void _addItem() {
    setState(() {
      _items.add(_items.length + 1);
      _listKey.currentState?.insertItem(_items.length - 1);
    });
  }

  Widget _mainView() {
    return Column(
      children: [
        ElevatedButton(
          onPressed: _addItem,
          child: const Text('Add Item'),
        ),
        Expanded(
          child: AnimatedList(
            key: _listKey,
            initialItemCount: _items.length,
            itemBuilder: (context, index, animation) {
              return KeyedSubtree(
                key: ValueKey(_items[index]),
                child: SizeTransition(
                  sizeFactor: animation,
                  child: ListTile(
                    title: Text('Item ${_items[index]}'),
                  ),
                ),
              );
            },
          ),
        ),
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('非常用组件'),
      ),
      body: _mainView(),
    );
  }
}

KeyedSubtree

在这个示例中,我们使用了 AnimatedList 来显示一个动画列表。

  1. 添加了一个 _listKey 变量,它是一个 GlobalKey<AnimatedListState> 类型的键。这个键将用于访问 AnimatedList 的状态,从而能够在列表发生变化时执行插入/删除操作。
  2. _addItem 函数中,在添加新项目到 _items 列表之后,还调用了 _listKey.currentState?.insertItem(...) 方法。这个方法可以通知 AnimatedList 插入了一个新项目,从而触发动画效果。
  3. AnimatedList 组件上设置了 key 属性,值为 _listKey。这样可以确保 AnimatedList 能够正确地跟踪和管理列表项的变化。

KeyedSubtree 是一个非常有用的组件,它可以帮助开发者解决一些常见的 Flutter 问题。通过合理地使用 KeyedSubtree,开发者可以确保 Flutter 应用程序能够更好地处理动画、状态管理等问题,从而提高应用程序的稳定性和性能。

Baseline

Baseline 组件的主要作用是确保其子元素的文本基线对齐。这在处理包含不同字体大小的文本时特别有用,因为不同字体的基线位置可能不同。

下面是一个简单的例子,展示了如何使用 Baseline 组件:

  Widget _mainView() {
    return const Row(
      crossAxisAlignment: CrossAxisAlignment.baseline,
      textBaseline: TextBaseline.alphabetic,
      children: [
        Baseline(
          baseline: 30.0,
          baselineType: TextBaseline.alphabetic,
          child: Text(
            'Hello',
            style: TextStyle(fontSize: 24.0, backgroundColor: Colors.red),
          ),
        ),
        SizedBox(width: 8.0),
        Baseline(
          baseline: 30.0,
          baselineType: TextBaseline.alphabetic,
          child: Text(
            'World',
            style: TextStyle(fontSize: 16.0, backgroundColor: Colors.blue),
          ),
        ),
      ],
    );
  }

Baseline

在这个例子中:

  1. Row 组件的 crossAxisAlignment 被设置为 CrossAxisAlignment.baseline,这样可以确保其子元素的文本基线对齐。
  2. Row 组件的 textBaseline 被设置为 TextBaseline.alphabetic,这样可以指定使用字母字型的基线。
  3. 每个 Text 组件都被包裹在一个 Baseline 组件中。Baseline 组件的 baseline 属性被设置为 0.0,这样可以确保文本的基线位于垂直中心位置。baselineType 属性被设置为 TextBaseline.alphabetic,表示使用字母字型的基线。

通过使用 Baseline 组件,即使 Text 组件使用了不同的字体大小,它们的文本基线也能够保持对齐。这在处理复杂的布局时非常有用,例如在一个表格或者对话框中显示不同大小的文本。

除了文本对齐,Baseline 组件还可以用于其他一些用例,比如对齐图标和文本,或者在不同的行内元素之间保持垂直对齐。可以帮助我们创建出更加精细和美观的 UI 界面。

小结

本文将深入探讨 Flutter 框架中一些鲜为人知但却功能强大的组件,帮助 Flutter 开发者拓展应用程序的设计边界,提升代码质量和用户体验。

感谢阅读本文

如果有什么建议,请在评论中让我知道。我很乐意改进。


flutter 学习路径


© 猫哥
ducafecat.com

end


独立开发者_猫哥
669 声望128 粉丝