欢迎访问 生活随笔!

凯发ag旗舰厅登录网址下载

当前位置: 凯发ag旗舰厅登录网址下载 > > 编程问答 >内容正文

编程问答

uwp composition api -凯发ag旗舰厅登录网址下载

发布时间:2025/1/21 编程问答 9 豆豆
凯发ag旗舰厅登录网址下载 收集整理的这篇文章主要介绍了 uwp composition api - grouplistview(一) 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

需求:

光看标题大家肯定不知道是什么东西,先上效果图:

这不就是listview的group效果吗?? 看上去是的。但是请听完需求.
1.group中的集合需要支持增量加载isupportincrementalloading

2.支持ui virtualization

oh,no。listview 自带的group都不支持这2个需求。好吧,只有靠自己撸code了。。

实现前思考:

仔细想了下,其实要解决的主要问题有2个
数据源的处理 和 groupheader的ui的处理

1.数据源的处理 

 因为之前在写 uwp virtualizedvariablesizedgridview 支持可虚拟化可变大小item的view的时候已经做过这种处理源的工作了,所以方案出来的比较快。

不管有几个group,其实当第1个hasmore等false的时候,我们就可以加载第2个group里面的集合。

我为此写了一个类groupobservablecollection 它是继承 observablecollection, igroupcollection

public class groupobservablecollection : observablecollection, igroupcollection{private list> soureslist;private list<int> firstindexineachgroup = new list<int>();private list groupheaders;bool _isloadingmoreitems = false;public groupobservablecollection(list> soureslist, list groupheaders){this.soureslist = soureslist;this.groupheaders = groupheaders;}public bool hasmoreitems{get{if (currentgroupindex < soureslist.count){var source = soureslist[currentgroupindex];if (source is isupportincrementalloading){if (!(source as isupportincrementalloading).hasmoreitems){if (!_isloadingmoreitems){if (this.count < getsourcelisttotoalcount()){int count = 0;int precount = this.count;foreach (var item in soureslist){foreach (var item1 in item){if (count >= precount){this.add(item1);if (item == source && groupheaders[currentgroupindex].firstindex==-1){groupheaders[currentgroupindex].firstindex = this.count - 1;}}count ;}}}groupheaders[currentgroupindex].lastindex = this.count - 1;return false;}else{return true;}}else{return true;}}else{if (currentgroupindex == source.count - 1){if (this.count < getsourcelisttotoalcount()){int count = 0;int precount = this.count;foreach (var item in soureslist){foreach (var item1 in item){if (count >= precount){this.add(item1);if (item == source && groupheaders[currentgroupindex].firstindex == -1){groupheaders[currentgroupindex].firstindex = this.count - 1;}}count ;}}}groupheaders[currentgroupindex].lastindex = this.count - 1;return false;}else{return true;}}}else{return false;}}}int getsourcelisttotoalcount(){int i = 0;foreach (var item in soureslist){i = item.count;}return i;}public list<int> firstindexineachgroup{get{return firstindexineachgroup;}set{firstindexineachgroup = value;}}public list groupheaders{get{return groupheaders;}set{groupheaders = value;}}public iasyncoperation loadmoreitemsasync(uint count){return fetchitems(count).asasyncoperation();}private int currentgroupindex;public int currentgroupindex{get{int count = 0;for (int i = 0; i < soureslist.count; i ){var source = soureslist[i];count = source.count;if (count > this.count){currentgroupindex = i;return currentgroupindex;}else if (count == this.count){currentgroupindex = i;if ((source is isupportincrementalloading)){if (!(source as isupportincrementalloading).hasmoreitems){if (!_isloadingmoreitems){groupheaders[i].lastindex = this.count - 1;if (currentgroupindex 1 < soureslist.count){currentgroupindex = i 1;}}}}else{//nextif (currentgroupindex 1 < soureslist.count){currentgroupindex = i 1;}}return currentgroupindex;}else{continue;}}currentgroupindex = 0;return currentgroupindex;}}private async task fetchitems(uint count){var source = soureslist[currentgroupindex];if (source is isupportincrementalloading){int firstindex = 0;if (groupheaders[currentgroupindex].firstindex != -1){firstindex = source.count;}_isloadingmoreitems = true;var result = await (source as isupportincrementalloading).loadmoreitemsasync(count);for (int i = firstindex; i < source.count; i ){this.add(source[i]);if (i == 0){groupheaders[currentgroupindex].firstindex = this.count - 1;}}_isloadingmoreitems = false;return result;}else{int firstindex = 0;if (groupheaders[currentgroupindex].firstindex != -1){firstindex = source.count;}for (int i = firstindex; i < source.count; i ){this.add(source[i]);if (i == 0){groupheaders[currentgroupindex].firstindex = this.count - 1;}}groupheaders[currentgroupindex].lastindex = this.count - 1;return new loadmoreitemsresult() { count = (uint)source.count };}}} view code

而igroupcollection是个接口。

public interface igroupcollection: isupportincrementalloading{list groupheaders { get; set; }int currentgroupindex { get; }}public interface igroupheader{string name { get; set; }int firstindex { get; set; }int lastindex { get; set; }double height { get; set; }}public class defaultgroupheader : igroupheader{public string name { get; set; }public int firstindex { get; set; }public int lastindex { get; set; }public double height { get; set; }public defaultgroupheader(){firstindex = -1;lastindex = -1;}}

igroupheader 是用来描述group header的,你可以继承它,添加一些绑定groupheader的属性(注意请给firstindex和lastindex赋值-1的初始值)

比如:在效果图中,如果只有全部评论,没有精彩评论,那么后面的导航的按钮是应该不现实的,所以我加了gotobuttonvisibility属性来控制。

public class mygroupheader : igroupheader, inotifypropertychanged{public string name { get; set; }public int firstindex { get; set; }public int lastindex { get; set; }public double height { get; set; }public string goto { get; set; }private visibility _gotobuttonvisibility = visibility.collapsed;public visibility gotobuttonvisibility{get { return _gotobuttonvisibility; }set{_gotobuttonvisibility = value;onpropertychanged("gotobuttonvisibility");}}public mygroupheader(){firstindex = -1;lastindex = -1;}public event propertychangedeventhandler propertychanged;void onpropertychanged(string propertyname){if (propertychanged != null){propertychanged(this, new propertychangedeventargs(propertyname));}}}

数据源的处理还是比较简单的。

 2.groupheader的ui的处理

首先我想到的是加一个grid,然后这些groupheader放在里面,通过scrollviewer的viewchanged来处理它们。

比较了下listview的group效果,scrollbar是会挡住groupheader的,所以我把这个grid放进了scrollviewer的模板里面。

grouplistview的模板,这里大家可以看到我加入了个progressring,这个是后面做导航功能需要的,后面再讲。

"local:grouplistview">"{templatebinding borderbrush}" borderthickness="{templatebinding borderthickness}" background="{templatebinding background}">"scrollviewer" style="{staticresource grouplistviewscrollviewer}" automationproperties.accessibilityview="raw" bringintoviewonfocuschange="{templatebinding scrollviewer.bringintoviewonfocuschange}" horizontalscrollmode="{templatebinding scrollviewer.horizontalscrollmode}" horizontalscrollbarvisibility="{templatebinding scrollviewer.horizontalscrollbarvisibility}" ishorizontalrailenabled="{templatebinding scrollviewer.ishorizontalrailenabled}" ishorizontalscrollchainingenabled="{templatebinding scrollviewer.ishorizontalscrollchainingenabled}" isverticalscrollchainingenabled="{templatebinding scrollviewer.isverticalscrollchainingenabled}" isverticalrailenabled="{templatebinding scrollviewer.isverticalrailenabled}" isdeferredscrollingenabled="{templatebinding scrollviewer.isdeferredscrollingenabled}" tabnavigation="{templatebinding tabnavigation}" verticalscrollbarvisibility="{templatebinding scrollviewer.verticalscrollbarvisibility}" verticalscrollmode="{templatebinding scrollviewer.verticalscrollmode}" zoommode="{templatebinding scrollviewer.zoommode}">"{templatebinding footertransitions}" footertemplate="{templatebinding footertemplate}" footer="{templatebinding footer}" headertemplate="{templatebinding headertemplate}" header="{templatebinding header}" headertransitions="{templatebinding headertransitions}" padding="{templatebinding padding}"/>"progressring" visibility="collapsed" horizontalalignment="center" verticalalignment="center"/>

scrollviewer的模板

"{templatebinding background}">"*"/>"auto"/>"*"/>"auto"/>"scrollcontentpresenter" grid.columnspan="2" contenttemplate="{templatebinding contenttemplate}" margin="{templatebinding padding}" grid.rowspan="2"/>"groupheaderscanvas" grid.rowspan="2" grid.columnspan="2" horizontalalignment="stretch" verticalalignment="stretch"/>"topgroupheader" grid.rowspan="2" grid.columnspan="2" verticalalignment="top" horizontalalignment="stretch" horizontalcontentalignment="stretch" verticalcontentalignment="stretch"/>"verticalscrollbar" grid.column="1" horizontalalignment="right" istabstop="false" maximum="{templatebinding scrollableheight}" orientation="vertical" visibility="{templatebinding computedverticalscrollbarvisibility}" value="{templatebinding verticaloffset}" viewportsize="{templatebinding viewportheight}"/>"horizontalscrollbar" istabstop="false" maximum="{templatebinding scrollablewidth}" orientation="horizontal" grid.row="1" visibility="{templatebinding computedhorizontalscrollbarvisibility}" value="{templatebinding horizontaloffset}" viewportsize="{templatebinding viewportwidth}"/>"scrollbarseparator" background="{themeresource systemcontrolpagebackgroundchromelowbrush}" grid.column="1" grid.row="1"/>

下面就是实现对groupheader显示的控制了。

很快代码写好了。。运行起来效果还可以。。但是童鞋们说。。你这个跟composition api 一毛钱关系都没有啊。。

大家别急。。听我说。。模拟器里面运行还行,拿实体机器上运行的时候,当我快速向上或者向下滑动的时候,groupheader会出现顿一顿的感觉,卡一下,不会有惯性的感觉。

看到这个,我立马明白了。。不管是viewchanging或者viewchanged事件,它们跟manipulation都不是同步的。

看了上一盘 uwp composition api - pulltorefresh的童鞋会说,好吧,隐藏的真深。

那我们还是用composition api来建立groupheader和scrollviewer之间的关系。

1.首先我想的是,当进入viewport再用composition api来建立关系,但是很快被我否决了。还是因为viewchanged这个事件是有惯性的原因,这样没法让创建groupheader和scrollviewer之间的关系的初始数据完全准确。

就是说groupheader因为初始数据不正确的情况会造成没放在我想要的位置,只有当惯性停止的时候获取的位置信息才是准确的。

在preparecontainerforitemoverride中判断是否groupheader 的那个item已经准备添加到itemspanel里面。

protected override void preparecontainerforitemoverride(dependencyobject element, object item){base.preparecontainerforitemoverride(element, item);listviewitem listviewitem = element as listviewitem;listviewitem.sizechanged -= listviewitem_sizechanged;if (listviewitem.tag == null){defaultlistviewitemmargin = listviewitem.margin;}if (groupcollection != null){var index = indexfromcontainer(element);var group = groupcollection.groupheaders.firstordefault(x => x.firstindex == index || x.lastindex == index);if (group != null){if (!groupdic.containskey(group)){contentcontrol groupheader = creategroupheader(group);contentcontrol tempgroupheader = creategroupheader(group);expressionanimationitem expressionanimationitem = new expressionanimationitem();expressionanimationitem.visualelement = groupheader;expressionanimationitem.tempelement = tempgroupheader;groupdic[group] = expressionanimationitem;var temp = new dictionary();foreach (var keyvalue in groupdic.orderby(x => x.key.firstindex)){temp[keyvalue.key] = keyvalue.value;}groupdic = temp;if (groupheaderscanvas != null){groupheaderscanvas.children.add(groupheader);groupheaderscanvas.children.add(tempgroupheader);groupheader.measure(new windows.foundation.size(this.actualwidth, this.actualheight));group.height = groupheader.desiredsize.height;groupheader.height = tempgroupheader.height = group.height;groupheader.width = tempgroupheader.width = this.actualwidth;if (group.firstindex == index){listviewitem.tag = listviewitem.margin;listviewitem.margin = getitemmarginbaseondeafult(groupheader.desiredsize.height);listviewitem.sizechanged = listviewitem_sizechanged;}groupheader.visibility = visibility.collapsed;tempgroupheader.visibility = visibility.collapsed;updategroupheaders();}}else{if (group.firstindex == index){listviewitem.tag = listviewitem.margin;listviewitem.margin = getitemmarginbaseondeafult(group.height);listviewitem.sizechanged = listviewitem_sizechanged;}else{listviewitem.margin = defaultlistviewitemmargin;}}}else{listviewitem.margin = defaultlistviewitemmargin;}}else{listviewitem.margin = defaultlistviewitemmargin;}} view code

在updategroupheader方法里面去设置header的状态

internal void updategroupheaders(bool isintermediate = true){var firstvisibleitemindex = this.getfirstvisibleindex();foreach (var item in groupdic){//top headerif (item.key.firstindex <= firstvisibleitemindex && (firstvisibleitemindex <= item.key.lastindex || item.key.lastindex == -1)){currenttopgroupheader.visibility = visibility.visible;currenttopgroupheader.margin = new thickness(0);currenttopgroupheader.clip = null;currenttopgroupheader.datacontext = item.key;if (item.key.firstindex == firstvisibleitemindex){if (item.value.scrollviewer == null){item.value.scrollviewer = scrollviewer;}var isactive = item.value.isactive;item.value.stopanimation();item.value.visualelement.clip = null;item.value.visualelement.visibility = visibility.collapsed;if (!isactive){if (!isintermediate){item.value.visualelement.margin = new thickness(0);item.value.startanimation(true);}}else{item.value.startanimation(false);}}cleartempelement(item);}//moving headerelse{handlegroupheader(isintermediate, item);}}} view code

这里我简单说下几种状态:
1. 在itemspanel里面

1)全部在viewport里面

动画开启,clip设置为null

2)部分在viewport里面

动画开启,并且设置clip
3)没有在viewport里面

动画开启,visible 设置为collapsed
2. 没有在itemspanel里面

动画停止。

关于groupheader初始状态的设置,这里是最坑的,遇到很多问题。

public void startanimation(bool update = false){if (update || expression == null || visual == null){visual = elementcompositionpreview.getelementvisual(visualelement);//if (0 <= visualelement.margin.top && visualelement.margin.top <= scrollviewer.actualheight)//{// min = (float)-visualelement.margin.top;// max = (float)scrollviewer.actualheight min;//}//else if (visualelement.margin.top < 0)//{//}//else if (visualelement.margin.top > scrollviewer.actualheight)//{//}if (scrollviewermanipprops == null){scrollviewermanipprops = elementcompositionpreview.getscrollviewermanipulationpropertyset(scrollviewer);}compositor compositor = scrollviewermanipprops.compositor;// create the expression//expression = compositor.createexpressionanimation("min(max((scrollviewermanipprops.translation.y verticaloffset), minvalue), maxvalue)");////expression = compositor.createexpressionanimation("scrollviewermanipprops.translation.y verticaloffset");//expression.setscalarparameter("minvalue", min);//expression.setscalarparameter("maxvalue", max);//expression.setscalarparameter("verticaloffset", (float)scrollviewer.verticaloffset); expression = compositor.createexpressionanimation("scrollviewermanipprops.translation.y verticaloffset");////expression = compositor.createexpressionanimation("scrollviewermanipprops.translation.y verticaloffset");//expression.setscalarparameter("minvalue", min);//expression.setscalarparameter("maxvalue", max);verticaloffset = scrollviewer.verticaloffset;expression.setscalarparameter("verticaloffset", (float)scrollviewer.verticaloffset);// set "dynamic" reference parameter that will be used to evaluate the current position of the scrollbar every frameexpression.setreferenceparameter("scrollviewermanipprops", scrollviewermanipprops);}visual.startanimation("offset.y", expression);isactive = true;//windows.ui.xaml.media.compositiontarget.rendering -= oncompositiontargetrendering;//windows.ui.xaml.media.compositiontarget.rendering = oncompositiontargetrendering;}

注释掉了的代码是处理:

当groupheader进入viewport的时候才启动动画,离开之后就关闭动画,表达式就是一个限制,这个就不讲了。

expression = compositor.createexpressionanimation("scrollviewermanipprops.translation.y verticaloffset");

可以看到我给表达式加了一个vericaloffset。。嗯。其实visual的offset是表示 visual 相对于其父 visual 的位置偏移量。

举2个例子,整个viewport的高度是500,现在滚动条的vericaloffset是100。

1.如果我想把header(header高度为50)放到viewport的最下面(header刚好全部进入viewport),那么初始的参数应该是哪些呢?

header.margin = new thickness(450);

header.clip=null;

expression = compositor.createexpressionanimation("scrollviewermanipprops.translation.y 100");

这样向上滚scrollviewermanipprops.translation.y(-450),header 就会滚viewport的顶部。


2.如果我想把header(header高度为50)放到viewport的最下面(header刚好一半全部进入viewport),那么初始的参数应该是哪些呢?

header.margin = new thickness(475);

header.clip=new rectanglegeometry() { rect = new rect(0, 0, this.actualwidth, 25) };

expression = compositor.createexpressionanimation("scrollviewermanipprops.translation.y 100");

当向上或者向下滚动的时候,记得更新clip值就可以了。

说到为什么要加clip,因为如果你的控件不是整个page大小的时候,这个header会显示到控件外部去,大家应该都是懂得。

这里说下这个里面碰到一个问题。当groupheader viewport之外的时候(在grid之外的,margin大于grid的高度)创建动画,会发现你怎么修改header属性都是没有效果的。

最终结果的是不会在屏幕上显示任何东西。

实验了下用canvas发现就可以了,但是grid却不行,是不是可以认为visual在创建的时候如果对象不在它父容器的size范围之内,创建出来都是看不见的??

这个希望懂得童鞋能留言告诉一下。

把scrollviewer模板里面的grid换成canvas就好了。。

剩下的都是一些计算,计算位置,计算大小变化。

最后就是gotogroup方法,当跳转的group没有load出来的时候(也就是firstindex还没有值得时候),我们就load,load,load,直到

它有值,这个可能是个长的时间过程,所以加了progressring,找到index,最后用listview的api来跳转就好了。

public async task gotogroupasync(int groupindex, scrollintoviewalignment scrollintoviewalignment = scrollintoviewalignment.leading){if (groupcollection != null){var gc = groupcollection;if (groupindex < gc.groupheaders.count && groupindex >= 0 && !isgotogrouping){isgotogrouping = true;//load more so that scrollintoviewalignment.leading can go to topvar loadcount = this.getvisibleitemscount() 1;progressring.isactive = true;progressring.visibility = visibility.visible;//make sure user don't do any other thing at the time.this.ishittestvisible = false;//await task.delay(3000);while (gc.groupheaders[groupindex].firstindex == -1){if (gc.hasmoreitems){await gc.loadmoreitemsasync(loadcount);}else{break;}}if (gc.groupheaders[groupindex].firstindex != -1){//make sure there are enought items to go scrollintoviewalignment.leading//this.count > (firstindex loadcount)if (scrollintoviewalignment == scrollintoviewalignment.leading){var more = this.items.count - (gc.groupheaders[groupindex].firstindex loadcount);if (gc.hasmoreitems && more < 0){await gc.loadmoreitemsasync((uint)math.abs(more));}}progressring.isactive = false;progressring.visibility = visibility.collapsed;var groupfirstindex = gc.groupheaders[groupindex].firstindex;scrollintoview(this.items[groupfirstindex], scrollintoviewalignment);//already in viewport, maybe it will not change view if (groupdic.containskey(gc.groupheaders[groupindex]) && groupdic[gc.groupheaders[groupindex]].visibility == visibility.visible){this.ishittestvisible = true;isgotogrouping = false;}}else{this.ishittestvisible = true;isgotogrouping = false;progressring.isactive = false;progressring.visibility = visibility.collapsed;}}}}

 总结:

这个控件做下来,基本上都是在计算计算计算。。当然也知道了一些composition api的东西。

其实vistual的属性还有很多,在做这个控件的时候没有用到,以后用到了会继续分享的。 开源有益,源码github地址。

visual 元素有些基本的呈现相关属性,这些属性都能使用 composition api 的动画 api 来演示动画。

  • opacity 
    表示 visual 的透明度。

  • offset 
    表示 visual 相对于其父 visual 的位置偏移量。

  • clip 
    表示 visual 裁剪区域。

  • centerpoint 
    表示 visual 的中心点。

  • transformmatrix 
    表示 visual 的变换矩阵。

  • size 
    表示 visual 的尺寸大小。

  • scale 
    表示 visual 的缩放大小。

  • rotationaxis 
    表示 visual 的旋转轴。

  • rotationangle 
    表示 visual 的旋转角度。

有 4 个类派生自 visual,他们分别对应了不同种类的 visual,分别是:

    • containervisual 
      表示容器 visual,可能有子节点的 visual,大部分的 xaml 可视元素基本都是该 visual,其他的 visual 都也是派生自该类。

    • effectvisual 
      表示通过特效来呈现内容的 visual,可以通过配合 win2d 的支持 composition 的 effects 来呈现丰富多彩的内容。

    • imagevisual 
      表示通过图片来呈现内容的 visual,可以用于呈现图片。

    • solidcolorvisual
      表示一个纯色矩形的 visual 元素

转载于:https://www.cnblogs.com/fadekongjian/p/5629715.html

总结

以上是凯发ag旗舰厅登录网址下载为你收集整理的uwp composition api - grouplistview(一)的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得凯发ag旗舰厅登录网址下载网站内容还不错,欢迎将凯发ag旗舰厅登录网址下载推荐给好友。

网站地图