on 73354fd help sign in
unity8/Components/ListViewWithPageHeader.qml
189 lines 7.2 kB history raw
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
17import QtQuick 2.0
18import "Math.js" as MathLocal
20Item {
21 id: root
22 property Item pageHeader: null
23 property Component sectionDelegate: null
24 property string sectionProperty: ""
25 property alias model: listView.model
26 property alias delegate: listView.delegate
27 property ListView view: listView
28 property alias moving: flicker.moving
29 property alias atYEnd: flicker.atYEnd
30 property bool clipListView: true
32 readonly property real __headerHeight: (pageHeader) ? pageHeader.implicitHeight : 0
33 property real __headerVisibleHeight: __headerHeight
34 readonly property real __overshootHeight: (flicker.contentY < 0) ? -flicker.contentY : 0
37 // TODO move to AnimationController
38 ParallelAnimation {
39 id: headerAnimation
40 property real targetContentY
41 NumberAnimation {
42 target: root
43 property: "__headerVisibleHeight"
44 to: root.__headerHeight
45 duration: 200
46 easing.type: Easing.OutQuad
47 }
48 NumberAnimation {
49 target: listView
50 property: "contentY"
51 to: headerAnimation.targetContentY
52 duration: 200
53 easing.type: Easing.OutQuad
54 }
55 }
57 function positionAtBeginning() {
58 __headerVisibleHeight = __headerHeight
59 flicker.contentY = 0
60 }
62 function showHeader() {
63 headerAnimation.targetContentY = listView.contentY + (__headerHeight - __headerVisibleHeight)
64 headerAnimation.start()
65 }
67 function flick(xVelocity, yVelocity) {
68 flicker.flick(xVelocity, yVelocity);
69 }
71 onPageHeaderChanged: {
72 pageHeader.parent = pageHeaderContainer;
73 pageHeader.anchors.fill = pageHeaderContainer;
74 }
76 Item {
77 id: pageHeaderClipper
78 parent: flicker // parent to Flickable so mouse click events passed through to the header component
79 anchors {
80 top: parent.top
81 left: parent.left
82 right: parent.right
83 }
84 height: __headerVisibleHeight + __overshootHeight
86 Item {
87 id: pageHeaderContainer
88 anchors {
89 left: parent.left
90 right: parent.right
91 bottom: parent.bottom
92 }
93 height: __headerHeight + __overshootHeight
94 }
95 }
97 ListView {
98 id: listView
99 parent: flicker // parent to Flickable so mouse click events passed through to List delegates
100 anchors {
101 left: parent.left
102 right: parent.right
103 top: parent.top
104 topMargin: __headerVisibleHeight
105 }
106 height: root.height
108 // FIXME scrolling workaround, see below
109 cacheBuffer: height*10
111 section.property: sectionProperty
112 section.criteria: ViewSection.FullString
113 section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart
114 section.delegate: sectionDelegate
116 interactive: false
117 clip: root.clipListView
119 property int __sectionDelegateHeight: __getHeight(section.delegate)
120 function __getHeight(component) {
121 // want height (minus allowed overlap) of the section delegate as is needed for clipping
122 if (component === null) return 0;
123 var object = component.createObject(); //FIXME: throws 'section' not defined error
124 var value = object.height - object.bottomBorderAllowedOverlap;
125 object.destroy();
126 return value;
127 }
129 property real previousOriginY: 0
130 onOriginYChanged: {
131 var deltaOriginY = originY - previousOriginY;
132 previousOriginY = originY;
133 /* When originY changes, it causes the top of the flicker and the top of the list to fall
134 out of sync - and thus the contentY positioning will be broken. To correct for this we
135 manually change the flickable's contentY here */
136 flicker.contentY -= deltaOriginY;
137 }
139 /* For case when list content greater than container height and list scrolled down so header
140 hidden. If content shrinks to be smaller than the container height, we want the header to
141 re-appear */
142 property real __previousContentHeight: 0
143 onContentHeightChanged: {
144 var deltaContentHeight = contentHeight - __previousContentHeight;
145 __previousContentHeight = contentHeight;
146 if (contentHeight < height && deltaContentHeight < 0 && __headerVisibleHeight < height - contentHeight) {
147 __headerVisibleHeight = Math.min(height - contentHeight, __headerHeight);
148 }
149 }
150 }
152 Flickable {
153 id: flicker
154 anchors.fill: parent
155 contentHeight: listView.contentHeight + __headerHeight
156 maximumFlickVelocity: height * 10
157 flickDeceleration: height * 2
158 onContentYChanged: {
159 var deltaContentY = contentY - __previousContentY;
160 __previousContentY = contentY;
162 // first decide if movement will prompt the page header to change height
163 if ((deltaContentY < 0 && __headerVisibleHeight >= 0) ||
164 (deltaContentY > 0 && __headerVisibleHeight <= __headerHeight)) {
166 // calculate header height - but prevent bounce from changing it
167 if (contentY > 0 && contentY < contentHeight - height) {
168 __headerVisibleHeight = MathLocal.clamp(__headerVisibleHeight - deltaContentY, 0, __headerHeight);
169 }
170 }
172 // now we move list position, taking into account page header height
174 // BUG: With section headers enabled, the values of originY and contentY appear not
175 // correct at the exact point originY changes. originY changes when the ListView
176 // deletes/creates hidden delegates which are above the visible delegates.
177 // As a result of this bug, you experience jittering scrolling when rapidly moving
178 // around in large lists. See https://bugreports.qt-project.org/browse/QTBUG-27997
179 // A workaround is to use a large enough cacheBuffer to prevent deletions/creations
180 // so effectively originY is always zero.
181 var newContentY = flicker.contentY + listView.originY - __headerHeight + __headerVisibleHeight
182 if (newContentY < listView.contentHeight) {
183 listView.contentY = newContentY;
184 }
185 }
187 property real __previousContentY: 0
188 }