This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

Preface: Want to complete meituan like custom map components? Want software as silky as Meituan? Want the same salary as Meituan? It’s daytime. Do you want to daydream? Hi, I’m T. For a week, the main is to use the gold SDK realized a custom map, why want to do this, because online map processing on Flutter is too little, let alone a custom map, then website document is too brief, harm, said more are the tears, so I paid a girlfriend if you’ll excuse me five days, Write this article out!!

Source code at the end of the article, step by step in accordance with the article to run can certainly 💪

First, the effect picture:

There are many other small functions will not show, we can run their own look ~

Notes for reading this article:

1. The test code needs to use the real machine, so the simulator cannot load the map (it can locate, but cannot load the map, which may be related to the version)

2. Plug-ins used in this article:

permission_handler: ^ 8.1.4 # Permission management
amap_flutter_map: 2.02. # Amap
Copy the code

Body:

1. Apply for key on Autonavi’s developer platform:

Step 1: Sign up for a developer account

Autonavi developer address: console.amap.com/

Step 2: Create a new application and apply for a key

Step 3:

How to get SHA1 and handle errors in autonavi positioning is in this article (next article, still working on the code word 😭, give it a thumbs up).

Step 4 Get the key:

2. Handle permission to use Amap

Step 1: Add the permissions needed for location

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<! -- Access network -->
<uses-permission android:name="android.permission.INTERNET" />
<! -- Rough positioning -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<! -- Precise location -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<! -- Request to call a-GPS module -->
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<! -- Used to get carrier information, used to support the interface that provides carrier information -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<! -- Used to access wifi network information, wifi information will be used for network positioning -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<! -- Used to obtain wifi access permission, wifi information will be used for network location -->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<! -- Read the current state of the phone -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<! Write cache data to extended memory card -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Copy the code

** Step 2: ** Add the key you just applied for and add the development details

<meta-data android:name="com.amap.api.v2.apikey" Android :value=" own key" /> <service android:name="com.amap.api.location.APSService"/>Copy the code

Step 3: Configure some key.jks

How to generate key.jks in this article

After that, create a key.properties

StorePassword = keyPassword= keyPassword= keyAlias=key storeFile= Store location (D:\\flutter_gaode_keystore\\key.jks)Copy the code

Then use build. Gradle in app folder:

To find the key. The properties

def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
Copy the code

Then reference it under signingConfigs:

signingConfigs {
    release {
        keyAlias keystoreProperties['keyAlias']
        keyPassword keystoreProperties['keyPassword']
        storeFile file(keystoreProperties['storeFile'])
        storePassword keystoreProperties['storePassword']
    }
}
Copy the code

Step 4: Import the map

It’s still in build.gradle under app

Dependencies {implementation (' com. Amap. API: location: 5.2.0 ') implementation 'com. Amap. API: 3 dmap: 7.6.0' implementation 'com. Amap. API: search: 5.0.0'}Copy the code
SourceSets {// Add map SDK import path main {jnilibs. srcDirs = ['libs']}}Copy the code

And add two lines under buildTypes:

BuildTypes {release {signingConfig SigningConfigs. release minifyEnabled false // Remove shrinkResources false // Remove unwanted resources }}Copy the code

That’s how ~ is configured

3. Load amap

The next section is the code implementation section, showing only the core code 🐷

Step 1: Add the requested key

Class ConstConfig {/// Configure the applied apikey. After this configuration, you can set the 'apikey' property /// /// when initializing the [AMapWidget]. Note: [AMapWidget] 's' apiKey 'property has a higher priority than Native keys. // Native keys will become invalid after being configured with [AMapWidget' s' apiKey 'property. Select static const AMapApiKey amapApiKeys = AMapApiKey(androidKey: 'your own key', iosKey: 'If you are not applying for ios, fill in the Android key first so that you can also test on Android phones '); }Copy the code

Step 2: Define map types

// Map type
MapType _mapType;

final Map<String, MapType> _radioValueMap = {
  'Normal map': MapType.normal,
  'Satellite map': MapType.satellite,
  'Navigation map': MapType.navi,
  'Bus map': MapType.bus,
  'Night Mode': MapType.night,
};
Copy the code

Initialize the map:

@override
void initState() {
  super.initState();
   // Default is normal map
  _mapType = MapType.normal;
}
Copy the code

Create a map:

// Create a map
final AMapWidget map = AMapWidget(
  apiKey: ConstConfig.amapApiKeys,
  // Map type attributes
  mapType: _mapType ?? MapType.normal,
);
Copy the code

Step 3: Display the map

AMapRadioGroup(
  groupLabel: 'Map style',
  groupValue: _mapType,
  radioValueMap: _radioValueMap,
  onChanged: (value) => {
    // Change the current map style to the selected onesetState(() { _mapType = value; })},Copy the code

Step 4: Default address

Static final CameraPosition _kInitialPosition = const CameraPosition(target: LatLng(39.909187, 116.397451), ZOOM: 10.0,);Copy the code

4. Custom maps

Step 1: Load a custom map style

// Used to record whether it is a custom map
bool _mapCreated = false;

// Load a custom map style
void _loadCustomData() async {
    if (null == _customStyleOptions) {
      _customStyleOptions = CustomStyleOptions(false);
    }
    ByteData styleByteData = await rootBundle.load('assets/style.data');
    _customStyleOptions.styleData = styleByteData.buffer.asUint8List();
    ByteData styleExtraByteData =
        await rootBundle.load('assets/style_extra.data');
    _customStyleOptions.styleExtraData =
        styleExtraByteData.buffer.asUint8List();
    // If you want to display the custom map directly after loading, you can use setState to change the CustomStyleOptions enable to true
    setState(() {
      _customStyleOptions.enabled = true;
    });
}
Copy the code

Step 2: Define the map

final AMapWidget map = AMapWidget(
  apiKey: ConstConfig.amapApiKeys,
  onMapCreated: onMapCreated,
  customStyleOptions: _customStyleOptions,
);

void onMapCreated(AMapController controller) {
    if (null! = controller) { _mapCreated =true; }}Copy the code

Step 3: Click to switch to custom map

AMapSwitchButton(
  label: Text(
    'Custom map',
    style: TextStyle(color: Colors.white),
  ),
  defaultValue: _customStyleOptions.enabled,
  onSwitchChanged: (value) => {
    if(_mapCreated) { setState(() { _customStyleOptions.enabled = value; }}}),)Copy the code

5. Let the delivery guy fly

Step 1: Get the custom image

There are three ways:

The first:

/ / / by BitmapDescriptor fromAssetImage pictures for Future < void > _createMarkerImageFromAsset (BuildContext context) async {the if (_markerIcon == null) { final ImageConfiguration imageConfiguration = createLocalImageConfiguration(context); BitmapDescriptor.fromAssetImage(imageConfiguration, 'assets/start.png') .then(_updateBitmap); }}Copy the code

The second:

///Through BitmapDescriptor. FromBytes way for image
  Future<void> _createMarkerImageFromBytes(BuildContext context) async {
    final Completer<BitmapDescriptor> bitmapIcon =
        Completer<BitmapDescriptor>();
    final ImageConfiguration config = createLocalImageConfiguration(context);

    const AssetImage('assets/end.png')
        .resolve(config)
        .addListener(ImageStreamListener((ImageInfo image, bool sync) async {
      final ByteData bytes =
          await image.image.toByteData(format: ImageByteFormat.png);
      final BitmapDescriptor bitmap =
          BitmapDescriptor.fromBytes(bytes.buffer.asUint8List());
      bitmapIcon.complete(bitmap);
    }));

    bitmapIcon.future.then((value) => _updateBitmap(value));
  }
Copy the code

The third (simplest) :

// The simplest way
if (null == _markerIcon) {
  _markerIcon = BitmapDescriptor.fromIconPath('assets/location_marker.png');
}
Copy the code

Step 2: calculate the position of the picture according to the x and y axes

void _changeAnchor() {
  final Marker marker = _markers[selectedMarkerId];
  if (marker == null) {
    return;
  }
  final Offset currentAnchor = marker.anchor;
  double dx = 0;
  double dy = 0;
  if (currentAnchor.dx < 1) {
    dx = currentAnchor.dx + 0.1;
  } else {
    dx = 0;
  }
  if (currentAnchor.dy < 1) {
    dy = currentAnchor.dy + 0.1;
  } else {
    dy = 0;
  }
  final Offset newAnchor = Offset(dx, dy);
  setState(() {
    _markers[selectedMarkerId] = marker.copyWith(
      anchorParam: newAnchor,
    );
  });
}
Copy the code

This will display the delivery man on the map, as well as a few other small functions

Modify the position of the image:

void _changePosition() {
  final Marker marker = _markers[selectedMarkerId];
  final LatLng current = marker.position;
  final Offset offset = Offset(
    mapCenter.latitude - current.latitude,
    mapCenter.longitude - current.longitude,
  );
  setState(() {
    _markers[selectedMarkerId] = marker.copyWith(
      positionParam: LatLng(
        mapCenter.latitude + offset.dy,
        mapCenter.longitude + offset.dx,
      ),
    );
  });
}

Future<void> _changeAlpha() async {
  final Marker marker = _markers[selectedMarkerId];
  final double current = marker.alpha;
  setState(() {
    _markers[selectedMarkerId] = marker.copyWith(
      alphaParam: current < 0.1 ? 1.0 : current * 0.75,); }); }Copy the code

Change the Angle of the picture:

Future<void> _changeRotation() async {
  final Marker marker = _markers[selectedMarkerId];
  final double current = marker.rotation;
  setState(() {
    _markers[selectedMarkerId] = marker.copyWith(
      rotationParam: current == 330.0 ? 0.0 : current + 30.0,); }); }Copy the code

6. Add multiple ICONS on the map

1. Define the marker

static final LatLng mapCenter = const LatLng(39.909187.116.397451);
 // An empty map must be set to the AMapWidget's markers, otherwise markers cannot be added later
final Map<String, Marker> _initMarkerMap = <String, Marker>{};
Copy the code

2. Increase the marker

Add a:

void _addMarker() {
  final _markerPosition =
      LatLng(_currentLatLng.latitude, _currentLatLng.longitude + 2 / 1000);
  final Marker marker = Marker(
    position: _markerPosition,
    // Use the default hue mode to set the Marker icon
    icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueOrange),
  );
  // Call setState to trigger an update of the AMapWidget to complete the marker addition
  setState(() {
    _currentLatLng = _markerPosition;
    // Add new marker to map
    _markers[marker.id] = marker;
  });
}
Copy the code

Add multiple:

for(int i=0; i< 10; i++) {
  LatLng position = LatLng(
      mapCenter.latitude + sin(i * pi / 12.0) / 20.0,
      mapCenter.longitude + cos(i * pi / 12.0) / 20.0);
  Marker marker = Marker(position: position);
  _initMarkerMap[marker.id] = marker;
}
Copy the code

7. Auxiliary tool Toast

Encapsulates a toast and adds it to the screen via OverlayEntry

Display its core code:

if (_overlayEntry == null) {
  //OverlayEntry is responsible for building the layout
  // Insert the built layout into the top layer of the entire layout with OverlayEntry
  _overlayEntry = OverlayEntry(
      builder: (BuildContext context) => Positioned(
            // Top value, which can be changed to change the position of toast in the screen
            top: buildToastPosition(context),
            child: Container(
                alignment: Alignment.center,
                width: MediaQuery.of(context).size.width,
                child: Padding(
                  padding: EdgeInsets.symmetric(horizontal: 40.0),
                  child: AnimatedOpacity(
                    opacity: _showing ? 1.0 : 0.0.// Target transparency
                    duration: _showing
                        ? Duration(milliseconds: 100)
                        : Duration(milliseconds: 400),
                    child: _buildToastWidget(),
                  ),
                )),
          ));
  // Insert into the top layer of the entire layout
  overlayState.insert(_overlayEntry);
} else {
  // Redraw the UI, like setState
  _overlayEntry.markNeedsBuild();
}
Copy the code

Draw core code:

/ / the toast
static _buildToastWidget() {
  return Center(
    child: Card(
      color: _bgColor,
      child: Padding(
        padding: EdgeInsets.symmetric(
            horizontal: _pdHorizontal, vertical: _pdVertical),
        child: Text(
          _msg,
          style: TextStyle(
            fontSize: _textSize,
            color: _textColor,
          ),
        ),
      ),
    ),
  );
}
Copy the code

Set the toast location:

// Set the toast position
  static buildToastPosition(context) {
    var backResult;
    if (_toastPosition == ToastPostion.top) {
      backResult = MediaQuery.of(context).size.height * 1 / 4;
    } else if (_toastPosition == ToastPostion.center) {
      backResult = MediaQuery.of(context).size.height * 2 / 5;
    } else {
      backResult = MediaQuery.of(context).size.height * 3 / 4;
    }
    return backResult;
  }
Copy the code

This basic function is explained to complete, and other functions are written in the source code, such as road display, limit map size and so on

The end of this article, see here brothers give a little praise, girlfriend has let me sleep at home 😭

(Too long to listen to her coaxing…) The comment section tells xiao Di 😭

The source code is here