Flutter ListView拖拽功能报错:hasSize is not true 求排查
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'sfeedbackwidget (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.feedbackwidget an explicit size (useContainerwith fixed dimensions orMediaQueryto 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
ReorderableListViewfirst: 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/Columnhave a parent that gives them width/height bounds.
内容的提问来源于stack exchange,提问作者Marc Hampson

