zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    多重背包POJ1276不要求恰好装满 poj1014多重背包恰好装满
    哈理工1053完全背包
    求最小公倍数与最大公约数的函数
    Bus Pass ZOJ 2913 BFS 最大中取最小的
    POJ 3624 charm bracelet 01背包 不要求装满
    HavelHakimi定理(判断一个序列是否可图)
    z0j1008Gnome Tetravex
    ZOJ 1136 Multiple BFS 取模 POJ 1465
    01背包 擎天柱 恰好装满 zjut(浙江工业大学OJ) 1355
    zoj 2412 水田灌溉,求连通分支个数
  • 原文地址:https://www.cnblogs.com/manupstairs/p/5557597.html
Copyright © 2011-2022 走看看