在进行缩放手势时替换 ScrollViewer 中的图像

Posted

技术标签:

【中文标题】在进行缩放手势时替换 ScrollViewer 中的图像【英文标题】:Replacing Image in ScrollViewer while doing a zoom gesture 【发布时间】:2013-01-07 21:16:26 【问题描述】:

我正在创建一个照片查看应用程序,我想实现图片的缩放。我创建了一个 ScrollViewer 并在那里放置了一个图像。这一切都是开箱即用的。我可以做一个缩放手势,它会放大图片。现在,我要实现的下一件事是在缩放手势开始时加载图片的高分辨率版本,并在加载时动态交换 Image 控件中的位图。我希望它无缝地发生,以便用户可以继续手势并继续放大并查看更详细的图片。 实现这一目标的最佳方法是什么? 这是我目前拥有的代码。我的代码的问题是,当 Image.Source 被替换时,我的手势被中断并且照片被重置为原始大小。更改 ScrollViewer ZoomFactor 无济于事,因为在替换图像时它似乎会重置。 我有一个带有 Image 属性的 DataModel,该属性最初返回 null,但开始以低分辨率模式从“文件”加载图片,并在加载完成时调用 OnPropertyChanged("Image")。调用 LoadFullImage() 加载全分辨率版本并在完成时调用 OnPropertyChanged("Image")。

以下是 DataModel.cs 的摘录:

public async Task LoadFullImage()
    
        loadFullImageTask = UpdateImage(0);
        await loadFullImageTask;
    

    public ImageSource Image
    
        get
        
            if (fullImage != null)
            
                return fullImage;
            
            else if (image != null)
            
                return image;
            
            else
            
                Task loadImageTask = UpdateImage(768);

                return null;
            
        
    

    public bool FullImageLoading
    
        get  return (this.loadFullImageTask != null) && (!this.loadFullImageTask.IsCompleted); 
    

    public bool FullImageLoaded
    
        get  return this.fullImage != null; 
    

这是我的 MainPage.xaml:

<Grid Background="StaticResource ApplicationPageBackgroundThemeBrush">
    <ScrollViewer x:Name="imageViewer" HorizontalAlignment="Stretch" HorizontalScrollBarVisibility="Visible" VerticalAlignment="Stretch" MinZoomFactor="1" ZoomMode="Enabled" ViewChanged="imageViewer_ViewChanged">
        <Image x:Name="image" Margin="0,0,0,0" Stretch="Uniform" Source="Binding Image" />
    </ScrollViewer>
</Grid>

这是我的 MainPage.xaml.cs:

protected override async void OnNavigatedTo(NavigationEventArgs e)
    
        FileOpenPicker filePicker = new FileOpenPicker();
        filePicker.SuggestedStartLocation = PickerLocationId.ComputerFolder;
        filePicker.FileTypeFilter.Add(".jpg");
        StorageFile file = await filePicker.PickSingleFileAsync();

        data = new DataModel(file);
        imageViewer.DataContext = data; 
    

    private async void imageViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
    
        ScrollViewer imageViewer = (ScrollViewer)sender;

        if (imageViewer.ZoomFactor > 1)
        
            if (!data.FullImageLoaded && (!data.FullImageLoading))
            
                int oldHeight = ((BitmapImage)data.Image).PixelHeight;
                int oldWidth = ((BitmapImage)data.Image).PixelWidth;
                double oldHOffset = imageViewer.HorizontalOffset;
                double oldVOffset = imageViewer.VerticalOffset;

                await data.LoadFullImage();

                int newHeight = ((BitmapImage)data.Image).PixelHeight;
                int newWidth = ((BitmapImage)data.Image).PixelWidth;

                float ratio = (float)oldHeight / (float)newHeight;
                imageViewer.MaxZoomFactor = imageViewer.MaxZoomFactor * ratio;
                imageViewer.MinZoomFactor = imageViewer.MinZoomFactor * ratio;
                imageViewer.ZoomToFactor(imageViewer.ZoomFactor * ratio);
                //imageViewer.ScrollToHorizontalOffset(oldHOffset / ratio);
                //imageViewer.ScrollToVerticalOffset(oldVOffset / ratio);
            
        
    

正如我已经提到的,这段代码的问题是手势被中断并且新图像没有调整大小/滚动到正确的位置,用户体验不是无缝的。 感谢您提供有关如何解决此问题的任何建议。

【问题讨论】:

【参考方案1】:

我不会为你写这段代码,因为它会太多,但这里是步骤:

    图像需要将 ScaleTransform 应用于其 RenderTransform 属性。当用户捏或拉伸时,您将使用它来使其“缩放”。 使用操作事件检测捏合手势;如果它可以帮助您减少一些代码,您甚至可以仅过滤到捏合手势。这些事件将是最可靠的。 Manipulation 事件将返回发生了多少捏合或拉伸,这需要与您在数字 1 中应用到图像的 ScaleTransform 的缩放程度相对应。 在代码隐藏中,您可以创建一个 BitmapImage 指向并从服务器加载图像。加载后,您可以替换用户正在捏的图像的来源。除非高分辨率图像是数兆字节,否则这对用户来说几乎是无缝的。不要那样做。

请注意,我的步骤不包括 ScrollViewer。 ScrollViewer 当然可以包装图像,但工作本身是作为图像本身的一部分完成的。

我可能还会向您指出 MultiScaleImage 控件,您可能想从中窃取一些逻辑以做到最好:http://msdn.microsoft.com/en-us/library/system.windows.controls.multiscaleimage%28v=vs.95%29.aspx

还有一件事。这可以通过 DeepZoom 轻松完成。由于它在 XAML 中尚不可用,这并不意味着您不能利用 WebView 来完成此操作。它将为您提供一个预先构建的、可疯狂扩展(且免费)的解决方案。但选择权在你。

【讨论】:

杰瑞,我认为当 ScrollViewer 不支持缩放手势时,需要使用 ScaleTransform 的方法。目前它与单张图片完美搭配——我可以非常流畅地放大。此外,我的 ScrollViewer 包含在 FlipView 控件中,来自 ScrollViewer 和 FlipView 控件的手势可以很好地协同工作:我可以在需要时翻转图像并放大。你是说在 ScrollViewer 中无法无缝切换图片?【参考方案2】:

有人记得这个帖子吗? =) 好的, 我在my app 上实现了“在缩放期间替换图像”。 正如上面提到的@Vitary,如果“内容”的大小发生变化,滚动查看器会重置缩放比例和 X/Y 偏移量。 为避免这种情况,我的应用 xaml 使用以下结构:

<FlipView>
  <DataTemplate>
    <ScrollViewer>
      <ViewBox> <-- This size is same with original image width/height. No change during zoom in/out.
        <Image/>  <-- This size is vary.
      </ViewBox>
    </ScrollViewer>
  </DataTemplate>
</FlipView>

通过保持 viewbox 的大小相同,可以避免滚动查看器的重置。 我的实现是...

    用户选择翻转视图项目。在这个序列中,viewsource 代码将原始图像的 w/h 设置为 viewsource 成员。此成员绑定到 viewbox 的 w/h。 进行低分辨率解码。然后通过绑定设置为图像控制。如您所知,viewbox 可以自动拉伸内容以适应 viewbox 的大小。 当用户做出缩放手势时,代码隐藏通知 ViewModel。 ViewModel 启动 original-res 解码,然后通过绑定将图像设置为图像控件。在这个序列中,滚动查看器可以保持偏移和缩放比例。因为,viewbox 的大小没有改变。从低分辨率到原始分辨率的切换非常流畅,没有故障。

【讨论】:

以上是关于在进行缩放手势时替换 ScrollViewer 中的图像的主要内容,如果未能解决你的问题,请参考以下文章

具有比例变换的 Grid 的 ScrollViewer

我们可以在 Android 中使用缩放手势检测器进行缩放吗?

UIImageView 手势(缩放、旋转)问题

在 iOS 10 上的 WKWebView 中打开 PDF 文件时启用捏合缩放手势

appcan平台中使用百度地图,在android手机上百度地图无法手势缩放

UIScrollView的缩放原理