本篇的最终目的,是模拟系统的照片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 / ;
this.scaleTransform.CenterY = this.ActualHeight / ;
};
this.DoubleTapped += ScalableGrid_DoubleTapped;
} private void ScalableGrid_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
scaleTransform.ScaleX = scaleTransform.ScaleY = ;
this.translateTransform.X = ;
this.translateTransform.Y = ;
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 / ;
scaleTransform.CenterY = this.ActualHeight / ;
} private void ScalableGrid_ManipulationDelta(object sender, Windows.UI.Xaml.Input.ManipulationDeltaRoutedEventArgs e)
{
if (scaleTransform.ScaleX == && scaleTransform.ScaleY == )
{
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 < )
{
scaleTransform.ScaleX = scaleTransform.ScaleY = ;
} 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 / ;
this.scaleTransform.CenterY = this.ActualHeight / ;
};

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

        private void ScalableGrid_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
scaleTransform.ScaleX = scaleTransform.ScaleY = ;
this.translateTransform.X = ;
this.translateTransform.Y = ;
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

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

  1. Android开发(十五)——ListView中Items的间距margin

    ListView中Items没有margin 参考:http://www.cnblogs.com/xitang/p/3677528.html

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

    各位好,终于讲到自定义Panel了.当系统自带的几个Panel比如Gird,StackPanel,RelativePanel不能满足我们的特定要求时(其实不常见啦),自定义Panel就显得非常必要,而 ...

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

    本篇借鉴了同事翔哥的劳动成果,在巨人的肩膀上把稿子又念了一遍. 内存泄漏的概念我这里就不说了,之前<UWP开发入门(十三)——用Diagnostic Tool检查内存泄漏>中提到过,即使有 ...

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

    本篇之所以起这样一个名字,是因为重点并非如何自定义控件,不涉及创建CustomControl和UserControl使用的Template和XAML概念.而是通过继承的方法来扩展一个现有的类,在继承的 ...

  5. UWP开发入门系列笔记之(一):UWP初览

    标签: 随着微软Build2015带来的好消息,Win10正式版发布的日子已经离我们越来越近了,我们也终于欣喜地看到:一个统一的Windows平台对于开发人员来说充满了吸引力,这局棋下的好大的说--于 ...

  6. UWP开发入门(四)——自定义CommandBar

    各位好,再次回到UWP开发入门系列,刚回归可能有些不适应,所以今天我们讲个简单的,自定义CommandBar,说通俗点就是自定义类似AppBarButton的东西,然后扔到CommandBar中使用. ...

  7. 十五、struts2中的拦截器(框架功能核心)

    十五.struts2中的拦截器(框架功能核心) 1.过滤器VS拦截器 功能是一回事. 过滤器是Servlet规范中的技术,可以对请求和响应进行过滤. 拦截器是Struts2框架中的技术,实现AOP(面 ...

  8. 学习笔记:CentOS7学习之二十五:shell中色彩处理和awk使用技巧

    目录 学习笔记:CentOS7学习之二十五:shell中色彩处理和awk使用技巧 25.1 Shell中的色彩处理 25.2 awk基本应用 25.2.1 概念 25.2.2实例演示 25.3 awk ...

  9. UWP开发入门(25)——通过Radio控制Bluetooth, WiFi

    回顾写了许久的UWP开发入门,竟然没有讲过通过Windows.Devices.Radios.Radio来控制Bluetooth和WiFi等功能的开关.也许是因为相关的API设计的简单好用,以至于被我给 ...

随机推荐

  1. python ide: pycharm

    1, 设置python路径 2,运行py文件 https://www.jetbrains.com/help/pycharm/2016.1/creating-and-running-your-first ...

  2. mysql中You can’t specify target table for update in FROM clause错误解决方法

    mysql中You can't specify target table for update in FROM clause错误的意思是说,不能先select出同一表中的某些值,再update这个表( ...

  3. ELK——Elasticsearch 搭建集群经验

    本文内容 背景 ES集群中第一个master节点 ES slave节点 本文总结 Elasticsearch(以下简称ES)搭建集群的经验.以 Elasticsearch-rtf-2.2.1 版本为例 ...

  4. Spectrum to XYZ to sRGB

    如何将频谱响应转换为对应的RGB显示值: 首先要在频率功率分布(SPD)曲线的基础上,分别使用X/Y/Z三个频率匹配曲线(spectral matching curves,又名CIE XYZ Colo ...

  5. 关于Server Sql 2008触发器的使用

    1.创建同一个服务器下同一个数据库实例两个不同数据库表同步方法 --==================================== -- Create database trigger -- ...

  6. Quartz 2D绘制简单图形

    在Quartz 2D中,绘图是通过图形上下文进行绘制的,以下绘制几个简单的图形 首先先创建一个QuartzView.swift文件继承自UIView,然后实现drawRect方法: import UI ...

  7. Linux环境的PHP执行

    /usr/local/php5/bin/php -c /var/spool/php.ini -q /var/spool/auto.php

  8. JPA与Hibernate的关系

    1.JPA JPA全称: Java Persistence API  JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中.  JPA的出现?  JPA ...

  9. nginx 相关问题

    Nginx配置文件nginx.conf 参考:http://www.2cto.com/os/201212/176520.html Nginx自动切分日志: nignx没有自动分开文件存储日志的机制. ...

  10. css3 定义选择器

    引言:CSS样式规则有两个主要部分.选择器决定将格式化应用到哪些元素.声明则定义要应用的格式化. 构造选择器 选择器可以定义五个不同的标准来选择要进行格式化的元素. 元素的类型或者名称.如下所示. h ...