欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

LongListSelector锁定组头(sticky header )之我的实现

程序员文章站 2023-04-01 19:17:37
longlistselector如何实现类似于wp7手机程序列表的效果,将屏幕显示范围内的第一个分组的groupheader一直显示在列表的最上方。 国内国外上问的不少,可都没有实现。 无耐之下自己...

longlistselector如何实现类似于wp7手机程序列表的效果,将屏幕显示范围内的第一个分组的groupheader一直显示在列表的最上方。
国内国外上问的不少,可都没有实现。

无耐之下自己动手搞定,闲话少续,开讲.....

核心思想:取得当前longlistselector控件可视范围内的第一条数据,取得它的分组名称,然后自己制做一个组头(在我的示例它叫做bordergroupname),覆盖在longlistselector控件之上。


[html]
<span style="font-size:12px;">  <border borderthickness="0" borderbrush="white"  visibility="visible" 
                      background="black"   canvas.zindex="10"   width="300"   height="80"   verticalalignment="top" rendertransformorigin="0.5,0.5"  grid.row="1"  name="bordergroupname"   > 
                    <border.rendertransform> 
                        <compositetransform translatex="-78"/> 
                    </border.rendertransform> 
                    <stackpanel> 
                        <border background="transparent" margin="12,8,0,8" width="300" > 
                            <border background="{staticresource phoneaccentbrush}"    
                                         width="62" height="62"                   
                                    mouseleftbuttondown="border_mouseleftbuttondown" 
                                        horizontalalignment="left"> 
                                <textblock text="a"  
                                               foreground="#ffffff"  
                                               fontsize="48" 
                                           margin="8,0,0,0" 
                                               fontfamily="{staticresource phonefontfamilysemilight}" 
                                               horizontalalignment="left" 
                                               name="txtgroupname" 
                                                  
                                               verticalalignment="bottom"/> 
                            </border> 
                        </border> 
                    </stackpanel> 
                </border> 
 
                <border borderthickness="0" borderbrush="red"   canvas.zindex="0"> 
                    <toolkit:longlistselector x:name="regionselector" background="transparent" 
                                          groupviewopened="longlistselector_groupviewopened" 
                                          groupviewclosing="longlistselector_groupviewclosing" 
                                             ................................................................</span> 

注意:longlistselector放在canvas中会产生冲突,所以仅把bordergroupname要覆盖到longlistselector只能通过<compositetransform translatex="-78"/>  这一句来实现了,但万幸canvas.zindex这个属性指定还是有效果的,为bordergroupname设置canvas.zindex="10",longlistselector的zindex只要比它小就ok了

然后说一下后台逻辑:

首先 提前计算一下每个分组的位置,我是把它保存到变量citygroupsetting,并写到独立存储里

请劳记citygroupsetting,所有分组位置信息都放在它里面

[csharp]
<span style="font-size:12px;">/// <summary> 
        /// 计算位置 
        /// 注意:如果控件大小发生改变,刚需重新计算 
        /// </summary> 
        private void btngroup_click(object sender, routedeventargs e) 
        { 
            debug.writeline("begintime " + system.datetime.now.timeofday.tostring()); 
            list<cityingroup> templist = (list<cityingroup>)(regionselector.itemssource); 
            list = new list<cityingroup>(); 
            foreach (var item in templist) 
            { 
                if (item.hasitems) 
                    list.add(item); 
            } 
 
            citygroupsetting = new citygroupsetting(); 
            citygroupsetting.grouppositions = new list<groupposition>(); 
            citygroupsetting.grouppositions.add(new groupposition("a", 0)); 
            regionselector.scrolltogroup((list[0])); 
 
            //由于直接跳转所有组,输出结果不准确.引入计时器 
            _timer.tick += new eventhandler(_timer_tick); 
            _timer.start(); 
            debug.writeline("endtime " + system.datetime.now.timeofday.tostring()); 
        } 
 
        /// <summary> 
        /// 计算分组位置,保存为xml文件 
        /// </summary> 
        void _timer_tick(object sender, eventargs e) 
        { 
            if (groupindex < list.count) 
            { 
                cityingroup citygroup = list[groupindex]; 
                if (citygroup.hasitems) 
                { 
                    if (groupindex > 1) 
                    { 
                        debug.writeline(list[groupindex - 1].key + "   max " + scrollbarlist[0].maximum + "  value " + scrollbarlist[0].value + "  " + system.datetime.now.timeofday.tostring()); 
                        citygroupsetting.grouppositions.add(new groupposition(list[groupindex - 1].key, int.parse(scrollbarlist[0].value.tostring()))); 
                    } 
                    regionselector.scrolltogroup((citygroup)); 
                } 
                groupindex++; 
            } 
            else 
            { 
                _timer.stop(); 
                debug.writeline(list[groupindex - 1].key + "   max " + scrollbarlist[0].maximum + "  value " + scrollbarlist[0].value + "  " + system.datetime.now.timeofday.tostring()); 
                citygroupsetting.grouppositions.add(new groupposition(list[groupindex - 1].key, int.parse(scrollbarlist[0].value.tostring()))); 
                citygroupsetting.save(); 
            } 
        }</span> 
在继续下面之前,先了解一下longlistselector前台的构成

scrollbar*2(一竖一横)+ scrollcontentpresenter  -->  scrollviewer  -->  (listbox==>templatedlistbox)  -->  longlistselector

在滚动longlistselector时,改变的值实际上是scrollbar的value

scrollbarlist[0] 是它的竖向滚动条,也是一会后面都会用到的

以下先取得longlistselect的scrollbar,并捕捉它的valuechanged事件

[csharp]
<span style="font-size:12px;">     list<scrollbar> scrollbarlist = new list<scrollbar>();        
     void cityselect_loaded(object sender, routedeventargs e) 
    { 
            getchildren(regionselector, ref scrollbarlist); 
            scrollbarlist[0].valuechanged += cityselect_valuechanged; 
    } 
 
        private ilist<scrollbar> getchildren(uielement element, ref list<scrollbar> list) 
        { 
            int count = visualtreehelper.getchildrencount(element); 
            for (int i = 0; i < count; i++) 
            { 
                dependencyobject child = visualtreehelper.getchild(element, i); 
                if (child is scrollbar) 
                { 
                    list.add((scrollbar)child); 
                } 
                uielement uielementchild = child as uielement; 
                if (uielementchild != null) 
                { 
                    getchildren(uielementchild, ref list); 
                } 
            } 
            return list; 
        }</span> 

在valuechanged事件中对当前scrollbar的值与citygroupsetting中存储的分组位置信息进行比较,取得groupheader的名称

[csharp]
<span style="font-size:12px;"> bordergroupname.visibility = visibility.visible; 
            double value = scrollbarlist[0].value; 
            double v1 = 0; 
            double v2 = 0; 
 
            if (value < .1) 
                bordergroupname.visibility = visibility.collapsed; 
            else 
            { 
                bordergroupname.visibility = system.windows.visibility.visible; 
 
                string groupname = null; 
 
                for (int i = 0; i < citygroupsetting.grouppositions.count - 1; i++) 
                { 
                    var item1 = citygroupsetting.grouppositions[i]; 
                    var item2 = citygroupsetting.grouppositions[i + 1]; 
                    v1 = item1.value - value; 
                    v2 = item2.value - value; 
                    if (math.abs(v1) < 0.1) 
                    { 
                        groupname = item1.groupname; 
                        break; 
                    } 
                    if (math.abs(v2) < 0.1) 
                    { 
                        groupname = item2.groupname; 
                        break; 
                    } 
                    if (i == citygroupsetting.grouppositions.count - 2 && value >= item2.value) 
                    { 
                        groupname = item2.groupname; 
                        break; 
                    } 
                    //.17 为gruopheader模版与它的容器下边界之间的像素换算出的大概value,其实在实际手指操作中有无它关系不并大,因为手指操作精度并没那么高 
                   //但本着尽量精确,在此把它加上,它与scollbar的maxvalue是成比例的 
                    if (value >= item1.value && value <= item2.value - .17) 
                    { 
                        groupname = item1.groupname; 
                        break; 
                    } 
                if (groupname != null) 
                { 
                    txtgroupname.text = groupname; 
                    debug.writeline("groupname  " + txtgroupname.text + " v1 " + v1 + " v2 " + v2); 
                } 
            }</span> 

你可能以为这样就ok了,可事实是总有意想不到的操蛋的问题在等着你 ,上面说了根据valuechanged判断当前的groupheader显内容。

坑爹的问题出现了,两次valuechanged之间会有较大的跨度,具体结果就是在小范围移动的时候,明明你的数据内容都到了b组了,grouphaeder还显示的是a。

请看我的调试信息   y:为移动像素,后面的那个值 是scrollbar.value;


y: -179  0 valuechanged
y: -195  2.47457627118644 valuechanged
y: -331  2.47457627118644 valuechanged
y: -387  5.32203389830508 valuechanged
y: -484  5.32203389830508 valuechanged
y: -544  8.10169491525424 valuechanged

可以看到当我都移动了179像素的时候 ,value还没发生改变,直到195才改变,但它的也并不是很有规律,这点让我很是无语

没法子,经过无数次的测试,在mousemove中写下代码,在固定范围内根据鼠标移动像素来设置

[csharp]
<span style="font-size:12px;">      private void longlistselector_mousemove(object sender, mouseeventargs e) 
        { 
            if (isbtndown) 
            { 
                y = (int)e.getposition(null).y - straty; 
                //txtnum.text = y.tostring(); 
                //经测试仅鼠标移动超过22个像素才会引发scrolling事件,个人猜测是ms对触摸屏设置的一个误差范围值 
                if (math.abs(y) < 200 && math.abs(y) > 22) 
                { 
                    //像素与scrollbar的value 的比例 
                    //经测试并不绝对准确,它总是有好像会有一定范围的变化 
                    //仅相对准确的 
                    double scalevalue = 78; 
 
                    double j = -y / scalevalue; 
                    double v = startvalue + j; 
                    debug.writeline("y: " + y + " postion " + v + " scale " + scalevalue); 
                    if (v > 0) 
                        scrollbarlist[0].value = v; 
                } 
            } 
        }</span> 
当代码写到这的时候我心说差不过完成了吧,可一测试又发现了其它问题,当我们的listbox到顶得时候,我们继续往下拉,内容会继续往下走一段距离之后不能再继续拉动,放手之后,内容会回弹到原来的位置。所以就涉及到当头问发生压缩的时候,要隐藏我的bordergroupname。
关于这个问题

建议您读一下这篇文章:

x">http://blogs.msdn.com/b/slmperf/archive/2011/06/30/windows-phone-mango-change-listbox-how-to-detect-compression-end-of-scroll-states.aspx
这里介绍的方法是对以上代码的扩展,在7.1中,我们可以拿到verticalcompression和horizontalcompression两种visualstategroup,可以用来检测listbox的上下左右方向的压缩状态。其中也包括了示例代码下载。
 

至此,核心逻辑已说明完毕。

需注意的:

仅适用于静态数据,且longlistselector大小固定,在longlistselector大小不同时,scrollbar.maxvalue也不同。对应各分组的位置也不同。

在极端情况下分组的名称也是会有显示不准确的时候,比如说拿鼠标一个像素一个像素的去拖,正常手指操作准确性还是可以保证的。

如果希望完全模仿wp的样式,希望你能找出那个scrollbar.value 与像素的准确比例。然后动态的改变bordergroupname的位置。

systemtray最好不要显示,因为打开分组选择的时候,它会自去把systemtray推上去,然后页面整体上移。动画会有明显卡顿感的感觉(可以看一下大众点评的城市选择),选择完成后页面在自已移下来。

 


摘自 zl1911的专栏