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

如何在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 CustomScrollView as 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 SliverList to render individual trips (one-way or round-trip).
  • Add StatefulBuilder for any local state changes (like expanding/collapsing trip details, which adds a nice interactive touch).
  • Use ListTile to 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 SliverAppBar and SliverList), 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 CustomScrollView for 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 SliverList dynamically 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:21:15