本篇的最终目的,是模拟系统的照片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. 江豚科技|专业移动APP开发与移动互联网解决方案

    北京江豚科技(www.eoiiioe.com)是国内领先的移动APP开发解决方案服务商,总部在中国的硅谷--中关村,分别在郑州.深圳设有服务机构. 江豚科技承接各类移动app开发外包和软件定制开发,我 ...

  2. 跨平台web调试代理工具---whistle

    whistle是基于Node实现的跨平台web调试代理工具,支持windows.mac.linux等所有安装了Node的操作系统,可以部署在本地机器.虚拟机或远程服务器,并通过本地网页查看或修改HTT ...

  3. fidder 抓 https包配置方法(ios & android & pc浏览器)

    1. fidder抓https包的基本配置,可参见以下博文 http://blog.csdn.net/idlear/article/details/50999490 2. 遇到问题:抓包看只有Tunn ...

  4. 新建一个Activity

    如果只是新建一个class,还得自己添加XML,好不麻烦: eclipse里可以直接new other Andriod activity,ADT还是很强发滴.哈哈.

  5. liunx 套接字编程(Linux_C++)

    网络中的进程是如何通信的? 在网络中进程之间进行通信的时候,那么每个通信的进程必须知道它要和哪个计算机上的哪个进程通信.否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行 ...

  6. O2O地图应用之判断用户订单地址是否在服务范围内

    O2O地图应用之判断用户订单地址是否在服务范围内 需求分析 在o2o项目中,经常要用到在用户下单时判断用户所填地址的坐标点是否在服务范围内的情况,这里参考网上的实现方式,用C#来实现,经测试后有效,特 ...

  7. 【转】中国正爆发聊天APP大战 未来或影响西方

    [搜狐IT消息]8月15日消息,<金融时报>报道称,在中国,聊天应用程序的竞争日渐激烈,腾讯.阿里巴巴都加入大战,在西方市场的未来竞争中,中国聊天应用可能会成为先驱. 一些分析师认为,快速 ...

  8. 実行時にMicrosoft.ACE.OLEDB.12.0プロバイダーはローカルコンピュータに登録されていませんが出てしまう

    環境 Windows8 64bit Visual Studio 2010 Access 2010 32bit 接続プロバイダは「Microsoft.ACE.OLEDB.12.0」 対応 Downloa ...

  9. 伪装MAC地址

    一.界面操作法 打开"网上邻居",右键属性"本地连接",点击配置 选择"高级",再选"网路卡位址"(不同系统名字略不同) ...

  10. React-Native入门指导之iOS篇 —— 一、准备工作

    React-Native 入门指导系列教程目录 一.准备工作 (已完成) 二.项目介绍与调试 三.CSS样式与Flex布局 四.常用UI控件的使用 五.JSX在React-Native中的应用 六.事 ...