前些日子在做景区App遇到需求,使用手绘图片做一个简易的地图,支持放大缩小平移以及显示景点Mark,安卓上可以使用一个叫做“mAppWidget”的开源库来完成,WP上有人建议用ArcGIS,但是考虑到只是简单的放大缩小平移以及展示Mark标记,没必要再去花费精力去大费周折的研究ArcGIS,于是就各种搜索WP下的放大缩小平移图片的文章,还好很庆幸找到一篇(连接地址给忘记了原作者如果看到的话希望能提醒下给加上原链接)。解决了对图片放大缩小平移的问题,剩下的就是在上面添加Mark标记点了以及解决每次放大缩小后的Mark重新定位的问题。不多说。上代码!
前端XAML:
<!--LayoutRoot 是包含所有页面内容的根网格--> <Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Border Padding="12,12,0,18" Grid.Row="0" Style="{StaticResource SinglePageHeardStyle}" > <TextBlock FontSize="28" Text="地图导览"/> </Border> <Grid x:Name="ContentPanel" Grid.Row="1" > <ViewportControl x:Name="Viewport" HorizontalAlignment="Left" VerticalAlignment="Top"> <Canvas x:Name="can_Map" Width="480" Height="800"> <Image x:Name="Image" Stretch="Uniform" CacheMode="BitmapCache" ManipulationStarted="Viewport_ManipulationStarted" ManipulationDelta="Viewport_ManipulationDelta" ManipulationCompleted="Viewport_ManipulationCompleted" > </Image> </Canvas> </ViewportControl> </Grid> </Grid>
后台cs:
public partial class MapPage : PhoneApplicationPage { private const double IMAGE_MAX_WIDTH = 2048; private const double IMAGE_MAX_HEIGHT = 2048; private double _width = 0; private double _height = 0; private double _scale = 1.0; private Point _relativeCenter; private bool _pinching = false; private WriteableBitmap _writeableBitmap; public MapPage() { InitializeComponent(); this.Loaded += MainPage_Loaded; } private void MainPage_Loaded(object sender, RoutedEventArgs e) { var bitmapImage = GetImgForRes(); _writeableBitmap = new WriteableBitmap(bitmapImage); double scale; if (_writeableBitmap.PixelWidth > _writeableBitmap.PixelHeight) { scale = IMAGE_MAX_WIDTH / _writeableBitmap.PixelWidth; } else { scale = IMAGE_MAX_HEIGHT / _writeableBitmap.PixelHeight; } _width = _writeableBitmap.PixelWidth * scale; _height = _writeableBitmap.PixelHeight * scale; this.Image.Source = _writeableBitmap; ConfigureViewport(); App.MapViewModel.GetSp(App.GetUserAcction()); App.MapViewModel.GetMapSpCompleted += () => { foreach (var item in App.MapViewModel.ScencParentByMobile.Data) { //计算当前每个Mark的宽高与当前手绘图片的宽高缩放比例 item.Sh = (item.MapY -30) / _height; item.Sw = (item.MapX - 30) / _width; Image el = new Image(); //SetLeft 和SetTop 是根据元素的(0,0)点坐标开始的 所以放置坐标Mark的时候要减去元素宽的一半和元素高度 Canvas.SetLeft(el, (Image.Width * item.Sw) - 30); Canvas.SetTop(el, (Image.Height * item.Sh) - 60); el.Height = el.Width = 60; el.Stretch = Stretch.Uniform; //根据业务区选择加载哪个图片 if (item.IsFlag == 1) el.Source = new BitmapImage(new Uri("/MapPage/scenic_pointer_gril@2x.png", UriKind.RelativeOrAbsolute)); else el.Source = new BitmapImage(new Uri("/MapPage/scenic_pointer@2x.png", UriKind.RelativeOrAbsolute)); el.Tag = item.SCID + "|" + item.Sw + "|" + item.Sh; //记录每个Mark的id 宽高缩放比例到Tag以备用 can_Map.Children.Add(el); } }; } private void ConfigureViewport() { if (_width < _height) { _scale = Viewport.ActualHeight / _height; } else { _scale = Viewport.ActualWidth / _width; } Image.Width = _width * _scale; Image.Height = _height * _scale; Viewport.Bounds = new Rect(0, 0, Image.Width, Image.Height); Viewport.SetViewportOrigin(new Point(Image.Width / 2 - Viewport.Viewport.Width / 2, Image.Height / 2 - Viewport.Viewport.Height / 2)); } private static BitmapImage GetImgForRes() { string fileName = "/QWCProject;component/MapPage/map.jpg"; using (Stream stream = Application.GetResourceStream(new Uri(fileName, UriKind.Relative)).Stream) { BitmapImage bitmapImage = new BitmapImage(); bitmapImage.SetSource(stream); return bitmapImage; } } private void Viewport_ManipulationStarted(object sender, System.Windows.Input.ManipulationStartedEventArgs e) { if (_pinching) { e.Handled = true; CompletePinching(); } } private void CompletePinching() { _pinching = false; double sw = Image.Width / _width; double sh = Image.Height / _height; _scale = Math.Min(sw, sh); //完成缩放放大后获取所有Mark点集合 var markList = WPTools.VisualTreeTool.GetChildObjects<Image>(can_Map, typeof(Image)); foreach (var item in markList) { //过滤手绘图片 if (item.Tag == null) continue; //根据当前Image(手绘图片)的宽高乘以该Mark的坐标缩放比例得出缩放放大后的每个Mark的坐标 Canvas.SetLeft(item, (Image.Width * double.Parse(item.Tag.ToString().Split(‘|‘)[1])) - 30); Canvas.SetTop(item, (Image.Height * double.Parse(item.Tag.ToString().Split(‘|‘)[2])) - 60); item.Visibility = Visibility.Visible; } } private void Viewport_ManipulationDelta(object sender, System.Windows.Input.ManipulationDeltaEventArgs e) { if (e.PinchManipulation != null) { //获取所有Mark 在缩放放大时候隐藏所有Mark var markList = WPTools.VisualTreeTool.GetChildObjects<Image>(can_Map, typeof(Image)); foreach (var item in markList) { if (item.Tag == null) continue; item.Visibility = Visibility.Collapsed; } e.Handled = true; if (!_pinching) { _pinching = true; _relativeCenter = new Point(e.PinchManipulation.Original.Center.X / Image.Width, e.PinchManipulation.Original.Center.Y / Image.Height); } double w, h; if (_width < _height) { h = _height * _scale * e.PinchManipulation.CumulativeScale; h = Math.Max(Viewport.ActualHeight, h); h = Math.Min(h, _height); w = h * _width / _height; } else { w = _width * _scale * e.PinchManipulation.CumulativeScale; w = Math.Max(Viewport.ActualWidth, w); w = Math.Min(w, _width); h = w * _height / _width; } Image.Width = w; Image.Height = h; Viewport.Bounds = new Rect(0, 0, w, h); GeneralTransform transform = Image.TransformToVisual(Viewport); Point p = transform.Transform(e.PinchManipulation.Original.Center); double x = _relativeCenter.X * w - p.X; double y = _relativeCenter.Y * h - p.Y; if (w < _width && h < _height) { System.Diagnostics.Debug.WriteLine("Viewport.ActualWidth={0} .ActualHeight={1} Origin.X={2} .Y={3} Image.Width={4} .Height={5}", Viewport.ActualWidth, Viewport.ActualHeight, x, y, Image.Width, Image.Height); Viewport.SetViewportOrigin(new Point(x, y)); } } else if (_pinching) { e.Handled = true; CompletePinching(); } } private void Viewport_ManipulationCompleted(object sender, System.Windows.Input.ManipulationCompletedEventArgs e) { if (_pinching) { e.Handled = true; CompletePinching(); } } }
另外还有个获取指定元素下的所有指定类型子元素的方法:
public static List<T> GetChildObjects<T>(DependencyObject obj, Type typename) where T : FrameworkElement { DependencyObject child = null; List<T> childList = new List<T>(); for (int i = 0; i <= VisualTreeHelper.GetChildrenCount(obj) - 1; i++) { child = VisualTreeHelper.GetChild(obj, i); if (child is T && (((T)child).GetType() == typename)) { childList.Add((T)child); } childList.AddRange(GetChildObjects<T>(child, typename)); } return childList; }
要注意的是:地图图片文件的生成资源要改为:Resource 不然没法获取到图片!坐标信息是从服务器获取的,在MainPage_Loaded()方法内—— App.MapViewModel.GetSp()方法
最后上个演示视频(手绘地图上用户去过的景点的Mark会出现卡通人物头像 没去过的则不出现卡通人物头像):