Xamarin Android实现带可筛选标记与搜索功能的自定义非谷歌地图
Got it, let's walk through how to implement this custom image map in your Xamarin Android app — I've built similar campus/mall map features before, so here's a step-by-step breakdown that works:
First, get your custom map image (campus/mall layout) optimized for different screen densities and place it in the drawable or mipmap folders. Then, we need to calculate the grid boundaries based on your A-Z latitude and 1-26 longitude rule.
We'll do this once the map view is fully laid out to get its actual dimensions:
private float _gridWidth; private float _gridHeight; private ImageView _mapImageView; protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); SetContentView(Resource.Layout.Main); _mapImageView = FindViewById<ImageView>(Resource.Id.mapImageView); // Wait for the map to finish layout to get accurate size _mapImageView.ViewTreeObserver.GlobalLayout += (sender, e) => { int mapWidth = _mapImageView.Width; int mapHeight = _mapImageView.Height; // Split into 26 equal columns (longitude 1-26) and rows (latitude A-Z) _gridWidth = mapWidth / 26f; _gridHeight = mapHeight / 26f; }; }
Create a simple model to store marker data, then convert grid coordinates to screen positions to place markers on top of the map.
First, define your marker model:
public class MapMarker { public char Latitude { get; set; } // A-Z public int Longitude { get; set; } // 1-26 public string Title { get; set; } public string Category { get; set; } // For filtering (e.g., "Cafe", "Classroom") }
Next, a helper to convert grid positions to screen coordinates:
private PointF GetMarkerScreenPosition(MapMarker marker) { int latIndex = marker.Latitude - 'A'; // A = 0, B = 1 ... Z = 25 int lonIndex = marker.Longitude - 1; // 1 = 0, 2 = 1 ... 26 = 25 // Center the marker in its grid cell float x = lonIndex * _gridWidth + (_gridWidth / 2); float y = latIndex * _gridHeight + (_gridHeight / 2); return new PointF(x, y); }
Add markers to your map (use a FrameLayout to layer markers over the image view):
private List<ImageView> _allMarkerViews = new List<ImageView>(); private List<MapMarker> _allMarkers = new List<MapMarker>(); private void AddMarker(MapMarker marker) { _allMarkers.Add(marker); PointF screenPos = GetMarkerScreenPosition(marker); ImageView markerView = new ImageView(this); markerView.SetImageResource(Resource.Drawable.marker_icon); markerView.Tag = marker; // Attach marker data for later use _allMarkerViews.Add(markerView); // Position the marker on the map FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent ); layoutParams.LeftMargin = (int)(screenPos.X - markerView.Width / 2); layoutParams.TopMargin = (int)(screenPos.Y - markerView.Height / 2); FrameLayout mapContainer = FindViewById<FrameLayout>(Resource.Id.mapContainer); mapContainer.AddView(markerView); }
Add checkboxes to your layout for each marker category, then wire up logic to show/hide markers based on selected filters.
First, add checkboxes to your XML layout:
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="8dp"> <CheckBox android:id="@+id/cb_cafes" android:text="Cafes" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <CheckBox android:id="@+id/cb_classrooms" android:text="Classrooms" android:layout_width="wrap_content" android:layout_height="wrap_content" android:marginStart="16dp"/> </LinearLayout>
Then handle filter logic in code:
private void SetupFilterCheckboxes() { CheckBox cbCafes = FindViewById<CheckBox>(Resource.Id.cb_cafes); CheckBox cbClassrooms = FindViewById<CheckBox>(Resource.Id.cb_classrooms); // Reuse the same handler for all checkboxes EventHandler<CompoundButton.CheckedChangeEventArgs> filterHandler = (sender, e) => { UpdateMarkerVisibility(); }; cbCafes.CheckedChange += filterHandler; cbClassrooms.CheckedChange += filterHandler; } private void UpdateMarkerVisibility() { CheckBox cbCafes = FindViewById<CheckBox>(Resource.Id.cb_cafes); CheckBox cbClassrooms = FindViewById<CheckBox>(Resource.Id.cb_classrooms); foreach (var markerView in _allMarkerViews) { var marker = markerView.Tag as MapMarker; bool shouldShow = false; // Check if marker matches any selected filter if ((cbCafes.Checked && marker.Category == "Cafe") || (cbClassrooms.Checked && marker.Category == "Classroom")) { shouldShow = true; } markerView.Visibility = shouldShow ? ViewStates.Visible : ViewStates.Gone; } }
Implement a search bar to find markers by grid position (e.g., "A1") or title. Add these views to your layout:
<EditText android:id="@+id/et_search" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Search (e.g., A1 or Library)" android:padding="8dp"/> <Button android:id="@+id/btn_search" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Search"/>
Then wire up the search logic:
private void SetupSearch() { EditText etSearch = FindViewById<EditText>(Resource.Id.et_search); Button btnSearch = FindViewById<Button>(Resource.Id.btn_search); btnSearch.Click += (sender, e) => { string query = etSearch.Text.Trim().ToUpper(); MapMarker foundMarker = null; // Search by grid position (e.g., "A1") if (query.Length == 2 && char.IsLetter(query[0]) && char.IsDigit(query[1])) { char lat = query[0]; int lon = int.Parse(query[1].ToString()); foundMarker = _allMarkers.FirstOrDefault(m => m.Latitude == lat && m.Longitude == lon); } // Search by marker title else { foundMarker = _allMarkers.FirstOrDefault(m => m.Title.ToUpper().Contains(query)); } if (foundMarker != null) { // Scroll map to center the marker PointF pos = GetMarkerScreenPosition(foundMarker); FrameLayout mapContainer = FindViewById<FrameLayout>(Resource.Id.mapContainer); mapContainer.ScrollTo( (int)(pos.X - mapContainer.Width / 2), (int)(pos.Y - mapContainer.Height / 2) ); // Highlight the found marker temporarily foreach (var markerView in _allMarkerViews) { var marker = markerView.Tag as MapMarker; if (marker == foundMarker) { markerView.SetBackgroundColor(Color.Yellow); new Handler().PostDelayed(() => markerView.SetBackgroundColor(Color.Transparent), 3000); } } } else { Toast.MakeText(this, "No location found", ToastLength.Short).Show(); } }; }
- If your map is larger than the screen, wrap the
FrameLayoutin aScrollView(orHorizontalScrollView) to enable dragging. - For better user clarity, you can draw grid lines directly on the map image or via a custom
Viewoverlay. - If you have a lot of markers, consider using a
RecyclerViewinstead of adding individual views, but for campus/mall scenarios, the above approach is lightweight enough.
内容的提问来源于stack exchange,提问作者Adam

