You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter ListView拖拽功能报错:hasSize is not true 求排查

Fixing the 'hasSize' Error in Your Draggable ListView

Hey there! Let's break down what's causing that frustrating 'hasSize' assertion error and get your ListView drag-and-drop working properly.

Why You're Seeing This Error

The 'hasSize': is not true error almost always means a widget in your tree doesn't have a defined size constraint. In drag-and-drop scenarios, this usually happens:

  • When your Draggable's feedback widget (the one that shows up while dragging) isn't given an explicit size.
  • Or when a parent widget (like an unconstrained Row/Column) isn't providing enough layout rules for its children to calculate their size.

The Best Fix: Use ReorderableListView (Official & Reliable)

Flutter has a built-in widget specifically for draggable, reorderable lists that handles all the layout constraints and logic for you—no more hasSize headaches. Here's a complete replacement for your main.dart:

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Draggable List Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const DraggableListScreen(),
    );
  }
}

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

  @override
  State<DraggableListScreen> createState() => _DraggableListScreenState();
}

class _DraggableListScreenState extends State<DraggableListScreen> {
  final List<String> _items = List.generate(10, (index) => 'Item ${index + 1}');

  void _reorderItems(int oldIndex, int newIndex) {
    setState(() {
      // Adjust index if dropping after the original position
      if (newIndex > oldIndex) newIndex -= 1;
      final item = _items.removeAt(oldIndex);
      _items.insert(newIndex, item);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Draggable List')),
      body: ReorderableListView(
        onReorder: _reorderItems,
        padding: const EdgeInsets.symmetric(vertical: 16),
        children: _items.map((item) {
          return ListTile(
            key: Key(item), // Required for ReorderableListView
            title: Text(item),
            trailing: const Icon(Icons.drag_handle),
          );
        }).toList(),
      ),
    );
  }
}

If You Need Custom Drag Logic (Draggable + DragTarget)

If you're building something more custom than basic reordering, here's how to fix the hasSize error in a manual setup:

  • Always give your Draggable.feedback widget an explicit size (use Container with fixed dimensions or MediaQuery to match screen width).
  • Ensure every list item has a constrained layout (avoid unbound widgets that can't calculate their own size).

Here's a working custom implementation snippet you can adapt:

// Inside your screen's build method, replace ReorderableListView with this:
body: ListView.builder(
  itemCount: _items.length,
  itemBuilder: (context, index) {
    return _DraggableListItem(
      item: _items[index],
      index: index,
      onDragComplete: (oldIndex, newIndex) => _reorderItems(oldIndex, newIndex),
    );
  },
),

And the custom list item component:

class _DraggableListItem extends StatefulWidget {
  final String item;
  final int index;
  final Function(int oldIndex, int newIndex) onDragComplete;

  const _DraggableListItem({
    required this.item,
    required this.index,
    required this.onDragComplete,
  });

  @override
  State<_DraggableListItem> createState() => _DraggableListItemState();
}

class _DraggableListItemState extends State<_DraggableListItem> {
  late final Color _itemColor = Colors.primaries[widget.index % Colors.primaries.length];

  @override
  Widget build(BuildContext context) {
    return DragTarget<int>(
      onAcceptWithDetails: (details) {
        if (details.data != widget.index) {
          widget.onDragComplete(details.data, widget.index);
        }
      },
      builder: (context, candidateData, rejectedData) {
        return Draggable<int>(
          data: widget.index,
          // Feedback has explicit size to avoid hasSize error
          feedback: Material(
            elevation: 4,
            child: Container(
              width: MediaQuery.of(context).size.width,
              padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
              color: _itemColor.withOpacity(0.8),
              child: Text(widget.item, style: const TextStyle(color: Colors.white)),
            ),
          ),
          childWhenDragging: Container(
            padding: const EdgeInsets.symmetric(vertical: 16),
            color: Colors.grey[200],
            child: const Center(child: Text('Placeholder')),
          ),
          child: Container(
            padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
            color: _itemColor,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(widget.item, style: const TextStyle(color: Colors.white, fontSize: 16)),
                const Icon(Icons.drag_indicator, color: Colors.white),
              ],
            ),
          ),
        );
      },
    );
  }
}

Key Takeaways

  • Use ReorderableListView first: It's the simplest, most maintainable solution for basic drag-and-drop reordering.
  • Explicit sizes are non-negotiable: Any widget involved in drag operations (especially feedback) needs a clear size constraint.
  • Avoid unconstrained parents: Make sure widgets like Row/Column have a parent that gives them width/height bounds.

内容的提问来源于stack exchange,提问作者Marc Hampson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:26:42