flutter_folderview 0.11.2 copy "flutter_folderview: ^0.11.2" to clipboard
flutter_folderview: ^0.11.2 copied to clipboard

A customizable Flutter widget for displaying hierarchical data in tree and folder views with rich theming support.

Flutter FolderView #

A customizable Flutter widget for displaying hierarchical data in tree and folder views.

Installation #

dependencies:
  flutter_folderview: ^0.11.2

Requires Flutter 3.13.0 (Dart 3.1.0) or newer, raised from 3.10.0 in 0.11.0.

Basic Usage #

import 'package:flutter_folderview/flutter_folderview.dart';

class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  final Set<String> _expandedIds = {};

  @override
  Widget build(BuildContext context) {
    return FolderView(
      data: [
        Node(
          id: '1',
          label: 'Documents',
          type: NodeType.folder,
          children: [
            Node(
              id: '2',
              label: 'Work',
              type: NodeType.parent,
              children: [
                Node(id: '3', label: 'Report.pdf', type: NodeType.child),
              ],
            ),
          ],
        ),
      ],
      expandedNodeIds: _expandedIds,
      mode: ViewMode.folder,
      onNodeTap: (node) {
        if (node.type != NodeType.child) {
          setState(() {
            if (_expandedIds.contains(node.id)) {
              _expandedIds.remove(node.id);
            } else {
              _expandedIds.add(node.id);
            }
          });
        }
      },
    );
  }
}

Node Types #

  • NodeType.folder: Top-level container nodes
  • NodeType.parent: Mid-level nodes that can have children
  • NodeType.child: Leaf nodes

View Modes #

  • ViewMode.folder: Hierarchical folder structure (folder → parent → child)
  • ViewMode.tree: Flattened tree structure (parent → child)

Line Styles #

theme: FlutterFolderViewTheme(
  lineTheme: FolderViewLineTheme(
    lineStyle: LineStyle.connector, // ├─ └─ connectors
    // lineStyle: LineStyle.scope,   // Vertical scope lines
    // lineStyle: LineStyle.none,    // No lines
  ),
)

Theming #

FolderView(
  data: nodes,
  expandedNodeIds: expandedIds,
  theme: FlutterFolderViewTheme(
    folderTheme: FolderNodeTheme(
      widget: Icon(Icons.folder),
      openWidget: Icon(Icons.folder_open),
      textStyle: TextStyle(fontSize: 14),
    ),
    parentTheme: ParentNodeTheme(
      widget: Icon(Icons.description),
      textStyle: TextStyle(fontSize: 14),
    ),
    childTheme: ChildNodeTheme(
      widget: Icon(Icons.insert_drive_file),
      textStyle: TextStyle(fontSize: 14),
      selectedTextStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
    ),
  ),
)

Scale #

Scale all content proportionally. Scrollbars and tooltips are not affected — they are treated as chrome (interactive overlays sized for input devices, not for content density).

FolderView(
  data: nodes,
  mode: ViewMode.folder,
  scale: 1.5, // default 1.0
  expandedNodeIds: expandedIds,
)
Scaled Not Scaled
Row height, row spacing Scrollbar thickness, track width
Icon sizes, padding, margin Scrollbar colors
Text fontSize, letterSpacing Tooltip dimensions (padding, arrow, offset, text)
Line width, indentation Animation durations, click intervals
Content padding, border radius Colors

Each theme class also exposes a scale() method directly — useful for callers who need a scaled copy of a theme outside FolderView (custom previews, integration tests, etc.):

final scaledTheme = myTheme.scaledForContext(context, 1.5);
// or, without a BuildContext:
final scaledTheme = myTheme.scale(factor: 1.5, defaultFontSize: 14.0);

Ctrl (Windows/Linux) or Cmd (macOS) + scroll wheel is blocked by default (blockModifierScroll: true) so zoom-by-scroll works without unintended scrolling. Set blockModifierScroll: false to allow normal scrolling while the modifier key is held.

The library also exports isScaleModifierPressed() — a platform-aware helper that returns true when Ctrl (Windows/Linux) or Cmd (macOS) is pressed. Use it to implement custom zoom logic:

Listener(
  onPointerSignal: (event) {
    if (event is PointerScrollEvent && isScaleModifierPressed()) {
      final delta = event.scrollDelta.dy > 0 ? -0.1 : 0.1;
      setState(() => _scale = (_scale + delta).clamp(0.5, 3.0));
    }
  },
  child: FolderView(
    data: nodes,
    mode: ViewMode.folder,
    scale: _scale,
    expandedNodeIds: expandedIds,
  ),
)
FolderView(
  data: nodes,
  mode: ViewMode.folder,
  scale: 1.5,
  blockModifierScroll: true, // default: true
  expandedNodeIds: expandedIds,
)

Tooltip #

There are two, and they differ by where you declare them and what they explain.

Declared on Styled by Attaches to Explains
Label tooltip NodeTooltipTheme, per node type the same object The label's glyphs The label — hover truncated text to read the rest
Row tooltip FolderView.rowTooltipBuilder, once FolderView.rowTooltipTheme The rest of the row The node — a card of its details

Enable both. Only one is ever visible — the innermost under the pointer — and the label tooltip claims exactly the glyphs it explains, so the row card gets the indent, the icon, the expand chevron, and the space beside a short label. See Row tooltip.

Label tooltip #

Each node type supports tooltip via NodeTooltipTheme:

FolderView(
  data: nodes,
  expandedNodeIds: expandedIds,
  theme: FlutterFolderViewTheme(
    folderTheme: FolderNodeTheme(
      tooltipTheme: NodeTooltipTheme(
        useTooltip: true,
        message: 'Folder node',
        direction: TooltipDirection.top,
        alignment: TooltipAlignment.center,
        interactive: true,
        waitDuration: Duration(milliseconds: 500),
        showDuration: Duration(seconds: 3),
        boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8)],
      ),
    ),
  ),
)
Property Type Description
useTooltip bool Enable tooltip (default: false)
message String? Static tooltip text
tooltipBuilder WidgetBuilder? Custom tooltip widget
tooltipBuilderResolver Function? Node-specific tooltip widget resolver
direction TooltipDirection Position: top, bottom, left, right
alignment TooltipAlignment Alignment: start, center, end, startTargetCenter, endTargetCenter
anchor TooltipAnchor Anchor the tooltip to the row's rect or the cursor: child (default), pointer
offset double Distance from widget (default: 8.0)
crossAxisOffset double Cross-axis offset (default: 0.0)
backgroundColor Color? Background color
elevation double? Shadow depth
boxShadow List<BoxShadow>? Custom shadow (overrides elevation)
borderRadius BorderRadius? Corner rounding
padding EdgeInsets? Inner padding
enableTap bool? Tap to show (default: false)
enableHover bool? Hover to show (default: true)
interactive bool? Keep visible on tooltip hover
waitDuration Duration? Delay before showing on hover
showDuration Duration? Auto-hide after duration (resets on re-enter)
animationDuration Duration? Show/hide animation duration
controller JustTooltipController? Programmatic show/hide control
onShow VoidCallback? Callback when shown
onHide VoidCallback? Callback when hidden
showArrow bool? Show arrow on tooltip (default: false)
arrowBaseWidth double? Arrow base width (default: 12.0)
arrowLength double? Arrow length (default: 6.0)
arrowPositionRatio double? Arrow position ratio 0.0~1.0 (default: 0.25)
borderColor Color? Tooltip border color
borderWidth double? Tooltip border width (default: 0.0)
screenMargin double? Min margin from screen edges (default: 8.0)
animation TooltipAnimation? Animation type: none, fade, scale, slide, fadeScale, fadeSlide, rotation
animationCurve Curve? Custom curve for animation
fadeBegin double? Starting opacity for fade animations (default: 0.0)
scaleBegin double? Starting scale for scale animations (default: 0.0)
slideOffset double? Slide distance as fraction of tooltip size (default: 0.3)
rotationBegin double? Starting rotation in turns (default: -0.05)
hideOnEmptyMessage bool? Suppress tooltip when message is empty (default: true)

Anchoring to the cursor #

A node's tooltip attaches to its icon-and-label content, not to the whole rendered row. But a row is as wide as the tree's longest label, and each label grows to fill its row — so a long label's rect spans the row, and a tooltip anchored to that rect appears at the row's centre, far from where the user is actually pointing. TooltipAnchor.pointer keeps the same hover region but places the tooltip at the cursor:

NodeTooltipTheme(
  useTooltip: true,
  message: 'Report.pdf',
  anchor: TooltipAnchor.pointer, // default: TooltipAnchor.child
)

The anchor is captured when the tooltip is shown and does not follow the pointer, so interactive tooltips stay reachable. Tap-triggered tooltips anchor at the tap, and a controller-driven show with no pointer present falls back to the label's rect.

Against a point there are no target edges to align to, so under TooltipAnchor.pointer the alignment field selects which of the tooltip's own edges lands on the pointer.

Note that anchor does not widen the hover region. A short label occupies only the left part of its row, and the space to its right raises no tooltip under either anchor. To make the whole row hoverable, use the row tooltip below.

When a tree is wide enough to scroll horizontally, a long label's rect extends past the visible FolderView. TooltipAnchor.child aims at the centre of the part still on screen, so the tooltip stays inside the view.

Row tooltip #

rowTooltipBuilder returns a card shown while the pointer is anywhere over a node's row — the indent, the expand chevron, the empty space beside a short label. Return null for a node that should not have one.

FolderView(
  data: nodes,
  expandedNodeIds: expandedIds,
  rowTooltipBuilder: (context, node) {
    if (node.type == NodeType.folder) return null;
    return Card(child: Padding(
      padding: const EdgeInsets.all(12),
      child: Text('${node.label} — ${node.children.length} children'),
    ));
  },
)

The card supplies its own surface, so by default the tooltip around it draws no background, padding, or elevation. Give it a Card, not a bare Text.

It is anchored at the pointer, and that is the one knob you cannot change. A row is laid out at the tree's content width rather than the viewport's, so anchoring to the row's rect would aim at a centre that leaves the screen the moment the view scrolls horizontally.

Styling the card #

Everything else is rowTooltipTheme:

FolderView(
  data: nodes,
  expandedNodeIds: expandedIds,
  rowTooltipBuilder: (context, node) => Card(child: Text(node.label)),
  rowTooltipTheme: RowTooltipTheme(
    waitDuration: Duration(milliseconds: 400), // don't flash on a quick sweep
    interactive: true,                         // default: reach into the card
    direction: TooltipDirection.bottom,
    offset: 12,
  ),
)
Property Type Description
interactive bool Card stays while the cursor is over it (default: true)
waitDuration Duration? Delay before showing. Null shows immediately
showDuration Duration? Auto-hide after this long
enableHover bool Whether hovering raises the card at all (default: true)
direction TooltipDirection Side of the pointer; flips when there is no room
alignment TooltipAlignment Which of the card's own edges lands on the pointer
offset double Distance from the pointer (default: 8.0)
crossAxisOffset double Offset along the cross-axis of direction
screenMargin double Minimum distance from the Overlay's edges
surface JustTooltipTheme The tooltip's own chrome. Defaults to JustTooltipTheme.bare() — nothing drawn
animation, animationCurve, animationDuration Show/hide animation
fadeBegin, scaleBegin, slideOffset, rotationBegin double Animation start values
onShow, onHide VoidCallback? Shown / hidden callbacks

If your builder returns unadorned content rather than a Card, give surface a real JustTooltipTheme and let the tooltip draw the box.

Enabling both #

Enable both. They divide the row between them.

Only one tooltip is visible at a time — the innermost under the pointer — and the label tooltip claims exactly the glyphs it explains. Hover the label text and you get the label tooltip; hover anywhere else on the row (the indent, the icon, the expand chevron, the space beside a short label) and you get the row card.

The one place the card cannot appear is over the glyphs themselves. On the tree's widest row the label runs the length of the row, so there the card is reachable over the indent and the icon only. That is the geometry, not a setting.

Example #

See example/ for complete examples including:

  • Theme customization
  • Dynamic styling with resolver functions
  • Data generation for testing
cd example
flutter run
1
likes
160
points
1.11k
downloads

Documentation

API reference

Publisher

verified publisherkihyun1998.com

Weekly Downloads

A customizable Flutter widget for displaying hierarchical data in tree and folder views with rich theming support.

Repository (GitHub)
View/report issues

Topics

#folderview #tree #folder #treeview #explorer

License

MIT (license)

Dependencies

flutter, just_tooltip

More

Packages that depend on flutter_folderview