• UWP开发入门(十五)——在FlipView中通过手势操作图片


      本篇的最终目的,是模拟系统的照片APP可以左右滑动,缩放图片的操作。在实现的过程中,我们会逐步分析UWP编写UI的一些思路和技巧。

      首先我们先实现一个横向的可以浏览图片的功能,也是大部分APP中的实现。最简单的方式是使用FlipView,再将FlipViewItemTemplate设置成Image。大体代码如下:

        <FlipView ItemsSource="{Binding Photos,Mode=OneTime}">
            <FlipView.ItemTemplate>
                <DataTemplate>
                    <Image Source="{Binding ImageUri,Mode=OneTime}"></Image>
                </DataTemplate>
            </FlipView.ItemTemplate>
        </FlipView>

      上述代码很简单,同时效果也非常好。问题图片如果纵横比例较大,比如长微博那种竖长的图片在手机上就没法方便地阅读了。这时候我们需要能够缩放和拖动图片,对图片的局部进行观察。请注意这是一个强需求!特别是打开一张柳岩照片却尴尬地发现无法缩放时的强需求!

      分析一下我们遇到的问题,需要支持手势对图片的缩放和移动。UWP里一般通过UIElement类型的Manipulation相关事件来处理。接下来我们来创建一个支持手势的控件。

      一开始的想法是继承Image来实现一个支持缩放的ScalableImage,但不幸的是Image类是不允许继承的sealed类型。那我们索性搞大一点,实现一个ScalableGrid,该Grid允许将内部的元素通过Manipulation进行操作。

        public class ScalableGrid : Grid
        {
            private TransformGroup transformGroup;
            private ScaleTransform scaleTransform;
            private TranslateTransform translateTransform;
    
            public ScalableGrid()
            {
                this.scaleTransform = new ScaleTransform();
                this.translateTransform = new TranslateTransform();
                this.transformGroup = new TransformGroup();
                this.transformGroup.Children.Add(scaleTransform);
                this.transformGroup.Children.Add(translateTransform);
                this.RenderTransform = transformGroup;
    
                this.ManipulationMode = ManipulationModes.System | ManipulationModes.Scale;
                this.ManipulationDelta += ScalableGrid_ManipulationDelta;
                this.Loaded += ScalableGrid_Loaded;
                this.SizeChanged += (a, b) =>
                {
                    this.scaleTransform.CenterX = this.ActualWidth / 2;
                    this.scaleTransform.CenterY = this.ActualHeight / 2;
                };
                this.DoubleTapped += ScalableGrid_DoubleTapped;
            }
    
            private void ScalableGrid_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
            {
                scaleTransform.ScaleX = scaleTransform.ScaleY = 1;
                this.translateTransform.X = 0;
                this.translateTransform.Y = 0;
                this.ManipulationMode = ManipulationModes.System | ManipulationModes.Scale;
            }
    
            private void ScalableGrid_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
            {
                this.Loaded -= ScalableGrid_Loaded;
                scaleTransform.CenterX = this.ActualWidth / 2;
                scaleTransform.CenterY = this.ActualHeight / 2;
            }
    
            private void ScalableGrid_ManipulationDelta(object sender, Windows.UI.Xaml.Input.ManipulationDeltaRoutedEventArgs e)
            {
                if (scaleTransform.ScaleX == 1 && scaleTransform.ScaleY == 1)
                {
                    this.ManipulationMode = ManipulationModes.System | ManipulationModes.Scale;
                }
                else
                {
                    this.ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY | ManipulationModes.Scale | ManipulationModes.TranslateInertia;
                }
    
                scaleTransform.ScaleX *= e.Delta.Scale;
                scaleTransform.ScaleY *= e.Delta.Scale;
                if (scaleTransform.ScaleY < 1)
                {
                    scaleTransform.ScaleX = scaleTransform.ScaleY = 1;
                }
    
                translateTransform.X += e.Delta.Translation.X;
                translateTransform.Y += e.Delta.Translation.Y;
                StopWhenTranslateToEdge();
            }

      TranslateTransformScaleTransform分别对应平移操作和缩放操作。

    this.ManipulationMode = ManipulationModes.System | ManipulationModes.Scale;

      ManipulationMode在构造函数中,初始设置支持SystemScale,没有TranslateXTranslateY是因为初始打开的时候不希望可以有平移操作,只有缩放后,才根据放大的具体情况放开对平移的支持。

     this.SizeChanged += (a, b) =>
                {
                    this.scaleTransform.CenterX = this.ActualWidth / 2;
                    this.scaleTransform.CenterY = this.ActualHeight / 2;
                };

      SizeChanged事件是为了在窗口大小变化,比如桌面缩放窗口或手机横竖屏切换时,重新定位缩放的中心点。

            private void ScalableGrid_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
            {
                scaleTransform.ScaleX = scaleTransform.ScaleY = 1;
                this.translateTransform.X = 0;
                this.translateTransform.Y = 0;
                this.ManipulationMode = ManipulationModes.System | ManipulationModes.Scale;
            }

      DoubleTapped事件是为了双击还原到初始状态。

      对手势的支持代码是在private void ScalableGrid_ManipulationDelta(object sender, Windows.UI.Xaml.Input.ManipulationDeltaRoutedEventArgs e)方法中。其中判断Scale大于1,也就是放大后才支持平移操作。同时去除System枚举,这是因为不希望对图片的平移被判断为滑动FlipView控件,导致切换Image。

       StopWhenTranslateToEdge()方法是希望避免将图片滑出屏幕边缘导致无法继续操作。

      将完成的ScalableGrid放置到FlipView的ItemTemplate中:

        <FlipView ItemsSource="{Binding Photos,Mode=OneTime}">
            <FlipView.ItemTemplate>
                <DataTemplate>
                    <local:ScalableGrid>
                        <Image Source="{Binding ImageUri,Mode=OneTime}"></Image>
                    </local:ScalableGrid>
                </DataTemplate>
            </FlipView.ItemTemplate>
        </FlipView>

      至此,一个滑动查看图片的功能算是完成了。我们可以左右切换图片,对FilpView的某一张图片进行缩放和平移的操作,阅读长微博也不是问题。

      那是不是完美无缺了呢?变态的用户们会发现,我们在放大图片后,如果当前的图片没有撑满整个FilpViewItem,通过在空白处滑动屏幕,可以切换到另一张图片。虽然也不是什么大问题,但是用户老爷会不爽,那如何解决呢?我们祭出神器Live Visual Tree,来检查一下到底是谁无视当前的ManipulationMode,硬是将手势事件传递给了FilpView

      

      从截图中的Visual Tree可以看出,选中ScalableGrid时,Gird实际是撑满整个FilpViewItem的,也就是说ScalableGrid在非图片区域不作为,不仅没有截获处理内部的Manipulation事件,反而直接冒泡传递给了上层FilpViewItem

      原先我的猜测是ScalableGird无法撑满FlipViewItemManipulation事件不经过ScalableGrid。这种情况我需要在ScalableGrid外层再套一个PanelBorder遮盖整个FlipViewItem的面积,然后绑定二者的ManipulationMode

      实际情况比想象的还要简单,我只需要设置ScalableGirdBackground属性为Transparent即可。最终的XAML如下:

        <FlipView ItemsSource="{Binding Photos,Mode=OneTime}">
            <FlipView.ItemTemplate>
                <DataTemplate>
                    <local:ScalableGrid Background="Transparent">
                        <Image Source="{Binding ImageUri,Mode=OneTime}" Stretch="None"></Image>
                    </local:ScalableGrid>
                </DataTemplate>
            </FlipView.ItemTemplate>
        </FlipView>

      好了,可以用你的Lumia 950XL或者Surface Pro 4来试一试了,没有的话赶紧去买,最近大降价了,你值得拥有。另外StopWhenTranslateToEdge的算法实现得不是很好,期待评论中有好的思路,最好能不依赖外部UIElement  

      GitHub

      https://github.com/manupstairs/UWPSamples/tree/master/UWPSamples/PhotosBrowser

  • 相关阅读:
    PHP设计模式
    秒杀方案
    lua 安装
    docker 相关命令
    dockerfile
    JS工具对象 DATE 方法
    JS工具对象 Array
    JS工具对象 String 10种常用 方法
    工具对象
    JS工具对象Math 7个常用 方法
  • 原文地址:https://www.cnblogs.com/manupstairs/p/5557597.html
Copyright © 2020-2023  润新知