如何定制Flutter ExpandableListView实现单条目展开
Solution: Single Expanded Item in Flutter Expandable List
Hey there! The problem with your current code is that every ExpandableListView manages its own local expandFlag state independently. That's why clicking a new item doesn't collapse the others—they don't know about each other's status. Let's fix this by lifting the state up to the parent Home widget, so it can track and control which single item is expanded at any time.
Key Changes:
- Convert
Hometo a stateful widget to track the currently expanded item index - Add a callback to update the expanded index when an item is tapped
- Remove the local
expandFlagfromExpandableListViewand use the parent's state to determine expansion status - Pass the expansion state and toggle callback to each child
ExpandableListView
Here's the modified full code:
import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; void main() { runApp(new MaterialApp(home: new Home())); } // Modified: Changed to StatefulWidget to manage global expansion state class Home extends StatefulWidget { @override _HomeState createState() => _HomeState(); } class _HomeState extends State<Home> { // Added: Track which index is expanded (-1 means no items are open) int _expandedIndex = -1; // Added: Callback to update expanded index when an item is tapped void _toggleExpansion(int index) { setState(() { // If tapping the already expanded item, collapse it; else expand it _expandedIndex = _expandedIndex == index ? -1 : index; }); } @override Widget build(BuildContext context) { return new Scaffold( backgroundColor: Colors.grey, appBar: new AppBar( title: new Text("Expandable List"), backgroundColor: Colors.redAccent, ), body: new ListView.builder( itemBuilder: (BuildContext context, int index) { // Modified: Pass index, expansion state, and toggle callback to child return new ExpandableListView( title: "Title $index", isExpanded: _expandedIndex == index, onToggle: () => _toggleExpansion(index), ); }, itemCount: 5, ), ); } } // Modified: Changed to StatelessWidget since state is managed by parent class ExpandableListView extends StatelessWidget { final String title; final bool isExpanded; // Added: Receive expansion state from parent final VoidCallback onToggle; // Added: Receive toggle action from parent const ExpandableListView({ Key key, this.title, this.isExpanded, this.onToggle, }) : super(key: key); @override Widget build(BuildContext context) { return new Container( margin: new EdgeInsets.symmetric(vertical: 1.0), child: new Column( children: <Widget>[ new Container( color: Colors.blue, padding: new EdgeInsets.symmetric(horizontal: 5.0), child: new Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ new IconButton( icon: new Container( height: 50.0, width: 50.0, decoration: new BoxDecoration( color: Colors.orange, shape: BoxShape.circle, ), child: new Center( child: new Icon( // Modified: Use parent-provided state to set icon isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, color: Colors.white, size: 30.0, ), ), ), onPressed: onToggle, // Modified: Trigger parent's toggle callback ), new Text( title, style: new TextStyle(fontWeight: FontWeight.bold, color: Colors.white), ) ], ), ), new ExpandableContainer( // Modified: Use parent-provided state to control expansion expanded: isExpanded, child: new ListView.builder( itemBuilder: (BuildContext context, int index) { return new Container( decoration: new BoxDecoration(border: new Border.all(width: 1.0, color: Colors.grey), color: Colors.black), child: new ListTile( title: new Text( "Cool $index", style: new TextStyle(fontWeight: FontWeight.bold, color: Colors.white), ), leading: new Icon( Icons.local_pizza, color: Colors.white, ), ), ); }, itemCount: 15, )) ], ), ); } } class ExpandableContainer extends StatelessWidget { final bool expanded; final double collapsedHeight; final double expandedHeight; final Widget child; ExpandableContainer({ @required this.child, this.collapsedHeight = 0.0, this.expandedHeight = 300.0, this.expanded = true, }); @override Widget build(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; return new AnimatedContainer( duration: new Duration(milliseconds: 500), curve: Curves.easeInOut, width: screenWidth, height: expanded ? expandedHeight : collapsedHeight, child: new Container( child: child, decoration: new BoxDecoration(border: new Border.all(width: 1.0, color: Colors.blue)), ), ); } }
How It Works:
- The
_expandedIndexvariable inHomeacts as a single source of truth for which item is expanded. - When you tap an item, the
_toggleExpansioncallback updates this index: if the tapped item was already open, it collapses it; otherwise, it sets that index as the active expanded item (automatically closing any previously open item). - Each
ExpandableListViewnow relies on the parent's state instead of its own, ensuring all items stay in sync.
内容的提问来源于stack exchange,提问作者Mohsen Emami
相关产品推荐
相关产品推荐

