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

Flutter Google Maps自定义信息窗口多页面左右滑动实现问询

为Google Maps自定义信息窗口添加多页面滑动功能

问题描述

我们正在开发一款美食园区定位应用,需求为点击Google Maps标记后,弹出可左右滑动切换多页面的自定义信息窗口。目前已实现自定义标记,但不知如何为CustomInfoWindow添加多页面滑动功能,现有代码如下:

import 'package:custom_info_window/custom_info_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

const LatLng customLocation = LatLng(14.852885352588201, 120.81614049713285);

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late GoogleMapController _mapController;
  final Map<String, Marker> _markers = {};
  final _customInfoWindowController = CustomInfoWindowController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          GoogleMap(
            initialCameraPosition:
                const CameraPosition(target: customLocation, zoom: 11.5),
            onMapCreated: (controller) {
              _mapController = controller;
              _customInfoWindowController.googleMapController = controller;

              addMarker5('test5',
                  const LatLng(14.896344082681443, 120.77674251390367));
            },
            onTap: (location) {
              _customInfoWindowController.hideInfoWindow!();
            },
            onCameraMove: (position) {
              _customInfoWindowController.onCameraMove!();
            },
            markers: _markers.values.toSet(),
          ),
          CustomInfoWindow(
            controller: _customInfoWindowController,
            height: 245,
            width: 220,
            offset: 50,
          )
        ],
      ),
    );
  }

  addMarker5(String markerid4, LatLng location) async {
    var markerIcon = await BitmapDescriptor.fromAssetImage(
        const ImageConfiguration(), 'assets/images/sanmarcosfoodpark.png');
    var marker = Marker(
        markerId: MarkerId(markerid4),
        position: location,
        icon: markerIcon,
        onTap: () {
          _customInfoWindowController.addInfoWindow!(
            Container(
              clipBehavior: Clip.antiAlias,
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(24),
              ),
              child: Column(
                children: [
                  Image.asset(
                    'assets/images/sanmarcoswindow.jpg',
                    fit: BoxFit.cover,
                  ),
                  const Text(
                    'San Marcos Foodpark',
                    style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
                  ),
                  const Text(
                    'Address:',
                    textAlign: TextAlign.right,
                    style: TextStyle(
                      fontSize: 12,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const Padding(
                    padding: EdgeInsets.only(left: 8.0, right: 8.0),
                    child: Text(
                      '13, R-9, Calumpit, Bulacan',
                      style: TextStyle(fontSize: 11),
                    ),
                  ),
                  const SizedBox(
                    height: 5.0,
                  ),
                  const Text('Contact no: ',
                      style:
                          TextStyle(fontSize: 12, fontWeight: FontWeight.bold)),
                  const Text('',
                      style: TextStyle(
                        fontSize: 11,
                      )),
                ],
              ),
            ),
            location,
          );
        });

    _markers[markerid4] = marker;
    setState(() {});
  }
}

解决方案

通过PageView组件可以轻松实现自定义信息窗口的多页面滑动功能,以下是修改后的完整代码:

import 'package:custom_info_window/custom_info_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

const LatLng customLocation = LatLng(14.852885352588201, 120.81614049713285);

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late GoogleMapController _mapController;
  final Map<String, Marker> _markers = {};
  final _customInfoWindowController = CustomInfoWindowController();
  // 新增PageController管理页面切换
  final PageController _pageController = PageController(initialPage: 0);
  // 跟踪当前页面索引,用于指示器
  int _currentPage = 0;

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          GoogleMap(
            initialCameraPosition:
                const CameraPosition(target: customLocation, zoom: 11.5),
            onMapCreated: (controller) {
              _mapController = controller;
              _customInfoWindowController.googleMapController = controller;

              addMarker5('test5',
                  const LatLng(14.896344082681443, 120.77674251390367));
            },
            onTap: (location) {
              _customInfoWindowController.hideInfoWindow!();
            },
            onCameraMove: (position) {
              _customInfoWindowController.onCameraMove!();
            },
            markers: _markers.values.toSet(),
          ),
          CustomInfoWindow(
            controller: _customInfoWindowController,
            height: 245,
            width: 220,
            offset: 50,
          )
        ],
      ),
    );
  }

  addMarker5(String markerid4, LatLng location) async {
    var markerIcon = await BitmapDescriptor.fromAssetImage(
        const ImageConfiguration(), 'assets/images/sanmarcosfoodpark.png');
    var marker = Marker(
        markerId: MarkerId(markerid4),
        position: location,
        icon: markerIcon,
        onTap: () {
          _customInfoWindowController.addInfoWindow!(
            Container(
              clipBehavior: Clip.antiAlias,
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(24),
              ),
              child: Column(
                children: [
                  // 用PageView替换原有的单一内容,实现多页面滑动
                  Expanded(
                    child: PageView(
                      controller: _pageController,
                      onPageChanged: (index) {
                        setState(() {
                          _currentPage = index;
                        });
                      },
                      children: [
                        // 页面1:基础信息
                        _buildInfoPage(),
                        // 页面2:餐厅评分与评价
                        _buildRatingPage(),
                        // 页面3:菜单预览
                        _buildMenuPage(),
                      ],
                    ),
                  ),
                  // 添加页面指示器
                  Padding(
                    padding: const EdgeInsets.symmetric(vertical: 8.0),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: List.generate(3, (index) {
                        return Container(
                          width: 8,
                          height: 8,
                          margin: const EdgeInsets.symmetric(horizontal: 3),
                          decoration: BoxDecoration(
                            shape: BoxShape.circle,
                            color: _currentPage == index
                                ? Colors.orange
                                : Colors.grey.shade300,
                          ),
                        );
                      }),
                    ),
                  )
                ],
              ),
            ),
            location,
          );
        });

    _markers[markerid4] = marker;
    setState(() {});
  }

  // 构建基础信息页面
  Widget _buildInfoPage() {
    return Column(
      children: [
        Image.asset(
          'assets/images/sanmarcoswindow.jpg',
          fit: BoxFit.cover,
          height: 120,
          width: double.infinity,
        ),
        const SizedBox(height: 8),
        const Text(
          'San Marcos Foodpark',
          style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 4),
        const Text(
          'Address:',
          style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
        ),
        const Padding(
          padding: EdgeInsets.symmetric(horizontal: 8.0),
          child: Text(
            '13, R-9, Calumpit, Bulacan',
            style: TextStyle(fontSize: 11),
            textAlign: TextAlign.center,
          ),
        ),
        const SizedBox(height: 4),
        const Text('Contact no: ',
            style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold)),
        const Text('(044) 123-4567', style: TextStyle(fontSize: 11)),
      ],
    );
  }

  // 构建评分页面
  Widget _buildRatingPage() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        const Text(
          '用户评分',
          style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 10),
        RatingBar.builder(
          initialRating: 4.5,
          minRating: 1,
          direction: Axis.horizontal,
          allowHalfRating: true,
          itemCount: 5,
          itemSize: 20,
          itemPadding: const EdgeInsets.symmetric(horizontal: 4.0),
          itemBuilder: (context, _) => const Icon(
            Icons.star,
            color: Colors.orange,
          ),
          onRatingUpdate: (rating) {},
        ),
        const SizedBox(height: 10),
        const Text(
          '基于24条用户评价',
          style: TextStyle(fontSize: 11, color: Colors.grey),
        ),
        const SizedBox(height: 8),
        const Padding(
          padding: EdgeInsets.symmetric(horizontal: 16.0),
          child: Text(
            '食物新鲜美味,园区环境干净舒适,推荐尝试烧烤套餐!',
            style: TextStyle(fontSize: 11),
            textAlign: TextAlign.center,
          ),
        )
      ],
    );
  }

  // 构建菜单预览页面
  Widget _buildMenuPage() {
    return Column(
      children: [
        const Text(
          '热门菜品',
          style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 8),
        Expanded(
          child: ListView(
            padding: const EdgeInsets.symmetric(horizontal: 8.0),
            children: const [
              ListTile(
                contentPadding: EdgeInsets.zero,
                leading: Icon(Icons.fastfood, size: 18),
                title: Text('烧烤拼盘', style: TextStyle(fontSize: 12)),
                trailing: Text('₱299', style: TextStyle(fontSize: 12)),
              ),
              Divider(height: 10),
              ListTile(
                contentPadding: EdgeInsets.zero,
                leading: Icon(Icons.local_drink, size: 18),
                title: Text('鲜榨芒果汁', style: TextStyle(fontSize: 12)),
                trailing: Text('₱49', style: TextStyle(fontSize: 12)),
              ),
              Divider(height: 10),
              ListTile(
                contentPadding: EdgeInsets.zero,
                leading: Icon(Icons.bakery_dining, size: 18),
                title: Text('芝士披萨', style: TextStyle(fontSize: 12)),
                trailing: Text('₱199', style: TextStyle(fontSize: 12)),
              ),
            ],
          ),
        )
      ],
    );
  }
}

关键修改说明

  • 新增PageController用于控制页面切换,并在组件销毁时释放资源
  • 将CustomInfoWindow的内容替换为PageView,包含多个自定义页面组件
  • 添加页面指示器,帮助用户直观了解当前页面位置
  • 将原有内容拆分为独立的页面构建方法,代码结构更清晰
  • 适配每个页面的布局,确保在CustomInfoWindow的尺寸内正常显示

内容的提问来源于stack exchange,提问作者foodpark app

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 13:10:38