Flutter 如果多级列表的节点具有单击时应显示的内容,该怎么办?(高级扩展文件)

Flutter 如果多级列表的节点具有单击时应显示的内容,该怎么办?(高级扩展文件),flutter,Flutter,对于Flatter项目,我需要一种树状结构,在这种结构中,当单击节点时,不仅会显示节点下方的条目,而且会像Windows中的文件管理器一样显示内容:在智能手机上作为新屏幕,在平板电脑上作为列表右侧的附加区域。 不幸的是,标准的ExpansionFile没有这个功能。因为我是一个新手,所以我首先看了源代码,并试图理解最重要的部分(我还在做;-)。然后我做了以下更改: 属性“AlignOpener”现在决定打开/关闭图标是显示在左侧还是右侧 我添加了属性“onTap”和“onLongPress”作为

对于Flatter项目,我需要一种树状结构,在这种结构中,当单击节点时,不仅会显示节点下方的条目,而且会像Windows中的文件管理器一样显示内容:在智能手机上作为新屏幕,在平板电脑上作为列表右侧的附加区域。
不幸的是,标准的ExpansionFile没有这个功能。

因为我是一个新手,所以我首先看了源代码,并试图理解最重要的部分(我还在做;-)。然后我做了以下更改:

  • 属性“AlignOpener”现在决定打开/关闭图标是显示在左侧还是右侧
  • 我添加了属性“onTap”和“onLongPress”作为回调。一旦在调用小部件中分配了其中一个属性,“\u isInAdvancedMode”属性设置为true,并且IconButton作为前导或尾随插入。 单击此按钮将打开/关闭目录树。单击其余部分将通过回调转发给调用小部件
  • 最后,我添加了一个属性“indentListTile”来控制每个层的缩进
  • 如果未指定任何属性,则AvancedExpansionTile的行为与标准扩展文件类似

    因为我是个新手,所以代码是否真的正确还有很多不确定性。据我所知,它是有效的,但如果你们当中有经验的开发人员能够检查代码并在必要时提出改进建议,我会很高兴。 也许代码解决了其他人也遇到的问题

    她的密码是:

    主列表项目。省道

    import 'package:meta/meta.dart';
    
    class ItemMaster {
      ItemMaster(
          {this.id,
          @required this.title,
          this.subtitle,
          this.children = const <ItemMaster>[]});
    
      final int id;
      final String title;
      final String subtitle;
      final List<ItemMaster> children;
    }
    
    import'package:meta/meta.dart';
    类ItemMaster{
    项目主管(
    {this.id,
    @需要这个标题,
    这个,副标题,,
    this.children=const[]});
    最终int id;
    最后的字符串标题;
    最后一串字幕;
    最后儿童名单;
    }
    
    飞镖

    import 'master_list_item.dart';
    
    class MasterList {
      List<ItemMaster> get items {
        return _items;
      }
    
      final List<ItemMaster> _items = <ItemMaster>[
        ItemMaster(
          title: 'Chapter Master List',
          id: 1,
          children: <ItemMaster>[
            ItemMaster(
              title: 'Scene A0',
              id: 2,
              children: <ItemMaster>[
                ItemMaster(title: 'Item A0.1', id: 3),
                ItemMaster(title: 'Item A0.2', id: 4),
                ItemMaster(title: 'Item A0.3', id: 5),
              ],
            ),
            ItemMaster(title: 'Scene A1', id: 6),
            ItemMaster(title: 'Scene A2', id: 7),
          ],
        ),
        ItemMaster(
          title: 'Chapter B',
          id: 8,
          children: <ItemMaster>[
            ItemMaster(title: 'Scene B0', id: 9),
            ItemMaster(title: 'Scene B1', id: 10),
          ],
        ),
        ItemMaster(
          title: 'Chapter C',
          id: 11,
          children: <ItemMaster>[
            ItemMaster(title: 'Scene C0', id: 12),
            ItemMaster(title: 'Scene C1', id: 13),
            ItemMaster(
              title: 'Scene C2',
              id: 14,
              children: <ItemMaster>[
                ItemMaster(title: 'Item C2.0', id: 15),
                ItemMaster(title: 'Item C2.1', id: 16),
                ItemMaster(title: 'Item C2.2', id: 17),
                ItemMaster(title: 'Item C2.3', id: 18),
              ],
            ),
          ],
        ),
      ];
    }
    
    import'master_list_item.dart';
    班级总名单{
    列出获取项目{
    退货(物品);;
    }
    最终清单_项目=[
    项目主管(
    标题:“章节主列表”,
    id:1,
    儿童:[
    项目主管(
    标题:“场景A0”,
    id:2,
    儿童:[
    ItemMaster(标题:“项目A0.1”,id:3),
    ItemMaster(标题:“项目A0.2”,id:4),
    ItemMaster(标题:“项目A0.3”,id:5),
    ],
    ),
    ItemMaster(标题:“场景A1”,id:6),
    ItemMaster(标题:“场景A2”,id:7),
    ],
    ),
    项目主管(
    标题:“B章”,
    id:8,
    儿童:[
    ItemMaster(标题:“场景B0”,id:9),
    ItemMaster(标题:“场景B1”,id:10),
    ],
    ),
    项目主管(
    标题:“C章”,
    id:11,
    儿童:[
    ItemMaster(标题:“场景C0”,id:12),
    ItemMaster(标题:“场景C1”,id:13),
    项目主管(
    标题:“场景C2”,
    身份证号码:14,
    儿童:[
    ItemMaster(标题:“项目C2.0”,id:15),
    ItemMaster(标题:“项目C2.1”,id:16),
    ItemMaster(标题:“项目C2.2”,id:17),
    ItemMaster(标题:“项目C2.3”,id:18),
    ],
    ),
    ],
    ),
    ];
    }
    
    主飞镖

    import 'package:flutter/material.dart';
    import 'advanced_expansion_tile.dart';
    import 'master_list_item.dart';
    import 'master_list.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      List<ItemMaster> items;
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
    
        items = MasterList().items;
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Container(
                child: ListView.builder(
              itemCount: items.length,
              itemBuilder: (context, int index) => MasterListEntry(items[index]),
            )),
          ),
        );
      }
    }
    
    class MasterListEntry extends StatelessWidget {
      const MasterListEntry(this.entry);
    
      final ItemMaster entry;
    
      Widget _buildTiles(ItemMaster root) {
        if (root.children.isEmpty)
          return ListTile(
            title: Text(root.title),
            onTap: () => print("onTap listTile"),
          );
        return AdvancedExpansionTile(
          key: PageStorageKey<ItemMaster>(root),
          title: Text(root.title),
          children: root.children.map(_buildTiles).toList(),
          onTap: () => print("onTap AdvancedExpansionTile"),
          alignOpener: AlignOpener.Right,
          indentListTile: 15.0,
    //      isInAdvancedMode: true,
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return _buildTiles(entry);
      }
    }
    
    导入“包装:颤振/材料.省道”;
    导入“advanced_expansion_tile.dart”;
    导入“master_list_item.dart”;
    导入“master_list.dart”;
    void main()=>runApp(MyApp());
    类MyApp扩展了无状态小部件{
    //此小部件是应用程序的根。
    @凌驾
    小部件构建(构建上下文){
    返回材料PP(
    标题:“颤振演示”,
    主题:主题数据(
    主样本:颜色。蓝色,
    ),
    主页:MyHomePage(标题:“颤振演示主页”),
    );
    }
    }
    类MyHomePage扩展StatefulWidget{
    MyHomePage({Key,this.title}):超级(Key:Key);
    最后的字符串标题;
    @凌驾
    _MyHomePageState createState()=>\u MyHomePageState();
    }
    类_MyHomePageState扩展状态{
    清单项目;
    @凌驾
    void initState(){
    //TODO:实现initState
    super.initState();
    项目=主列表()。项目;
    }
    @凌驾
    小部件构建(构建上下文){
    返回脚手架(
    appBar:appBar(
    标题:文本(widget.title),
    ),
    正文:中(
    子:容器(
    子项:ListView.builder(
    itemCount:items.length,
    itemBuilder:(context,int index)=>MasterListEntry(items[index]),
    )),
    ),
    );
    }
    }
    类MasterListEntry扩展了无状态小部件{
    const MasterListEntry(此条目);
    最终的ItemMaster条目;
    Widget\u buildTiles(ItemMaster根目录){
    if(root.children.isEmpty)
    返回列表块(
    标题:文本(root.title),
    onTap:()=>打印(“onTap列表块”),
    );
    返回高级扩展文件(
    密钥:PageStorageKey(根),
    标题:文本(root.title),
    children:root.children.map(_buildTiles.toList(),
    onTap:()=>打印(“onTap高级扩展文件”),
    对齐打开器:对齐打开器。对,
    indentListTile:15.0,
    //isInAdvancedMode:正确,
    );
    }
    @凌驾
    小部件构建(构建上下文){
    返回(条目);;
    }
    }
    
    高级扩展\u tile.dart(基于Flatter团队的源代码)

    //作者版权所有。版权所有。
    //此源代码的使用受BSD样式许可证的约束,该许可证可以
    //在许可证文件中找到。
    导入“package:flatter/widgets.dart”;
    进口“包装:颤振/材料.省道”;
    const Duration _kExpand=持续时间(毫秒:200);
    枚举对齐开启器{左,右}
    类AdvancedExpansionFile扩展StatefulWidget{
    常量高级扩展文件({
    关键点,
    这个,领导,,
    这个,拖尾,,
    @需要这个标题,
    这个背景色,
    这个,一个扩展改变了,
    这个.onTap,
    这是唯一的新闻,
    这是我的开瓶器,
    这是一块瓷砖,
    
    // Copyright 2017 The Chromium Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    
    import 'package:flutter/widgets.dart';
    import 'package:flutter/material.dart';
    
    const Duration _kExpand = Duration(milliseconds: 200);
    enum AlignOpener { Left, Right }
    
    class AdvancedExpansionTile extends StatefulWidget {
      const AdvancedExpansionTile({
        Key key,
        this.leading,
        this.trailing,
        @required this.title,
        this.backgroundColor,
        this.onExpansionChanged,
        this.onTap,
        this.onLongPress,
        this.alignOpener,
        this.indentListTile,
        this.children = const <Widget>[],
        this.initiallyExpanded = false,
      })  : assert(initiallyExpanded != null),
            super(key: key);
    
      /// A widget to display before the title.
      ///
      /// Typically a [CircleAvatar] widget.
      final Widget leading;
    
      /// The primary content of the list item.
      ///
      /// Typically a [Text] widget.
      final Widget title;
    
      /// Called when the tile expands or collapses.
      ///
      /// When the tile starts expanding, this function is called with the value
      /// true. When the tile starts collapsing, this function is called with
      /// the value false.
      final ValueChanged<bool> onExpansionChanged;
    
      /// The widgets that are displayed when the tile expands.
      ///
      /// Typically [ListTile] widgets.
      final List<Widget> children;
    
      /// The color to display behind the sublist when expanded.
      final Color backgroundColor;
    
      /// Specifies if the list tile is initially expanded (true) or collapsed (false, the default).
      final bool initiallyExpanded;
    
      /// A widget to display instead of a rotating arrow icon.
      final Widget trailing;
    
      /// A callback for onTap and onLongPress on the listTile
      final GestureTapCallback onTap;
      final GestureLongPressCallback onLongPress;
    
      /// The side where the Open/Close-Icon/IconButton will be placed
      final AlignOpener alignOpener;
    
      /// indent of listTile (left)
      final indentListTile;
    
      @override
      _AdvancedExpansionTileState createState() => _AdvancedExpansionTileState();
    }
    
    class _AdvancedExpansionTileState extends State<AdvancedExpansionTile>
        with SingleTickerProviderStateMixin {
      static final Animatable<double> _easeOutTween =
          CurveTween(curve: Curves.easeOut);
      static final Animatable<double> _easeInTween =
          CurveTween(curve: Curves.easeIn);
      static final Animatable<double> _halfTween =
          Tween<double>(begin: 0.0, end: 0.5);
    
      final ColorTween _borderColorTween = ColorTween();
      final ColorTween _headerColorTween = ColorTween();
      final ColorTween _iconColorTween = ColorTween();
      final ColorTween _backgroundColorTween = ColorTween();
    
      AnimationController _controller;
      Animation<double> _iconTurns;
      Animation<double> _heightFactor;
      Animation<Color> _borderColor;
      Animation<Color> _headerColor;
      Animation<Color> _iconColor;
      Animation<Color> _backgroundColor;
    
      bool _isExpanded = false;
    
      /// If set to true an IconButton will be created. This button will open/close the children
      bool _isInAdvancedMode;
      AlignOpener _alignOpener;
      double _indentListTile;
    
      @override
      void initState() {
        super.initState();
        _controller = AnimationController(duration: _kExpand, vsync: this);
        _heightFactor = _controller.drive(_easeInTween);
        _iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
        _borderColor = _controller.drive(_borderColorTween.chain(_easeOutTween));
        _headerColor = _controller.drive(_headerColorTween.chain(_easeInTween));
        _iconColor = _controller.drive(_iconColorTween.chain(_easeInTween));
        _backgroundColor =
            _controller.drive(_backgroundColorTween.chain(_easeOutTween));
    
        _isExpanded =
            PageStorage.of(context)?.readState(context) ?? widget.initiallyExpanded;
        if (_isExpanded) _controller.value = 1.0;
    
        /// OnTap or onLongPress are handled in the calling widget --> AdvancedExpansionTile is in Advanced Mode
        if (widget.onTap != null || widget.onLongPress != null) {
          _isInAdvancedMode = true;
        } else {
          _isInAdvancedMode = false;
        }
    
        /// fallback to standard behaviour if aligning isn't set
        _alignOpener = widget.alignOpener ?? AlignOpener.Right;
    
        /// if no indent is set the indent will be 0.0
        _indentListTile = widget.indentListTile ?? 0.0;
      }
    
      @override
      void dispose() {
        _controller.dispose();
        super.dispose();
      }
    
      void _handleTap() {
        setState(() {
          _isExpanded = !_isExpanded;
          if (_isExpanded) {
            _controller.forward();
          } else {
            _controller.reverse().then<void>((void value) {
              if (!mounted) return;
              setState(() {
                // Rebuild without widget.children.
              });
            });
          }
          PageStorage.of(context)?.writeState(context, _isExpanded);
        });
        if (widget.onExpansionChanged != null)
          widget.onExpansionChanged(_isExpanded);
      }
    
      Widget _buildChildren(BuildContext context, Widget child) {
        final Color borderSideColor = _borderColor.value ?? Colors.transparent;
    
        return Container(
          decoration: BoxDecoration(
            color: _backgroundColor.value ?? Colors.transparent,
            border: Border(
              top: BorderSide(color: borderSideColor),
              bottom: BorderSide(color: borderSideColor),
            ),
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              ListTileTheme.merge(
                iconColor: _iconColor.value,
                textColor: _headerColor.value,
                child: ListTile(
                  onTap: () {
                    _isInAdvancedMode ? widget.onTap() : _handleTap();
                  }, // in AdvancedMode a callback will handle the gesture inside the calling widget
    
                  onLongPress: () {
                    _isInAdvancedMode ? widget.onLongPress() : _handleTap();
                  }, // in AdvancedMode a callback will handle the gesture inside the calling widget
                  leading: getLeading(),
                  title: widget.title,
                  trailing: getTrailing(),
                ),
              ),
              ClipRect(
                  child: Padding(
                padding: EdgeInsets.only(left: _indentListTile), // set the indent
                child: Align(
                  heightFactor: _heightFactor.value,
                  child: child,
                ),
              )),
            ],
          ),
        );
      }
    
      @override
      void didChangeDependencies() {
        final ThemeData theme = Theme.of(context);
        _borderColorTween..end = theme.dividerColor;
        _headerColorTween
          ..begin = theme.textTheme.subhead.color
          ..end = theme.accentColor;
        _iconColorTween
          ..begin = theme.unselectedWidgetColor
          ..end = theme.accentColor;
        _backgroundColorTween..end = widget.backgroundColor;
        super.didChangeDependencies();
      }
    
      @override
      Widget build(BuildContext context) {
        final bool closed = !_isExpanded && _controller.isDismissed;
        return AnimatedBuilder(
          animation: _controller.view,
          builder: _buildChildren,
          child: closed ? null : Column(children: widget.children),
        );
      }
    
      /// A method to decide what will be shown in the leading part of the lisTile
      getLeading() {
        if (_alignOpener.toString() == AlignOpener.Left.toString() &&
            _isInAdvancedMode == true) {
          return buildIcon(); //IconButton will be created
        } else if (_alignOpener.toString() == AlignOpener.Left.toString() &&
            _isInAdvancedMode == false) {
          return widget.leading ??
              RotationTransition(
                turns: _iconTurns,
                child: const Icon(Icons.expand_more),
              );
        } else {
          return widget.leading;
        }
      }
    
      /// A method to decide what will be shown in the trailing part of the lisTile
      getTrailing() {
        if (_alignOpener.toString() == AlignOpener.Right.toString() &&
            _isInAdvancedMode == true) {
          return buildIcon(); //IconButton will be created
        } else if (_alignOpener.toString() == AlignOpener.Right.toString() &&
            _isInAdvancedMode == false) {
          return widget.trailing ??
              RotationTransition(
                turns: _iconTurns,
                child: const Icon(Icons.expand_more),
              );
        } else {
          return widget.leading;
        }
      }
    
      /// A widget to build the IconButton for the leading or trailing part of the listTile
      Widget buildIcon() {
        return Container(
            child: RotationTransition(
          turns: _iconTurns,
          child: IconButton(
            icon: Icon(Icons.expand_more),
            onPressed: () {
              _handleTap();
              //toDo: open/close is working but not the animation
            },
          ),
        ));
      }
    }