如何在Flutter中实现嵌套视图及多场景预订确认页(指定组件)
Hey Daniel, let's build that booking confirmation page you need—covering all those scenarios (one-way/round-trip, mixed services or single service) using the exact components you specified. I'll walk you through a complete implementation that leverages your mock data and hits all requirements.
1. Core Approach
We'll structure the page like this:
- Use
CustomScrollViewas the main container to support smooth scrolling with sliver components. - For each service type (Flight, Train, etc.), create a section with a header, then use
SliverListto render individual trips (one-way or round-trip). - Add
StatefulBuilderfor any local state changes (like expanding/collapsing trip details, which adds a nice interactive touch). - Use
ListTileto display each trip's origin, destination, time, and provider clearly.
2. Complete Implementation
First, keep your existing mock data classes, then add the widget implementation:
import 'package:flutter/material.dart'; // Your existing mock data classes class SummaryData { String serviceName; // flight, train List<SingleService> lisOfService; SummaryData(this.serviceName, this.lisOfService); } class SingleService { var origin, destination; var takeOfTime; var serviceProvider; SingleService(this.origin, this.destination, this.takeOfTime, this.serviceProvider); } class SummaryDataRepository { List<SummaryData> listOfSummaryData; SummaryDataRepository() { listOfSummaryData = []; // Example: Round-trip flight + one-way train listOfSummaryData.add(SummaryData( "Flight", [ SingleService("London", "Rome", "11:00", "AirX"), SingleService("Rome", "London", "16:30", "AirX"), // Round-trip return ], )); listOfSummaryData.add(SummaryData( "Train", [SingleService("London", "Heathrow", "10:00", "TrainX")], )); // Uncomment below to test single-service scenario // listOfSummaryData = [SummaryData("Flight", [SingleService("Paris", "Berlin", "09:45", "AirY")])]; } List<SummaryData> getSummaryData() { return listOfSummaryData; } } // Booking Confirmation Page Widget class BookingConfirmationPage extends StatefulWidget { const BookingConfirmationPage({super.key}); @override State<BookingConfirmationPage> createState() => _BookingConfirmationPageState(); } class _BookingConfirmationPageState extends State<BookingConfirmationPage> { final SummaryDataRepository _repo = SummaryDataRepository(); late List<SummaryData> _bookingData; @override void initState() { super.initState(); _bookingData = _repo.getSummaryData(); } @override Widget build(BuildContext context) { return Scaffold( body: CustomScrollView( slivers: [ // Sliver App Bar for top header const SliverAppBar( title: Text("Booking Confirmation"), pinned: true, backgroundColor: Colors.blueAccent, ), // Main content: List of service sections SliverList( delegate: SliverChildBuilderDelegate( (context, index) { final service = _bookingData[index]; return _buildServiceSection(service); }, childCount: _bookingData.length, ), ), ], ), ); } Widget _buildServiceSection(SummaryData service) { // Use StatefulBuilder to handle local expand/collapse state return StatefulBuilder( builder: (context, setState) { bool isExpanded = true; // Default to expanded, you can toggle this return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Card( elevation: 2, child: Column( children: [ // Service Header (tap to expand/collapse) ListTile( title: Text( service.serviceName, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), subtitle: Text( service.lisOfService.length == 1 ? "One-way" : "Round-trip", style: const TextStyle(color: Colors.grey), ), trailing: IconButton( icon: Icon(isExpanded ? Icons.expand_less : Icons.expand_more), onPressed: () => setState(() => isExpanded = !isExpanded), ), ), // Trip Details (show/hide based on expand state) if (isExpanded) Column( children: service.lisOfService.map((trip) { final tripIndex = service.lisOfService.indexOf(trip); return ListTile( leading: CircleAvatar( child: Text(tripIndex == 0 ? "✈️" : "🔙"), // Icon for departure/return ), title: Text("${trip.origin} → ${trip.destination}"), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Departure: ${trip.takeOfTime}"), Text("Provider: ${trip.serviceProvider}"), ], ), ); }).toList(), ), ], ), ), ); }, ); } } // To use the page, add it to your app's navigation: // Navigator.push(context, MaterialPageRoute(builder: (context) => const BookingConfirmationPage()));
3. Key Component Breakdown
Let's go over how this handles all your requirements:
- CustomScrollView: Provides a flexible scrolling container that works seamlessly with sliver components (like
SliverAppBarandSliverList), ensuring smooth performance even with long lists of bookings. - StatefulBuilder: Used inside each service section to manage the expand/collapse state locally—this means tapping to expand one service won't trigger a full page rebuild, which is efficient.
- SliverList: Renders each service section as a sliver, which integrates perfectly with
CustomScrollViewfor native-like scrolling behavior. - ListTile: Used twice—once for the service header (with expand/collapse toggle) and once for each trip's details, making the UI clean and consistent.
Handling All Scenarios
- One-way vs Round-trip: We check the length of
lisOfService—if it's 1, we show "One-way"; if it's 2, "Round-trip". The trip details will automatically render all entries (so you can add more than 2 if needed, like multi-leg trips). - Single or Multiple Services: The
SliverListdynamically renders all service entries from your repository. If you only have a flight in the data, only the flight section will show; if you have flight + train, both sections appear.
You can tweak the UI (colors, icons, text styles) to match your app's design system, but this implementation covers all your core requirements.
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

