الخرائط والموقع وميزات الجهاز

التحكم في كاميرا الخريطة وإعدادات الواجهة

16 دقيقة الدرس 2 من 12

التحكم في كاميرا الخريطة وإعدادات الواجهة

بمجرد عرض خريطة Google داخل ودجت Flutter، تأتي الخطوة التالية: تعلُّم التحكم فيما يراه المستخدم — الموضع ومستوى التكبير والميل والاتجاه للكاميرا — وكيفية ضبط عناصر التحكم المدمجة في واجهة Google Maps. هذه القدرات ضرورية لبناء تطبيقات تستجيب لأفعال المستخدم، وترتبط بمواقع محددة، وتُقدِّم تجربة خريطة متقنة ومتسقة مع هوية العلامة التجارية.

الفئة CameraPosition

CameraPosition هي لقطة ثابتة (غير قابلة للتغيير) لزاوية نظر كاميرا الخريطة. تحمل أربع خصائص:

  • target — كائن LatLng يحدد إلى أين تتجه الكاميرا
  • zoom — قيمة double بين 0 (عرض العالم) و~21 (مستوى المبنى)
  • tilt — درجات ميل الكاميرا من أعلى (0 – 90)؛ يعمل فقط عند مستويات تكبير عالية
  • bearing — اتجاه البوصلة الذي تواجهه الكاميرا بالدرجات (0 = شمال)

تُمرِّر initialCameraPosition إلى ودجت GoogleMap عند تحميل الخريطة لأول مرة. في كل مرة تريد تحريك الكاميرا برمجيًا بعد ذلك، تنشئ كائن CameraUpdate وترسله عبر GoogleMapController.

الإعلان عن CameraPosition الابتدائي

import 'package:google_maps_flutter/google_maps_flutter.dart';

const CameraPosition _initialPosition = CameraPosition(
  target: LatLng(24.7136, 46.6753), // الرياض، المملكة العربية السعودية
  zoom: 12.0,
  tilt: 0,
  bearing: 0,
);

GoogleMap(
  initialCameraPosition: _initialPosition,
  onMapCreated: (GoogleMapController controller) {
    _controller = controller;
  },
)

الحصول على GoogleMapController وتخزينه

GoogleMapController هو مقبضك للوصول إلى نسخة الخريطة الحية. يُسلَّم عبر دالة ردّ النداء onMapCreated بمجرد تحميل بلاطات الخريطة. خزِّنه داخل Completer<GoogleMapController> حتى تتمكن أي دالة تحتاجه من الانتظار الآمن حتى يتوفر، حتى لو استُدعيت قبل انتهاء تهيئة الخريطة.

استخدام Completer لتخزين المتحكم بأمان

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

class MapCameraPage extends StatefulWidget {
  const MapCameraPage({super.key});

  @override
  State<MapCameraPage> createState() => _MapCameraPageState();
}

class _MapCameraPageState extends State<MapCameraPage> {
  final Completer<GoogleMapController> _controllerCompleter =
      Completer<GoogleMapController>();

  static const CameraPosition _riyadh = CameraPosition(
    target: LatLng(24.7136, 46.6753),
    zoom: 12,
  );

  void _onMapCreated(GoogleMapController controller) {
    _controllerCompleter.complete(controller);
  }

  /// الانتقال برمجيًا إلى موضع جديد
  Future<void> _goToMecca() async {
    final GoogleMapController controller =
        await _controllerCompleter.future;
    const CameraPosition mecca = CameraPosition(
      target: LatLng(21.3891, 39.8579),
      zoom: 14,
      tilt: 45,
      bearing: 30,
    );
    await controller.animateCamera(
      CameraUpdate.newCameraPosition(mecca),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('عرض كاميرا الخريطة')),
      body: GoogleMap(
        initialCameraPosition: _riyadh,
        onMapCreated: _onMapCreated,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _goToMecca,
        child: const Icon(Icons.location_city),
      ),
    );
  }
}

CameraUpdate — أمر الحركة

CameraUpdate هي فئة مصنع (factory class) تنشئ تعليمات حركة الكاميرا. تُمرِّر كائن CameraUpdate إلى controller.animateCamera() (حركة سلسة ومتحركة) أو controller.moveCamera() (فورية بدون تحريك). أكثر دوال البناء المصنعية فائدةً هي:

  • CameraUpdate.newCameraPosition(pos) — تحكم كامل: الهدف + التكبير + الميل + الاتجاه
  • CameraUpdate.newLatLng(latLng) — الانتقال إلى إحداثيات مع الحفاظ على التكبير الحالي
  • CameraUpdate.newLatLngZoom(latLng, zoom) — الانتقال وضبط التكبير في آنٍ واحد
  • CameraUpdate.zoomIn() / CameraUpdate.zoomOut() — زيادة أو إنقاص التكبير بمقدار 1
  • CameraUpdate.zoomTo(zoom) — القفز مباشرةً إلى مستوى تكبير محدد
  • CameraUpdate.newLatLngBounds(bounds, padding) — ملاءمة مستطيل حدودي داخل نافذة العرض
ملاحظة: تُعيد animateCamera() كائن Future<void> يكتمل عند انتهاء الحركة. انتظاره (await) يُمكِّنك من تسلسل حركات الكاميرا أو تشغيل كود فقط بعد استقرار الكاميرا.

ضبط عناصر التحكم في الواجهة المدمجة

يكشف ودجت GoogleMap خصائص منطقية (boolean) تُتيح تشغيل أو إيقاف عناصر التحكم الأصلية التي يرسمها Google Maps SDK. تُرسم هذه العناصر عبر طبقة المنصة (لا Flutter)، لذا تبدو أصيلة على كل نظام تشغيل:

  • zoomControlsEnabled — يعرض أزرار التكبير/التصغير +/– (أندرويد فقط؛ القيمة الافتراضية true)
  • zoomGesturesEnabled — يُفعِّل التكبير بقرصة الأصابع والنقر المزدوج (افتراضيًا true)
  • compassEnabled — يعرض شارة البوصلة عند تدوير الخريطة (افتراضيًا true)
  • myLocationButtonEnabled — يعرض زر "تحديد موقعي" (يتطلب إذن الموقع؛ افتراضيًا true)
  • myLocationEnabled — يرسم النقطة الزرقاء على موقع المستخدم الحالي
  • rotateGesturesEnabled — يسمح بالتدوير بإصبعين (افتراضيًا true)
  • scrollGesturesEnabled — يسمح بإيماءات السحب (افتراضيًا true)
  • tiltGesturesEnabled — يُفعِّل الإمالة بإصبعين (افتراضيًا true)
  • mapToolbarEnabled — يعرض شريط أدوات أندرويد "فتح في الخرائط / الاتجاهات" (أندرويد فقط؛ افتراضيًا true)
  • mapType — إحدى القيم: MapType.normal، MapType.satellite، MapType.terrain، MapType.hybrid

تبديل نوع الخريطة وعناصر التحكم في وقت التشغيل

class _MapCameraPageState extends State<MapCameraPage> {
  MapType _currentMapType = MapType.normal;
  bool _showCompass = true;
  bool _showZoomControls = true;

  void _toggleMapType() {
    setState(() {
      _currentMapType = _currentMapType == MapType.normal
          ? MapType.satellite
          : MapType.normal;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GoogleMap(
        initialCameraPosition: _riyadh,
        onMapCreated: _onMapCreated,
        mapType: _currentMapType,
        compassEnabled: _showCompass,
        zoomControlsEnabled: _showZoomControls,
        zoomGesturesEnabled: true,
        myLocationButtonEnabled: false, // مخفي — نستخدم زرنا الخاص
        rotateGesturesEnabled: true,
        tiltGesturesEnabled: true,
        scrollGesturesEnabled: true,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _toggleMapType,
        child: const Icon(Icons.layers),
      ),
    );
  }
}
نصيحة: على iOS، لا تؤثر خاصية zoomControlsEnabled — يستخدم مستخدمو iOS دائمًا إيماءة القرص للتكبير. اضبط myLocationButtonEnabled: false وقدِّم زرك المُصمَّم خاصةً حين تحتاج إلى تناسق بصري على كلا المنصتين.

الاستماع إلى أحداث حركة الكاميرا

يُطلق ودجت GoogleMap عدة دوال ردّ نداء يمكنك ربطها للتفاعل مع تغيرات الكاميرا:

  • onCameraMove(CameraPosition pos) — يُطلق باستمرار أثناء تحرك الكاميرا
  • onCameraMoveStarted() — يُطلق عند بدء حركة الكاميرا
  • onCameraIdle() — يُطلق بمجرد توقف الكاميرا (مثالي لتحميل البيانات للمنطقة المرئية)
تحذير: يمكن أن يُطلَق onCameraMove عشرات المرات في الثانية أثناء السحب أو القرص. لا تُنفِّذ أبدًا عمليات ثقيلة (طلبات شبكة، حسابات معقدة) بداخله. بدلًا من ذلك، استخدم تقنية الـ debounce أو استخدم onCameraIdle لمنطق جلب البيانات.

ملخص

أصبح لديك الآن مجموعة الأدوات الكاملة للتحكم البرمجي في كاميرا الخريطة في Flutter:

  • استخدم CameraPosition لوصف زاوية نظر الكاميرا (الهدف، التكبير، الميل، الاتجاه).
  • خزِّن GoogleMapController في Completer ليكون دائمًا متاحًا بأمان.
  • استخدم دوال البناء المصنعية لـ CameraUpdate مع animateCamera أو moveCamera للتحكم في الكاميرا.
  • بدِّل عناصر التحكم في الواجهة مثل compassEnabled وzoomControlsEnabled وmapType مباشرةً على ودجت GoogleMap.
  • تفاعل مع حركة الكاميرا عبر onCameraMove وonCameraIdle، لكن حافظ على خفة تلك الدوال.