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

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

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

  首先我们先实现一个横向的可以浏览图片的功能,也是大部分APP中的实现。最简单的方式是使用FlipView,再将FlipView的ItemTemplate设置成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();
        }

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

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

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

 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无法撑满FlipViewItem,Manipulation事件不经过ScalableGrid。这种情况我需要在ScalableGrid外层再套一个Panel或Border遮盖整个FlipViewItem的面积,然后绑定二者的ManipulationMode。

  实际情况比想象的还要简单,我只需要设置ScalableGird的Background属性为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

时间: 2024-12-03 12:55:15

UWP开发入门(十五)——在FlipView中通过手势操作图片的相关文章

UWP开发入门(五)——自定义Panel

原文:UWP开发入门(五)--自定义Panel 各位好,终于讲到自定义Panel了.当系统自带的几个Panel比如Gird,StackPanel,RelativePanel不能满足我们的特定要求时(其实不常见啦),自定义Panel就显得非常必要,而且因为是针对性的处理,效果也会非常好.更何况自定义Panel其实并不复杂,今天俺们就来学习一下. 记得上一篇自定义CommandBar在增加占位控件AppBarEmpty时,采用的是通过Page的SizeChanged事件中计算页面Width,减去Co

Windows 8风格应用开发入门 十五 ShareContract构建

共享数据包 DataPackage(数据包)是共享数据标准. 共享数据格式可以是多种的,包括文本.URI.HTML.图像等,以及更多可扩展的格式. 我们可以通过以下方法设置需要的分享数据类型: 1) SetText():设置共享文本,例如: 开发入门 十五 ShareContract构建-sharepoint入门教程"> 可以实现效果: 2) SetUri():设置共享Uri; 3) SetHtmlFormat():设置共享Html; 4) SetBitmap():设置共享位图,例如:

【IOS-COCOS2D-X 游戏开发之十五】COCOS2DX中响应ANDROID的BACK(返回)与MENU(小房子)事件&amp;&amp;COCOS2DX自动释放粒子内存函数!

本站文章均为 李华明Himi 原创,转载务必在明显处注明:  转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/iphone-cocos2dx/792.html 本篇介绍两个常用知识,一个是粒子的自动释放机制函数,因为不少童鞋说cocos2dx为什么没有? 其实是没找到,这里特意拿出来说下!另外一个是如何响应Android的Menu菜单和Back返回事件的方法: 首先对于Cocos2dx中对于粒子自动释放的函数Himi这里给出,不少童鞋都在群里或者论坛

Kinect for Windows SDK开发入门(十五)进阶指引 下

上一篇文章介绍了Kinect for Windows SDK进阶开发需要了解的一些内容,包括影像处理Coding4Fun Kinect工具类库以及如何建立自己的扩展方法类库来方便开发,接下来介绍了利用Kinect进行近距离探测的一些方法,限于篇幅原因,仅仅介绍了近距离探测的三种方式.  本文接上文将继续介绍近距离探测中如何探测运动,如何获取并保存产生的影像数据:然后将会介绍如何进行脸部识别,以及介绍全息图(Holograme)的一些知识,最后介绍了一些值得关注的类库和项目. 2.4 运动识别 目

UWP开发入门(十四)—— UserControl中Adaptive UI的小技巧

原文:UWP开发入门(十四)-- UserControl中Adaptive UI的小技巧 本篇我们通过绘制一个非常简单的UserControl控件,来分享一下对Adaptive UI的理解及一些图形绘制的技巧. 现在流行的APP都少不了精致的用户头像,首先假设我们需要绘制如下的图形作为默认头像: <UserControl x:Class="AdaptiveUserControl.Circle0" xmlns="http://schemas.microsoft.com/w

UWP开发入门(十九)——10分钟学会在VS2015中使用Git

原文:UWP开发入门(十九)--10分钟学会在VS2015中使用Git 写程序必然需要版本控制,哪怕是个人项目也是必须的.我们在开发UWP APP的时候,VS2015默认提供了对微软TFS和Git的支持.考虑到现在Git很火,作为微软系的程序员也不得不学一点防身,以免被开源世界的家伙们嘲笑.蜀黍我Git也是菜鸟一只(还请老司机多多指点),只会用VS2015和SourceTree这样的GUI工具点一点按钮,但是我相信用惯了SVN和TFS的童鞋们,需要一点勇气去学习一些新东西,特别是Git已经形成潮

UWP开发入门(十二)——神器Live Visual Tree

原文:UWP开发入门(十二)--神器Live Visual Tree 很久以前,我们就有Snoop这样的工具实时修改.查看正在运行的WPF程序,那时候调个样式,修改个模板,相当滋润.随着历史的车轮陷进WP的泥潭中,无论WP7的Silverlight还是WP8.1的runtime,偶们都不能方便快捷的查看APP的可视化树(Visual Tree)了,呜呼哉,是可忍孰不可忍放下筷子就骂微软.没想到Visual Studio 2015倒是给了我们一个惊喜,自带了一套非常强大的调试工具Live Visu

UWP开发入门(十)——通过继承来扩展ListView

原文:UWP开发入门(十)--通过继承来扩展ListView 本篇之所以起这样一个名字,是因为重点并非如何自定义控件,不涉及创建CustomControl和UserControl使用的Template和XAML概念.而是通过继承的方法来扩展一个现有的类,在继承的子类中增加属性和扩展行为. 我们在<UWP开发入门(七)--下拉刷新>中提到过嵌套ScrollViewer的实现思路,本篇我们对ListView的第一个扩展行为,即是摒弃嵌套的做法,而是通过访问ListView内部的ScrollView

UWP开发入门(十六)——常见的内存泄漏的原因

原文:UWP开发入门(十六)--常见的内存泄漏的原因 本篇借鉴了同事翔哥的劳动成果,在巨人的肩膀上把稿子又念了一遍. 内存泄漏的概念我这里就不说了,之前<UWP开发入门(十三)--用Diagnostic Tool检查内存泄漏>中提到过,即使有垃圾回收机制,写C#还是有可能发生内存泄漏. 一般来说,以下两种情况会导致内存泄漏: 对象用完了但是没有释放资源 对象本身是做了清理内存的操作,但是对象内部的子对象没有成功释放资源 下面就UWP开发中具体的实例来说明需要避免的写法 从static/glob