Flutter信息展开/收起插件disclosure的使用

发布于 1周前 作者 itying888 来自 Flutter

Flutter信息展开/收起插件disclosure的使用

简介

disclosure 是一个 Flutter 插件,用于简化自定义 UI 的构建,特别是那些需要显示和隐藏内容的控件,如手风琴面板。通过 disclosure,你可以轻松地实现信息的展开和收起功能。

版本和许可

  • Pub Version
  • GitHub License
  • Buy Me a Coffee
  • Ko-Fi

预览

Preview

在线演示

使用方法

导入包

在你的 Dart 文件中导入 disclosure 包:

import 'package:disclosure/disclosure.dart';

示例代码

以下是一些使用 disclosure 插件的示例代码,展示了如何实现不同的展开/收起效果。

单个展开面板

Disclosure(
  wrapper: (state, child) {
    return Card.outlined(
      color: state.closed ? null : Colors.yellow.shade100,
      clipBehavior: Clip.antiAlias,
      shape: RoundedRectangleBorder(
        side: BorderSide(
          color: state.closed ? Colors.black26 : Colors.amber,
          width: state.closed ? 1 : 2,
        ),
      ),
      borderRadius: BorderRadius.circular(10),
      child: child,
    );
  },
  header: const DisclosureButton(
    child: ListTile(
      title: Text('Disclosure Panel'),
      trailing: DisclosureIcon(),
    ),
  ),
  divider: const Divider(height: 1),
  child: const DisclosureView(
    maxHeight: 200,
    padding: EdgeInsets.all(15.0),
    child: Text(
      "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
    ),
  ),
)

多个展开面板

DisclosureGroup(
  multiple: false,
  clearable: true,
  insets: const EdgeInsets.all(15),
  children: List<Widget>.generate(5, (i) {
    return Disclosure(
      key: ValueKey('disclosure-$i'),
      wrapper: (state, child) {
        return Card.outlined(
          color: state.closed ? null : Colors.yellow.shade100,
          clipBehavior: Clip.antiAlias,
          shape: RoundedRectangleBorder(
            side: BorderSide(
              color: state.closed ? Colors.black26 : Colors.amber,
              width: state.closed ? 1 : 2,
            ),
          ),
          child: child,
        );
      },
      header: DisclosureButton(
        child: ListTile(
          title: Text('Disclosure Panel ${i + 1}'),
          trailing: const DisclosureIcon(),
        ),
      ),
      divider: const Divider(height: 1),
      child: const Padding(
        padding: EdgeInsets.all(15),
        child: Text(
          "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen.",
        ),
      ),
    );
  }),
)

菜单和列表中的展开项

DisclosureTile(
  title: const Text('Nested Menu'),
  children: [
    DisclosureTile(
      title: const Text('Menu Item'),
      onTap: () {},
    ),
    DisclosureTile(
      title: const Text('Nested Menu'),
      children: [
        DisclosureTile(
          title: const Text('Menu Item'),
          onTap: () {},
        ),
      ],
    ),
  ],
),

复杂实现:带次要操作和网格布局

Disclosure(
  secondary: DisclosureButton.basic(
    child: Image.network(
      'https://picsum.photos/500?image=1',
      fit: BoxFit.cover,
    ),
  ),
  child: Column(
    mainAxisSize: MainAxisSize.min,
    children: [
      GridView(
        shrinkWrap: true,
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
        ),
        children: [
          Image.network('https://picsum.photos/150?image=1'),
          Image.network('https://picsum.photos/150?image=2'),
          Image.network('https://picsum.photos/150?image=3'),
          Image.network('https://picsum.photos/150?image=4'),
          Image.network('https://picsum.photos/150?image=5'),
          Image.network('https://picsum.photos/150?image=6'),
          Image.network('https://picsum.photos/150?image=7'),
          Image.network('https://picsum.photos/150?image=8'),
          Image.network('https://picsum.photos/150?image=9'),
        ],
      ),
      const Padding(
        padding: EdgeInsets.all(15.0),
        child: Text(
          "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
        ),
      ),
      DisclosureButton(
        wrapper: DisclosureButtonWrapper.outlined,
        child: Text('Close'),
      ),
      const SizedBox(height: 20),
    ],
  ),
)

有限可见性的剧透文本

Disclosure(
  closed: true,
  secondary: DisclosureButton(
    child: Padding(
      padding: EdgeInsets.all(20.0),
      child: Text(
        "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
        maxLines: 2,
        overflow: TextOverflow.ellipsis,
      ),
    ),
  ),
  child: Padding(
    padding: EdgeInsets.all(20.0),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
        ),
        SizedBox(height: 20),
        DisclosureButton.tonal(
          child: Text(
            'Show less',
            style: TextStyle(fontWeight: FontWeight.bold),
          ),
        ),
      ],
    ),
  ),
)

完整示例

以下是一个完整的示例应用,展示了如何在 Flutter 应用中使用 disclosure 插件。

import 'package:flutter/material.dart';
import 'package:disclosure/disclosure.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Disclosure',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      showPerformanceOverlay: false,
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Wrapper(
      children: <Widget>[
        const Padding(
          padding: EdgeInsets.fromLTRB(0, 40, 0, 25),
          child: Center(
            child: Text(
              'Disclosure',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
          ),
        ),
        Example(
          title: 'Disclosure panel with scrollable content',
          child: Disclosure(
            wrapper: (state, child) {
              return Card.outlined(
                color: state.closed ? null : Colors.yellow.shade100,
                clipBehavior: Clip.antiAlias,
                shape: RoundedRectangleBorder(
                  side: BorderSide(
                    color: state.closed ? Colors.black26 : Colors.amber,
                    width: state.closed ? 1 : 2,
                  ),
                  borderRadius: BorderRadius.circular(10),
                ),
                child: child,
              );
            },
            header: const DisclosureButton(
              child: ListTile(
                title: Text('Disclosure Panel'),
                trailing: DisclosureIcon(),
              ),
            ),
            divider: const Divider(height: 1),
            child: const DisclosureView(
              maxHeight: 200,
              padding: EdgeInsets.all(15.0),
              child: Text(
                "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
              ),
            ),
          ),
        ),
        const SizedBox(height: 20),
        Example(
          title: 'Disclosure group',
          child: Card.outlined(
            child: DisclosureGroup(
              multiple: false,
              clearable: true,
              insets: const EdgeInsets.all(15),
              children: List<Widget>.generate(3, (i) {
                return Disclosure(
                  key: ValueKey('disclosure-$i'),
                  wrapper: (state, child) {
                    return Card.outlined(
                      color: state.closed ? null : Colors.yellow.shade100,
                      clipBehavior: Clip.antiAlias,
                      shape: RoundedRectangleBorder(
                        side: BorderSide(
                          color: state.closed ? Colors.black26 : Colors.amber,
                          width: state.closed ? 1 : 2,
                        ),
                      ),
                      child: child,
                    );
                  },
                  header: DisclosureButton(
                    child: ListTile(
                      title: Text('Disclosure Panel ${i + 1}'),
                      trailing: const DisclosureSwitcher(
                        opened: Icon(Icons.arrow_drop_down_circle),
                        closed: Icon(Icons.arrow_drop_down),
                      ),
                    ),
                  ),
                  divider: const Divider(height: 1),
                  child: const Padding(
                    padding: EdgeInsets.all(15),
                    child: Text(
                      "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen.",
                    ),
                  ),
                );
              }),
            ),
          ),
        ),
        const SizedBox(height: 20),
        Example(
          title: 'Disclosure tile',
          child: Card.filled(
            clipBehavior: Clip.hardEdge,
            child: DisclosureTheme.merge(
              icon: Icons.arrow_drop_down,
              wrapper: (state, child) {
                final side = BorderSide(
                  color: Colors.black26,
                  style: state.closed ? BorderStyle.none : BorderStyle.solid,
                );
                return AnimatedContainer(
                  duration: const Duration(milliseconds: 200),
                  decoration: BoxDecoration(
                    border: Border(top: side, bottom: side),
                  ),
                  child: child,
                );
              },
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  DisclosureTile(
                    title: const Text('Menu Item'),
                    onTap: () {},
                  ),
                  DisclosureTile(
                    insets: const EdgeInsets.only(left: 25),
                    title: const Text('Nested Menu'),
                    children: [
                      DisclosureTile(
                        title: const Text('Menu Item'),
                        onTap: () {},
                      ),
                      DisclosureTile(
                        title: const Text('Nested Menu 1'),
                        children: [
                          DisclosureTile(
                            title: const Text('Menu Item'),
                            onTap: () {},
                          ),
                          DisclosureTile(
                            title: const Text('Menu Item'),
                            onTap: () {},
                          ),
                          DisclosureTile(
                            title: const Text('Menu Item'),
                            onTap: () {},
                          ),
                        ],
                      ),
                      DisclosureTile(
                        title: const Text('Nested Menu 2'),
                        children: [
                          DisclosureTile(
                            title: const Text('Menu Item'),
                            onTap: () {},
                          ),
                          DisclosureTile(
                            title: const Text('Menu Item'),
                            onTap: () {},
                          ),
                          DisclosureTile(
                            title: const Text('Menu Item'),
                            onTap: () {},
                          ),
                        ],
                      ),
                      DisclosureTile(
                        title: const Text('Menu Item'),
                        onTap: () {},
                      ),
                    ],
                  ),
                  DisclosureTile(
                    title: const Text('Menu Item'),
                    onTap: () {},
                  ),
                ],
              ),
            ),
          ),
        ),
        const SizedBox(height: 20),
        Example(
          title: 'With secondary widget',
          child: Card(
            clipBehavior: Clip.hardEdge,
            child: Disclosure(
              secondary: const DisclosureButton.basic(
                child: Image.network(
                  'https://picsum.photos/500?image=1',
                  fit: BoxFit.cover,
                ),
              ),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  GridView(
                    shrinkWrap: true,
                    physics: const NeverScrollableScrollPhysics(),
                    gridDelegate:
                        const SliverGridDelegateWithFixedCrossAxisCount(
                      crossAxisCount: 3,
                    ),
                    children: const [
                      Image.network('https://picsum.photos/150?image=1'),
                      Image.network('https://picsum.photos/150?image=2'),
                      Image.network('https://picsum.photos/150?image=3'),
                      Image.network('https://picsum.photos/150?image=4'),
                      Image.network('https://picsum.photos/150?image=5'),
                      Image.network('https://picsum.photos/150?image=6'),
                    ],
                  ),
                  const Padding(
                    padding: EdgeInsets.all(15.0),
                    child: Text(
                      "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.",
                    ),
                  ),
                  const DisclosureButton(
                    wrapper: DisclosureButtonWrapper.outlined,
                    child: Text('Close'),
                  ),
                  const SizedBox(height: 20),
                ],
              ),
            ),
          ),
        ),
        const SizedBox(height: 20),
        const Example(
          title: 'Spoiler text',
          child: Card.outlined(
            clipBehavior: Clip.hardEdge,
            child: Disclosure(
              closed: true,
              secondary: DisclosureButton(
                padding: EdgeInsets.all(20.0),
                child: Text(
                  "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                ),
              ),
              child: Padding(
                padding: EdgeInsets.all(20.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
                    ),
                    SizedBox(height: 20),
                    DisclosureButton.tonal(
                      child: Text(
                        'Show less',
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
        const SizedBox(height: 40),
      ],
    );
  }
}

class Wrapper extends StatelessWidget {
  const Wrapper({
    super.key,
    required this.children,
  });

  final List<Widget> children;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        child: Center(
          child: ConstrainedBox(
            constraints: const BoxConstraints(
              maxWidth: 450,
            ),
            child: SizedBox(
              width: double.infinity,
              child: Padding(
                padding: const EdgeInsets.symmetric(horizontal: 20),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: children,
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class Example extends StatelessWidget {
  const Example({
    super.key,
    required this.title,
    required this.child,
  });

  final String title;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      mainAxisSize: MainAxisSize.min,
      children: [
        Padding(
          padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
          child: Text(
            title,
            style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
        ),
        child,
      ],
    );
  }
}

赞助

如果你觉得这个插件或其他我创建的插件对你有帮助,请考虑赞助我,以便我可以有更多时间处理问题、修复 bug、合并 PR 和添加新功能。

感谢你的支持!


更多关于Flutter信息展开/收起插件disclosure的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter信息展开/收起插件disclosure的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter中使用disclosure插件来实现信息展开/收起功能的示例代码。这个示例假设你已经在pubspec.yaml文件中添加了disclosure插件的依赖,并且已经运行了flutter pub get来安装它。

首先,确保你的pubspec.yaml文件中包含以下依赖:

dependencies:
  flutter:
    sdk: flutter
  disclosure: ^x.y.z  # 请替换为最新的版本号

然后,在你的Dart文件中,你可以按照以下方式使用Disclosure组件来实现信息的展开和收起:

import 'package:flutter/material.dart';
import 'package:disclosure/disclosure.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Disclosure Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Disclosure Example'),
        ),
        body: Center(
          child: MyDisclosureWidget(),
        ),
      ),
    );
  }
}

class MyDisclosureWidget extends StatefulWidget {
  @override
  _MyDisclosureWidgetState createState() => _MyDisclosureWidgetState();
}

class _MyDisclosureWidgetState extends State<MyDisclosureWidget> {
  bool isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Disclosure(
          expanded: isExpanded,
          onChange: (newState) {
            setState(() {
              isExpanded = newState;
            });
          },
          content: Container(
            padding: EdgeInsets.all(16.0),
            decoration: BoxDecoration(
              border: Border.all(color: Colors.grey.shade300),
              borderRadius: BorderRadius.circular(8.0),
            ),
            child: Text(
              'This is the expandable content. You can put any widget here, such as text, images, or lists.',
              style: TextStyle(fontSize: 16),
            ),
          ),
          header: Text(
            'Click to ${isExpanded ? 'collapse' : 'expand'}',
            style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
          ),
        ),
      ],
    );
  }
}

在这个示例中,我们创建了一个MyDisclosureWidget组件,它包含一个Disclosure组件。Disclosure组件有两个主要属性:expandedonChange,以及可选的contentheader

  • expanded:一个布尔值,表示当前内容是否展开。
  • onChange:一个回调函数,当用户点击头部以展开或收起内容时调用,并传入新的展开状态。
  • content:当内容展开时显示的子组件。
  • header:一个显示在内容上方的头部组件,通常用于显示点击以展开/收起的文本。

当用户点击头部时,onChange回调函数会被调用,并更新isExpanded状态,这将触发setState,从而重新构建Disclosure组件并显示或隐藏内容。

这个示例提供了一个简单而直观的方式来在Flutter应用中实现信息的展开和收起功能。你可以根据需要进一步自定义和扩展这个示例。

回到顶部