flutter/examples/api/lib/widgets/sliver/sliver_tree.1.dart
Kate Lovett b50eb97b7d
TreeSliver & associated classes (#147171)
**FYI for Reviewers:** Much of the API surface matches that of the 2D TreeView in https://github.com/flutter/packages/pull/6592. If it changes here, it should change there, and vice versa.

📜  [Design Document](https://docs.google.com/document/d/1-aFI7VjkF9yMkWpP94J8T_JREDS-M3bOak26PVehUYg/edit?usp=sharing)

This adds classes and associated callbacks and controllers for TreeSliver. Core components:
- TreeSliver
- RenderTreeSliver
- TreeSliverNode
- TreeSliverController
- TreeSliverStateMixin
- TreeSliverIndentationType

Fixes https://github.com/flutter/flutter/issues/114299

https://github.com/flutter/flutter/assets/16964204/3facd095-7262-4068-aa33-d713e2deca99

https://github.com/flutter/flutter/assets/16964204/f851ae30-8e71-45c7-82a4-9606986a5872
2024-06-04 22:50:06 +00:00

190 lines
5.5 KiB
Dart

// Copyright 2014 The Flutter 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/material.dart';
import 'package:flutter/rendering.dart';
/// Flutter code sample for [TreeSliver].
void main() => runApp(const TreeSliverExampleApp());
class TreeSliverExampleApp extends StatelessWidget {
const TreeSliverExampleApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: TreeSliverExample(),
);
}
}
class TreeSliverExample extends StatefulWidget {
const TreeSliverExample({super.key});
@override
State<TreeSliverExample> createState() => _TreeSliverExampleState();
}
class _TreeSliverExampleState extends State<TreeSliverExample> {
TreeSliverNode<String>? _selectedNode;
final List<TreeSliverNode<String>> tree = <TreeSliverNode<String>>[
TreeSliverNode<String>('README.md'),
TreeSliverNode<String>('analysis_options.yaml'),
TreeSliverNode<String>(
'lib',
children: <TreeSliverNode<String>>[
TreeSliverNode<String>(
'src',
children: <TreeSliverNode<String>>[
TreeSliverNode<String>(
'widgets',
children: <TreeSliverNode<String>>[
TreeSliverNode<String>('about.dart.dart'),
TreeSliverNode<String>('app.dart'),
TreeSliverNode<String>('basic.dart'),
TreeSliverNode<String>('constants.dart'),
],
),
],
),
TreeSliverNode<String>('widgets.dart'),
],
),
TreeSliverNode<String>('pubspec.lock'),
TreeSliverNode<String>('pubspec.yaml'),
TreeSliverNode<String>(
'test',
children: <TreeSliverNode<String>>[
TreeSliverNode<String>(
'widgets',
children: <TreeSliverNode<String>>[
TreeSliverNode<String>('about_test.dart'),
TreeSliverNode<String>('app_test.dart'),
TreeSliverNode<String>('basic_test.dart'),
TreeSliverNode<String>('constants_test.dart'),
],
),
],
),
];
Widget _treeNodeBuilder(
BuildContext context,
TreeSliverNode<Object?> node,
AnimationStyle toggleAnimationStyle,
) {
final bool isParentNode = node.children.isNotEmpty;
final BorderSide border = BorderSide(
width: 2,
color: Colors.purple[300]!,
);
return TreeSliver.wrapChildToToggleNode(
node: node,
child: Row(
children: <Widget>[
// Custom indentation
SizedBox(width: 10.0 * node.depth! + 8.0),
DecoratedBox(
decoration: BoxDecoration(
border: node.parent != null
? Border(left: border, bottom: border)
: null,
),
child: const SizedBox(height: 50.0, width: 20.0),
),
// Leading icon for parent nodes
if (isParentNode)
DecoratedBox(
decoration: BoxDecoration(border: Border.all()),
child: SizedBox.square(
dimension: 20.0,
child: Icon(
node.isExpanded ? Icons.remove : Icons.add,
size: 14,
),
),
),
// Spacer
const SizedBox(width: 8.0),
// Content
Text(node.content.toString()),
],
),
);
}
Widget _getTree() {
return DecoratedSliver(
decoration: BoxDecoration( border: Border.all()),
sliver: TreeSliver<String>(
tree: tree,
onNodeToggle: (TreeSliverNode<Object?> node) {
setState(() {
_selectedNode = node as TreeSliverNode<String>;
});
},
treeNodeBuilder: _treeNodeBuilder,
treeRowExtentBuilder: (
TreeSliverNode<Object?> node,
SliverLayoutDimensions layoutDimensions,
) {
// This gives more space to parent nodes.
return node.children.isNotEmpty ? 60.0 : 50.0;
},
// No internal indentation, the custom treeNodeBuilder applies its
// own indentation to decorate in the indented space.
indentation: TreeSliverIndentationType.none,
),
);
}
@override
Widget build(BuildContext context) {
// This example is assumes the full screen is available.
final Size screenSize = MediaQuery.sizeOf(context);
final List<Widget> selectedChildren = <Widget>[];
if (_selectedNode != null) {
selectedChildren.addAll(<Widget>[
const Spacer(),
Icon(
_selectedNode!.children.isEmpty
? Icons.file_open_outlined
: Icons.folder_outlined,
),
const SizedBox(height: 16.0),
Text(_selectedNode!.content),
const Spacer(),
]);
}
return Scaffold(
body: Row(children: <Widget>[
SizedBox(
width: screenSize.width / 2,
height: double.infinity,
child: CustomScrollView(
slivers: <Widget>[
_getTree(),
],
),
),
DecoratedBox(
decoration: BoxDecoration(
border: Border.all(),
),
child: SizedBox(
width: screenSize.width / 2,
height: double.infinity,
child: Center(
child: Column(
children: selectedChildren,
),
),
),
),
]),
);
}
}