背景:

之前用ScrollViewer 来做过 PullToRefresh的控件,在项目一些特殊的条件下总有一些问题,比如ScrollViewer不会及时到达指定位置。
于是便有了使用Composition API来重新实现PullToRefresh控件。本控件的难点不是实现,而是对Composition API的一些探索。

本文的一些观点或者说结论不一定是全对的,都是通过实验得到的,Composition API 可用的资料实在是太少了。

成品效果图:

资料:

Composition API 资料

1.官方Sample

2. 原作者 Nick Waggoner 供职于微软 native Windows UI platform 链接是对文章的翻译 VALID VOID

3.比较旧的资料

在网上查阅了些资料,看到网上有大神已经实现过了。了解了大概的实现过程,因为自己想做的效果还是跟大神的有差距的,所以

还是自己动手封装成控件。

实现原理:

这里引用 VALID VOID里面的话

输入驱动动画

自大约五年前触摸渐成主流起,创造低延迟体验成为了一种普遍需求。使用手指或笔在屏幕上操作,使得人眼获得了更直观的参照点来辨识操作的延迟和流畅性。为使操作流畅,主流操作系统公司均将更多的操作移交至系统和 GPU (如 ChromeIE)执行。在 Windows 上,这由 DirectManipulation 这一或多或少是针对于触摸构建的动画引擎实现的。它解决了关键的延迟挑战,也就是如何自然地以展示从输入驱动到事件驱动过渡的动效。但另一方面,它也几乎没有提供对定制惯性观感的支持,就像福特 T 型车那样——“只要车是黑色的,你可以把它涂成任意你喜欢的颜色”。2

ElementCompositionPreview.GetScrollViewerManipulationPropertySet 是让你能够把玩输入驱动动效的第一步。虽然它仍然没给你任何对内容滚动时观感进行控制的额外能力,但它确实允许你对次级内容应用表达式动画。例如,我们终于能完成我们的基础视差滚动代码:

  1. // 创建驱动视差滚动的表达式动画。
  2. ExpressionAnimation parallaxAnimation = compositor.CreateExpressionAnimation("MyForeground.Translation.Y / MyParallaxRatio");
  3. // 设置对前景对象的引用。
  4. CompositionPropertySet MyPropertySet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(MyScrollViewer);
  5. parallaxAnimation.SetReferenceParameter("MyForeground", MyPropertySet);
  6. // 设置背景对象视差滚动的速度。
  7. parallaxAnimation.SetScalarParameter("MyParallaxRatio", 0.5f);
  8. // 对背景对象开始视差动画。
  9. backgroundVisual.StartAnimation("Offset.Y", parallaxAnimation);

使用这一技巧,你能够实现多种优秀的效果:视差滚动、粘性表头、自定义滚动条等等。唯一缺失的就是定制操作本身的观感……

我讲一下我的理解:使用过ScrollViewer 和Manipulation相关事件的童鞋都知道,想要得到一些ScrollViewer 触摸的详情太难了,

DirectManipulationStarted和DirectManipulationCompleted得到信息太少了,而Manipulation其他的事件又需要设置ManipulationMode,这样全部的情况都要你自己来处理。当看到

ElementCompositionPreview.GetScrollViewerManipulationPropertySet(MyScrollViewer); 

的时候,你是不是感觉有点亲切。看上去你拿到了ScrollViewer 的一些Manipulation 的信息。。话说这里是最最坑爹了,

MyForeground 其实是GetScrollViewerManipulationPropertySet返回的东东,

但MyForeground.Translation.Y这是什么鬼东西。。Manipulation 的Translation??? 后来我在网上搜索了一下,

但我查了下CompositionPropertySet 并没发现有相关Translation的属性啊? 

难道跟Manipulation事件里面的参数里面的Translation 是一样的吗??这是我的推测。

网上都是这样用的,但是没有文档。。除了Translation不知道还有其他属性能使用不。

暂时没有寻找到答案,希望知道的童鞋可以留言,万分感激。。。

上面这段的代码的意思就是说把Manipulation.Translation.Y 映射到backgroundVisual的Offset.Y上面。

也就是说你现在已经可以找到ScrollViewer滚动的时候一些有用的数值了。。值得说的是,用鼠标滚动的时候 映射依然能生效。

这种映射是实时的,是会有惯性效果的。

实现过程:

因为要用到ScrollViewer,所以我做了2种,一种是刷新内容第一元素是ScrollViewer的,一种不是ScrollViewer的。

如果刷新内容第一元素是ScrollViewer,模板如下:

  1. <ControlTemplate TargetType="local:PullToRefreshGrid1">
  2. <Grid>
  3. <ContentControl x:Name="Header" Opacity="" VerticalAlignment="Top" ContentTemplate="{TemplateBinding HeaderTemplate}" HorizontalContentAlignment="Center" VerticalContentAlignment="Bottom" />
  4. <ContentPresenter x:Name="Content" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
  5. </Grid>
  6. </ControlTemplate>

如果刷新内容第一元素不是ScrollViewer,那么我为它添加了一个ScrollViewer:

  1. <ControlTemplate TargetType="local:PullToRefreshGrid">
  2. <Grid>
  3. <ContentControl x:Name="Header" Opacity="" VerticalAlignment="Top" ContentTemplate="{TemplateBinding HeaderTemplate}" HorizontalContentAlignment="Center" VerticalContentAlignment="Bottom" />
  4. <ScrollViewer x:Name="ScrollViewer" VerticalSnapPointsType="MandatorySingle" VerticalSnapPointsAlignment="Near"
  5. VerticalScrollMode="Enabled" VerticalScrollBarVisibility="Hidden" VerticalContentAlignment="Stretch" VerticalAlignment="Stretch">
  6. <ContentPresenter x:Name="Content" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
  7. </ScrollViewer>
  8. </Grid>
  9. </ControlTemplate>

当然,其实第2种也是统用的,我只是想减少控件的Child,如果你不知道刷新内容里面有没有ScrollViewer,那么用第2种就好了。有人会说为啥控件的名字这么奇怪。。那是因为我之前也写过PullToRefresh控件(PullToRefreshControl,PullToRefreshPanel),我把名字都想完了。。实在想不出更好的了。。大家体谅下。。( ╯□╰ )

好了,重点就是拿到这个ScrollViewer 然后跟Header,产生某种关系(你懂的)。。。

  1. if (RefreshThreshold == 0.0)
  2. {
  3. RefreshThreshold = headerHeight;
  4. }
  5. ratio = RefreshThreshold / headerHeight;
  6.  
  7. _offsetAnimation = _compositor.CreateExpressionAnimation("(min(max(0, ScrollManipulation.Translation.Y * ratio) / Divider, 1)) * MaxOffsetY");
  8. _offsetAnimation.SetScalarParameter("Divider", (float)RefreshThreshold);
  9. _offsetAnimation.SetScalarParameter("MaxOffsetY", (float)RefreshThreshold * / );
  10. _offsetAnimation.SetScalarParameter("ratio", (float)ratio);
  11. _offsetAnimation.SetReferenceParameter("ScrollManipulation", _scrollerViewerManipulation);
  12.  
  13. _opacityAnimation = _compositor.CreateExpressionAnimation("min((max(0, ScrollManipulation.Translation.Y * ratio) / Divider), 1)");
  14. _opacityAnimation.SetScalarParameter("Divider", (float)headerHeight);
  15. _opacityAnimation.SetScalarParameter("ratio", (float));
  16. _opacityAnimation.SetReferenceParameter("ScrollManipulation", _scrollerViewerManipulation);
  17.  
  18. _headerVisual = ElementCompositionPreview.GetElementVisual(_header);
  19.  
  20. _contentVisual = ElementCompositionPreview.GetElementVisual(_scrollViewerBorder);
  21.  
  22. _headerVisual.StartAnimation("Offset.Y", _offsetAnimation);
  23. _headerVisual.StartAnimation("Opacity", _opacityAnimation);
  24. _contentVisual.StartAnimation("Offset.Y", _offsetAnimation);

RefreshThreshold 是到达Release to refresh的一个点。。可以由用户设定,默认是header的高度。

MaxOffsetY 是当到达RefreshThreshold之后我还能拖动的最大值,这里设置为RefreshThreshold 的5/4。

  1. (min(max(, ScrollManipulation.Translation.Y * ratio) / Divider, )) * MaxOffsetY

我再来讲讲这个表达式的意思,ScrollManipulation.Translation.Y 大家都已经知道了。是ScrollViewer进行Manipulation的Y值,向下是正值,向上时负值,初始为0.

综合起来就是最大值为MaxOffsetY最小值为0.。这个速率是根据RefreshThreshold/headerHeight来的,因为你会发现,你向下drag scrollViewer的时候是有一定最大值的,当RefreshThreshold比较大的时候,你很难ScrollManipulation.Translation.Y值达到RefreshThreshold。

  1. min((max(, ScrollManipulation.Translation.Y * ratio) / Divider), )

这个也比较简单,是给header的Opaicty做了一个动画。

  1. _headerVisual.StartAnimation("Offset.Y", _offsetAnimation);
  2. _headerVisual.StartAnimation("Opacity", _opacityAnimation);
  3. _contentVisual.StartAnimation("Offset.Y", _offsetAnimation);

完成之后我们将动画开始就好了。这样在向下拖scrollviewer的时候,scrollviewer和header就都会向下移动。

接下来我们需要监听 offset。

  1. private void ScrollViewer_DirectManipulationStarted(object sender, object e)
  2. {
  3. Windows.UI.Xaml.Media.CompositionTarget.Rendering += OnCompositionTargetRendering;
  4. _refresh = false;
  5. _header.Opacity = ;
  6. }

在开始manipulat的时候,注册CompositionTarget.Rendering事件。在这个事件里面我们就可以实时获得offset的变化了。

  1. private void OnCompositionTargetRendering(object sender, object e)
  2. {
  3. _headerVisual.StopAnimation("Offset.Y");
  4.  
  5. var offsetY = _headerVisual.Offset.Y;
  6. IsReachThreshold = offsetY >= RefreshThreshold;
  7. _scrollViewerBorder.Clip = new RectangleGeometry() { Rect = new Rect(, , _content.Width, _content.Height - offsetY) };
  8. Debug.WriteLine(IsReachThreshold + "," + _headerVisual.Offset.Y + "," + RefreshThreshold);
  9. _headerVisual.StartAnimation("Offset.Y", _offsetAnimation);
  10.  
  11. if (!_refresh)
  12. {
  13. _refresh = IsReachThreshold;
  14. }
  15.  
  16. if (_refresh)
  17. {
  18. _pulledDownTime = DateTime.Now;
  19. }
  20.  
  21. if (_refresh && offsetY <= )
  22. {
  23. _releaseTime = DateTime.Now;
  24. }
  25.  
  26. }

这里有点坑爹的是,发现如果不StopAnimation,那么Offset.Y永远都是0.。。很囧啊。。

最后我们在ScrollViewer_DirectManipulationCompleted事件里面处理是否要 触发PullToRefresh事件就ok了。

  1. private void ScrollViewer_DirectManipulationCompleted(object sender, object e)
  2. {
  3. Windows.UI.Xaml.Media.CompositionTarget.Rendering -= OnCompositionTargetRendering;
  4.  
  5. var cancelled = (_releaseTime - _pulledDownTime) > TimeSpan.FromMilliseconds();
  6.  
  7. if (_refresh)
  8. {
  9. _refresh = false;
  10. if (cancelled)
  11. {
  12. Debug.WriteLine("Refresh cancelled...");
  13. }
  14. else
  15. {
  16. Debug.WriteLine("Refresh now!!!");
  17. if (PullToRefresh != null)
  18. {
  19. _headerVisual.StopAnimation("Offset.Y");
  20. LastRefreshTime = DateTime.Now;
  21. _headerVisual.StartAnimation("Offset.Y", _offsetAnimation);
  22. PullToRefresh(this, null);
  23. }
  24. }
  25. }
  26. }

最后说下Header模板(HeaderTemplate)是可以定义的。。它的DataContext是绑定到这个控件上的,有用的属性有(LastRefreshTime,IsReachThreshold等),你可以用它们创造你属于你喜欢的Header样式。

通过本控件,初步了解Composition API 的一些用法。下一篇,我会讲讲一些更多的探索。。

开源有益,源码GitHub地址

问题:

1.Visual 是继承IDisposable,我们需要在什么时候Dispose 掉它呢? 还是它自己管理的?

我试过在unload的时候去dispose它。但是会出了Win32的异常。。官方sample里面对这个也讲的不清楚。

希望知道的童鞋能留言告知,万分感谢。另外希望有对这个比较了解的童鞋能提供一些sample,资料,再次感谢。

补充:
使用条件:

  1. // Windows build 10240 and later.
  2. if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 1))
  3. {
  4. ...
  5. }
  6. // Windows build10586 and later.
  7. if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 2))
  8. {
  9. ...
  10. }
  11. // Windows build14332 and later.
  12. if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3))
  13. {
  14. ...
  15. }
  1. 调试了下
  1. 1. Windows build14332 and later 123都为true
    2. Windows build10586 and later 12true
  1. 3. Windows build 10240 and later: 1true

因为10586之前的版本是不支持Composition API的。所以使用的时候记得判断:

  1. // Windows build10586 and later.
  2. if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 2))
  3. {
  4. ...
  5. }
  1.  
  1.  

UWP Composition API - PullToRefresh的更多相关文章

  1. UWP Composition API - GroupListView(一)

    需求: 光看标题大家肯定不知道是什么东西,先上效果图: 这不就是ListView的Group效果吗?? 看上去是的.但是请听完需求.1.Group中的集合需要支持增量加载ISupportIncreme ...

  2. UWP Composition API - New FlexGrid 锁定行列

    如果之前看了 UWP Jenkins + NuGet + MSBuild 手把手教你做自动UWP Build 和 App store包 这篇的童鞋,针对VS2017,需要对应更新一下配置,需要的童鞋点 ...

  3. UWP Composition API - 锁定列的FlexGrid

    需求是第一列锁定,那么怎么让锁定列不跟着滚动条向做移动呢? 其实很简单,让锁定列跟scrollviewer的滚动做反方向移动. 先看一下这个控件的模板,嗯,其实很简单,就是ListView的模板,不同 ...

  4. UWP Composition API - GroupListView(二)

    还是先上效果图: 看完了上一篇UWP Composition API - GroupListView(一)的童鞋会问,这不是跟上一篇一样的吗??? 骗点击的?? No,No,其实相对上一个有更简单粗暴 ...

  5. UWP Composition API - RadialMenu

    用Windows 8.1的童鞋应该知道OneNote里面有一个RadialMenu.如下图,下图是WIn10应用Drawboard PDF的RadialMenu,Win8.1的机器不好找了.哈哈,由于 ...

  6. UWP中使用Composition API实现吸顶(1)

    前几天需要在UWP中实现吸顶,就在网上找了一些文章: 吸顶大法 -- UWP中的工具栏吸顶的实现方式之一 在UWP中页面滑动导航栏置顶 发现前人的实现方式大多是控制ListViewBase的Heade ...

  7. win10 UWP 等级控件Building a UWP Rating Control using XAML and the Composition API | XAML Brewer, by Diederik Krols

    原文:Building a UWP Rating Control using XAML and the Composition API | XAML Brewer, by Diederik Krols ...

  8. [UWP小白日记-12]使用新的Composition API来实现控件的阴影

    前言 看了好久官方的Windows UI Dev Labs示例好久才有点心得,真是头大.(其实是英语幼儿园水平(⊙﹏⊙)b) 真的网上关于这个API的资料可以说几乎没有. 正文 首先用这东西的添加WI ...

  9. Windows Composition API 指南 - 认识 Composition API

    微软在 Windows 10中 面向通用 Windows 应用 (Universal Windows Apps, UWA) 新引入了一套用于用户界面合成的 API:Composition API.Co ...

随机推荐

  1. Salesforce ADM201备考心得

    Salesforce拥有很多针对不同角色的认证考试.ADM201是面对初级管理员的认证. 考试形式是单选题和多选题(如果是多选题,题干上会提示你要多选),两个小时时间,60道题目.内容涉及管理Sale ...

  2. 【先定一个小目标】怎么解决mysql不允许远程连接的错误

    最近使用Navicat for MySQl访问远程mysql数据库,出现报错,显示“1130 - Host'xxx.xxx.xxx.xxx' is not allowed to connect to ...

  3. Oracle下批量将一个用户的所有表的select权限赋值给另外一个用户

    起因 为什么会有这篇文章呢? 因为最近在做项目的时候遇到一个问题...实际生产环境中程序datasource登陆的Oracle数据库用户user1不是我们创建的.这个用户没有访问我们业务表的权限(因为 ...

  4. 第一章 --- 关于Javascript 设计模式 之 单例模式

    首先我们对单例模式先进行理论上的讲解,接下来,我们再通过具体的代码示例,来讲解,这个单例模式的使用场景和这种模式的优缺点 (这个系列的所有关于设计模式的都是面向Javascript) 一.理论定义: ...

  5. thinkphp如何一次性的上传多个文件,在文件域中可以多选?

    可以做到类似于某度网盘的样式吗? 文件夹的命名, 可以用单数, 也可以用复数, 在同一个项目中, 只要统一就好了. 毕竟项目开发不同于英语写作. 建议使用缩写, 不管是不是缩写都用单数, 这样简洁,容 ...

  6. Android 项目结构图

    src:存放Java源代码 gen:存放系统自动生成的配置文件 Android 4.4.2:包含Android.jar文件,包含构建应用程序所需的所有Android SDK库 asssets:存放资源 ...

  7. 我的攒机(zuosi)过程

    先贴上自己的配置清单: CPU E3 1240V2 930 https://item.taobao.com/item.htm?spm=a230r.1.14.8.lds6QF&id=532971 ...

  8. cat命令

    [cat]          合并文件和打印到标准输出 命令格式: cat [OPTION]... [FILE]... 命令功能: 拼接文件或者做标准输入输出 命令格式: cat [OPTION].. ...

  9. linux 环境变量

    电脑中必不可少的就是操作系统.而Linux的发展非常迅速,有赶超微软的趋势.这里介绍Linux的知识,让你学好应用Linux系统.比如要把/etc/apache/bin目录添加到PATH中,方法有三: ...

  10. 将页面转化为pdf的实现方法

    1.实现代码把html转化为pdf主要是使用wkhtmltopdf.exe工具生成,在获取转化的地址,创建一个进程,把地址传递到进程参数中进行调用wkhtmltopdf.exe工具打印 2.代码片段/ ...