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;

  构造函数中设置支持的操作包括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,通过在空白处滑动屏幕,可以切换到另一张图片。虽然也不是什么大问题,但是用户老爷会不爽,那如何解决呢?那只有等下一篇了。

  GitHub:

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

时间: 2024-12-17 19:24:01

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

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

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

从零开始学ios开发(十五):Navigation Controllers and Table Views(中)

这篇内容我们继续上一篇的例子接着做下去,为其再添加3个table view的例子,有了之前的基础,学习下面的例子会变得很简单,很多东西都是举一反三,稍稍有些不同的内容,好了,闲话少说,开始这次的学习. 如果没有上一篇的代码,可以从这里下载Nav_1 1)第三个subtableview:Controls on Table Rows这个例子,我们将为每个table view的每一行添加一个按钮,这个按钮将放在accessory icon的位置(之前我们使用过accessoryType,其实这也是一个

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

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

[WebGL入门]十五,为多边形涂抹颜色(顶点颜色的指定)

注:文章译自http://wgld.org/,原作者杉本雅広(doxas),文章中如果有我的额外说明,我会加上[lufy:],另外,鄙人webgl研究还不够深入,一些专业词语,如果翻译有误,欢迎大家指正. 本次的demo的运行结果 增加顶点属性的个数 上次,终于绘制了一个三角形,但是只绘制了一个纯白色的多边形.这次,给多边形的顶点中添加颜色属性,基本上做的事情和上一篇文章一样,只是稍微增加点步骤而已.首先,就像以前多次重复的那样,顶点可以包含很多种情报(参考:顶点缓存和基础),而且每一个情报叫做

嵌入式Linux裸机开发(十五)——LCD

嵌入式Linux裸机开发(十五)--LCD 一.LCD简介 LCD(Liquid Crystal Display)是液晶显示器简称.LCD的构造是在两片平行的玻璃基板当中放置液晶盒,下基板玻璃上设置TFT(薄膜晶体管),上基板玻璃上设置彩色滤光片,通过TFT上的信号与电压改变来控制液晶分子的转动方向,从而达到控制每个像素点偏振光出射与否而达到显示目的. 1.LCD类型 按照背光源的不同,LCD可以分为CCFL和LED两种. A.CCFL 指用CCFL(冷阴极荧光灯管)作为背光光源的液晶显示器(L

QT开发(十五)——QT坐标系统

QT开发(十五)--QT坐标系统 一.QT坐标系简介 Qt中每一个窗口都有一个坐标系,默认窗口左上角为坐标原点,然后水平向右依次增大,水平向左依次减小,垂直向下依次增大,垂直向上依次减小.原点即为(0,0)点,以像素为单位增减. 二.坐标系变换 坐标系变换是利用变换矩阵来进行的, 通常利用QTransform类来设置变换矩阵.QPainter类提供了对坐标系的平移,缩放,旋转,扭曲等变换函数. void translate(const QPointF & offset) void transla

S3C2416裸机开发系列十五_GCC下uCOS的移植(1)

S3C2416裸机开发系列十五 GCC下uCOS的移植(1) 象棋小子    1048272975 操作系统是用来管理系统硬件.软件及数据资源,控制程序运行,并为其它应用软件提供支持的一种系统软件.根据不同的种类,又可分为实时操作系统.桌面操作系统.服务器操作系统等.对于一些小型的应用,对系统实时性要求高,硬件资源有限等的情况下,应尽量避免使用复杂庞大的操作系统(如Linux),使用小型的实时操作系统(如uCOS)更能满足应用的需求.笔者此处就uCOS-II的移植作一个简单的介绍. 1. 代码准

S3C2416裸机开发系列十五_GCC下uCOS的移植(2)

S3C2416裸机开发系列十五 GCC下uCOS的移植(2) 象棋小子    1048272975 4. uCOS配置 uCOS是可裁减实时操作系统,可以根据实际的应用对内核未使用到的功能进行裁减,以进一步节省系统宝贵的硬件资源,通常可用的uCOS-II内核代码在6K~26K,这在uCOS-II配置文件os_cfg.h中进行配置,这个配置文件在源码目录为os_cfg_r.h,从目录中拷贝添加到uCOS/uCOS-II/Cfg目录中,并重命名为os_cfg.h. #ifndef OS_CFG_H

Go语言开发(十五)、Go语言常用标准库五

Go语言开发(十五).Go语言常用标准库五 一.md5 1.md5简介 md5在crypto/md5包中,md5包提供了New和Sum方法. func New() hash.Hash func Sum(data []byte) [Size]byte hash.Hash继承了io.Writer,因此可以将其当成一个输入流进行内容的更新. type Writer interface { Write(p []byte) (n int, err error) } Write方法将p中的内容读入后存入到h