diff options
author | Richard Moe Gustavsen <richard.gustavsen@qt.io> | 2019-09-24 15:09:34 +0200 |
---|---|---|
committer | Richard Moe Gustavsen <richard.gustavsen@qt.io> | 2019-12-04 01:35:02 +0100 |
commit | 1841a9e41d02c9b95a7eb3c47e09f6773da56a85 (patch) | |
tree | 965a96a2b4299abf2ada3057895a5ef4abb0af2f /src | |
parent | 8c72e634b3b0eacbfdee883bfc34994d3c19ed77 (diff) |
QQuickListView: implement support for reusing items
This patch will implement delegate item recycling in
ListView. The API will be the same as used in TableView, except
that it will be off by default since the behavior of a delegate
that is reused is not compatible with legacy applications
which were not written with this in mind.
Most importantly:
- Component.onCompleted will only be called on a delegate the
first time it's created, and never when it's reused.
- Any user-declared properties in the delegate (that is, not
model roles, index, etc) will not be cleared or updated when an
item is reused. The application must do this manually upon
receiving the pooled or reused signal in the item.
[ChangeLog][ListView] ListView now has support for reusing delegate
items. This can be switched on by setting the reuseItems property of
ListView to true.
Task-number: QTBUG-80507
Change-Id: I68cc8300b050e4a1f89feebb1d31a2fd9189c793
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
Diffstat (limited to 'src')
-rw-r--r-- | src/quick/items/qquickgridview.cpp | 4 | ||||
-rw-r--r-- | src/quick/items/qquickitemview.cpp | 76 | ||||
-rw-r--r-- | src/quick/items/qquickitemview_p.h | 12 | ||||
-rw-r--r-- | src/quick/items/qquickitemview_p_p.h | 13 | ||||
-rw-r--r-- | src/quick/items/qquicklistview.cpp | 27 |
5 files changed, 105 insertions, 27 deletions
diff --git a/src/quick/items/qquickgridview.cpp b/src/quick/items/qquickgridview.cpp index e69e9cff46..5f6c194bcf 100644 --- a/src/quick/items/qquickgridview.cpp +++ b/src/quick/items/qquickgridview.cpp @@ -497,7 +497,7 @@ bool QQuickGridViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, qreal // We've jumped more than a page. Estimate which items are now // visible and fill from there. int count = (fillFrom - (rowPos + rowSize())) / (rowSize()) * columns; - releaseVisibleItems(); + releaseVisibleItems(reusableFlag); modelIndex += count; if (modelIndex >= model->count()) modelIndex = model->count() - 1; @@ -576,7 +576,7 @@ void QQuickGridViewPrivate::removeItem(FxViewItem *item) item->releaseAfterTransition = true; releasePendingTransition.append(item); } else { - releaseItem(item); + releaseItem(item, QQmlDelegateModel::NotReusable); } } diff --git a/src/quick/items/qquickitemview.cpp b/src/quick/items/qquickitemview.cpp index 857b1a1c34..e88f60db6d 100644 --- a/src/quick/items/qquickitemview.cpp +++ b/src/quick/items/qquickitemview.cpp @@ -197,6 +197,8 @@ void QQuickItemView::setModel(const QVariant &m) disconnect(d->model, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*))); disconnect(d->model, SIGNAL(createdItem(int,QObject*)), this, SLOT(createdItem(int,QObject*))); disconnect(d->model, SIGNAL(destroyingItem(QObject*)), this, SLOT(destroyingItem(QObject*))); + disconnect(d->model, SIGNAL(itemPooled(int, QObject *)), this, SLOT(onItemPooled(int, QObject *))); + disconnect(d->model, SIGNAL(itemReused(int, QObject *)), this, SLOT(onItemReused(int, QObject *))); } QQmlInstanceModel *oldModel = d->model; @@ -232,6 +234,8 @@ void QQuickItemView::setModel(const QVariant &m) connect(d->model, SIGNAL(createdItem(int,QObject*)), this, SLOT(createdItem(int,QObject*))); connect(d->model, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*))); connect(d->model, SIGNAL(destroyingItem(QObject*)), this, SLOT(destroyingItem(QObject*))); + connect(d->model, SIGNAL(itemPooled(int, QObject *)), this, SLOT(onItemPooled(int, QObject *))); + connect(d->model, SIGNAL(itemReused(int, QObject *)), this, SLOT(onItemReused(int, QObject *))); if (isComponentComplete()) { d->updateSectionCriteria(); d->refill(); @@ -692,6 +696,28 @@ void QQuickItemView::setHighlightMoveDuration(int duration) } } +bool QQuickItemView::reuseItems() const +{ + return bool(d_func()->reusableFlag == QQmlDelegateModel::Reusable); +} + +void QQuickItemView::setReuseItems(bool reuse) +{ + Q_D(QQuickItemView); + if (reuseItems() == reuse) + return; + + d->reusableFlag = reuse ? QQmlDelegateModel::Reusable : QQmlDelegateModel::NotReusable; + + if (!reuse && d->model) { + // When we're told to not reuse items, we + // immediately, as documented, drain the pool. + d->model->drainReusableItemsPool(0); + } + + emit reuseItemsChanged(); +} + QQuickTransition *QQuickItemView::populateTransition() const { Q_D(const QQuickItemView); @@ -846,7 +872,7 @@ void QQuickItemViewPrivate::positionViewAtIndex(int index, int mode) setPosition(qMin(itemPos, maxExtent)); // now release the reference to all the old visible items. for (FxViewItem *item : oldVisible) - releaseItem(item); + releaseItem(item, reusableFlag); item = visibleItem(idx); } if (item) { @@ -1089,8 +1115,8 @@ qreal QQuickItemViewPrivate::calculatedMaxExtent() const void QQuickItemViewPrivate::applyDelegateChange() { - releaseVisibleItems(); - releaseItem(currentItem); + releaseVisibleItems(QQmlDelegateModel::NotReusable); + releaseItem(currentItem, QQmlDelegateModel::NotReusable); currentItem = nullptr; updateSectionCriteria(); refill(); @@ -1192,7 +1218,7 @@ void QQuickItemView::destroyRemoved() } else { if (hasRemoveTransition) d->runDelayedRemoveTransition = true; - d->releaseItem(item); + d->releaseItem(item, d->reusableFlag); it = d->visibleItems.erase(it); } } else { @@ -1636,7 +1662,7 @@ void QQuickItemViewPrivate::updateCurrent(int modelIndex) if (currentItem) { if (currentItem->attached) currentItem->attached->setIsCurrentItem(false); - releaseItem(currentItem); + releaseItem(currentItem, reusableFlag); currentItem = nullptr; currentIndex = modelIndex; emit q->currentIndexChanged(); @@ -1673,7 +1699,7 @@ void QQuickItemViewPrivate::updateCurrent(int modelIndex) if (oldCurrentItem != currentItem && (!oldCurrentItem || !currentItem || oldCurrentItem->item != currentItem->item)) emit q->currentItemChanged(); - releaseItem(oldCurrentItem); + releaseItem(oldCurrentItem, reusableFlag); } void QQuickItemViewPrivate::clear(bool onDestruction) @@ -1683,17 +1709,17 @@ void QQuickItemViewPrivate::clear(bool onDestruction) bufferedChanges.reset(); timeline.clear(); - releaseVisibleItems(); + releaseVisibleItems(QQmlInstanceModel::NotReusable); visibleIndex = 0; for (FxViewItem *item : qAsConst(releasePendingTransition)) { item->releaseAfterTransition = false; - releaseItem(item); + releaseItem(item, QQmlInstanceModel::NotReusable); } releasePendingTransition.clear(); auto oldCurrentItem = currentItem; - releaseItem(currentItem); + releaseItem(currentItem, QQmlDelegateModel::NotReusable); currentItem = nullptr; if (oldCurrentItem) emit q->currentItemChanged(); @@ -1752,7 +1778,7 @@ void QQuickItemViewPrivate::refill(qreal from, qreal to) if (currentChanges.hasPendingChanges() || bufferedChanges.hasPendingChanges()) { currentChanges.reset(); bufferedChanges.reset(); - releaseVisibleItems(); + releaseVisibleItems(reusableFlag); } int prevCount = itemCount; @@ -1916,7 +1942,7 @@ void QQuickItemViewPrivate::layout() continue; } if (!success) { - releaseItem(*it); + releaseItem(*it, reusableFlag); it = releasePendingTransition.erase(it); continue; } @@ -2063,7 +2089,7 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult prepareRemoveTransitions(¤tChanges.removedItems); for (QHash<QQmlChangeSet::MoveKey, FxViewItem *>::Iterator it = currentChanges.removedItems.begin(); it != currentChanges.removedItems.end(); ++it) { - releaseItem(it.value()); + releaseItem(it.value(), reusableFlag); } currentChanges.removedItems.clear(); @@ -2072,7 +2098,7 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult if (currentItem->item && currentItem->attached) currentItem->attached->setIsCurrentItem(false); auto oldCurrentItem = currentItem; - releaseItem(currentItem); + releaseItem(currentItem, reusableFlag); currentItem = nullptr; if (oldCurrentItem) emit q->currentItemChanged(); @@ -2279,7 +2305,7 @@ void QQuickItemViewPrivate::viewItemTransitionFinished(QQuickItemViewTransitiona { for (int i=0; i<releasePendingTransition.count(); i++) { if (releasePendingTransition.at(i)->transitionableItem == item) { - releaseItem(releasePendingTransition.takeAt(i)); + releaseItem(releasePendingTransition.takeAt(i), reusableFlag); return; } } @@ -2385,7 +2411,23 @@ void QQuickItemView::destroyingItem(QObject *object) } } -bool QQuickItemViewPrivate::releaseItem(FxViewItem *item) +void QQuickItemView::onItemPooled(int modelIndex, QObject *object) +{ + Q_UNUSED(modelIndex); + + if (auto *attached = d_func()->getAttachedObject(object)) + emit attached->pooled(); +} + +void QQuickItemView::onItemReused(int modelIndex, QObject *object) +{ + Q_UNUSED(modelIndex); + + if (auto *attached = d_func()->getAttachedObject(object)) + emit attached->reused(); +} + +bool QQuickItemViewPrivate::releaseItem(FxViewItem *item, QQmlInstanceModel::ReusableFlag reusableFlag) { Q_Q(QQuickItemView); if (!item) @@ -2396,13 +2438,15 @@ bool QQuickItemViewPrivate::releaseItem(FxViewItem *item) QQmlInstanceModel::ReleaseFlags flags = {}; if (model && item->item) { - flags = model->release(item->item); + flags = model->release(item->item, reusableFlag); if (!flags) { // item was not destroyed, and we no longer reference it. QQuickItemPrivate::get(item->item)->setCulled(true); unrequestedItems.insert(item->item, model->indexOf(item->item, q)); } else if (flags & QQmlInstanceModel::Destroyed) { item->item->setParentItem(nullptr); + } else if (flags & QQmlInstanceModel::Pooled) { + item->setVisible(false); } } delete item; diff --git a/src/quick/items/qquickitemview_p.h b/src/quick/items/qquickitemview_p.h index 6bc00411f0..521580d292 100644 --- a/src/quick/items/qquickitemview_p.h +++ b/src/quick/items/qquickitemview_p.h @@ -110,6 +110,8 @@ class Q_QUICK_PRIVATE_EXPORT QQuickItemView : public QQuickFlickable Q_PROPERTY(qreal preferredHighlightEnd READ preferredHighlightEnd WRITE setPreferredHighlightEnd NOTIFY preferredHighlightEndChanged RESET resetPreferredHighlightEnd) Q_PROPERTY(int highlightMoveDuration READ highlightMoveDuration WRITE setHighlightMoveDuration NOTIFY highlightMoveDurationChanged) + Q_PROPERTY(bool reuseItems READ reuseItems WRITE setReuseItems NOTIFY reuseItemsChanged REVISION 15) + QML_NAMED_ELEMENT(ItemView) QML_UNCREATABLE("ItemView is an abstract base class.") QML_ADDED_IN_MINOR_VERSION(1) @@ -226,6 +228,9 @@ public: int highlightMoveDuration() const; virtual void setHighlightMoveDuration(int); + bool reuseItems() const; + void setReuseItems(bool reuse); + enum PositionMode { Beginning, Center, End, Visible, Contain, SnapPosition }; Q_ENUM(PositionMode) @@ -281,6 +286,8 @@ Q_SIGNALS: void preferredHighlightEndChanged(); void highlightMoveDurationChanged(); + Q_REVISION(15) void reuseItemsChanged(); + protected: void updatePolish() override; void componentComplete() override; @@ -296,6 +303,8 @@ protected Q_SLOTS: virtual void initItem(int index, QObject *item); void modelUpdated(const QQmlChangeSet &changeSet, bool reset); void destroyingItem(QObject *item); + void onItemPooled(int modelIndex, QObject *object); + void onItemReused(int modelIndex, QObject *object); void animStopped(); void trackedPositionChanged(); @@ -399,6 +408,9 @@ Q_SIGNALS: void prevSectionChanged(); void nextSectionChanged(); + void pooled(); + void reused(); + public: QPointer<QQuickItemView> m_view; bool m_isCurrent : 1; diff --git a/src/quick/items/qquickitemview_p_p.h b/src/quick/items/qquickitemview_p_p.h index b31f53b2c0..2942f9ddaf 100644 --- a/src/quick/items/qquickitemview_p_p.h +++ b/src/quick/items/qquickitemview_p_p.h @@ -174,7 +174,7 @@ public: void mirrorChange() override; FxViewItem *createItem(int modelIndex,QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested); - virtual bool releaseItem(FxViewItem *item); + virtual bool releaseItem(FxViewItem *item, QQmlInstanceModel::ReusableFlag reusableFlag); QQuickItem *createHighlightItem() const; QQuickItem *createComponentItem(QQmlComponent *component, qreal zValue, bool createDefault = false) const; @@ -238,15 +238,17 @@ public: q->polish(); } - void releaseVisibleItems() { + void releaseVisibleItems(QQmlInstanceModel::ReusableFlag reusableFlag) { // make a copy and clear the visibleItems first to avoid destroyed // items being accessed during the loop (QTBUG-61294) const QList<FxViewItem *> oldVisible = visibleItems; visibleItems.clear(); for (FxViewItem *item : oldVisible) - releaseItem(item); + releaseItem(item, reusableFlag); } + virtual QQuickItemViewAttached *getAttachedObject(const QObject *) const { return nullptr; } + QPointer<QQmlInstanceModel> model; QVariant modelVariant; int itemCount; @@ -288,6 +290,11 @@ public: QQmlComponent *footerComponent; FxViewItem *footer; + // Reusing delegate items cannot be on by default for backwards compatibility. + // Reusing an item will e.g mean that Component.onCompleted will only be called for an + // item when it's created and not when it's reused, which will break legacy applications. + QQmlInstanceModel::ReusableFlag reusableFlag = QQmlInstanceModel::NotReusable; + struct MovedItem { FxViewItem *item; QQmlChangeSet::MoveKey moveKey; diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp index 778f28bdd5..9206628716 100644 --- a/src/quick/items/qquicklistview.cpp +++ b/src/quick/items/qquicklistview.cpp @@ -92,7 +92,7 @@ public: FxViewItem *newViewItem(int index, QQuickItem *item) override; void initializeViewItem(FxViewItem *item) override; - bool releaseItem(FxViewItem *item) override; + bool releaseItem(FxViewItem *item, QQmlInstanceModel::ReusableFlag reusableFlag) override; void repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer) override; void repositionPackageItemAt(QQuickItem *item, int index) override; void resetFirstItemPosition(qreal pos = 0.0) override; @@ -139,6 +139,8 @@ public: bool flick(QQuickItemViewPrivate::AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, QQuickTimeLineCallback::Callback fixupCallback, qreal velocity) override; + QQuickItemViewAttached *getAttachedObject(const QObject *object) const override; + QQuickListView::Orientation orient; qreal visiblePos; qreal averageSize; @@ -634,15 +636,15 @@ void QQuickListViewPrivate::initializeViewItem(FxViewItem *item) } } -bool QQuickListViewPrivate::releaseItem(FxViewItem *item) +bool QQuickListViewPrivate::releaseItem(FxViewItem *item, QQmlInstanceModel::ReusableFlag reusableFlag) { if (!item || !model) - return QQuickItemViewPrivate::releaseItem(item); + return QQuickItemViewPrivate::releaseItem(item, reusableFlag); QPointer<QQuickItem> it = item->item; QQuickListViewAttached *att = static_cast<QQuickListViewAttached*>(item->attached); - bool released = QQuickItemViewPrivate::releaseItem(item); + bool released = QQuickItemViewPrivate::releaseItem(item, reusableFlag); if (released && it && att && att->m_sectionItem) { // We hold no more references to this item int i = 0; @@ -682,7 +684,7 @@ bool QQuickListViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, qreal int newModelIdx = qBound(0, modelIndex + count, model->count()); count = newModelIdx - modelIndex; if (count) { - releaseVisibleItems(); + releaseVisibleItems(reusableFlag); modelIndex = newModelIdx; visibleIndex = modelIndex; visiblePos = itemEnd + count * (averageSize + spacing); @@ -737,7 +739,7 @@ void QQuickListViewPrivate::removeItem(FxViewItem *item) releasePendingTransition.append(item); } else { qCDebug(lcItemViewDelegateLifecycle) << "\treleasing stationary item" << item->index << (QObject *)(item->item); - releaseItem(item); + releaseItem(item, reusableFlag); } } @@ -1772,6 +1774,12 @@ void QQuickListViewPrivate::setSectionHelper(QQmlContext *context, QQuickItem *s sectionItem->setProperty("section", section); } +QQuickItemViewAttached *QQuickListViewPrivate::getAttachedObject(const QObject *object) const +{ + QObject *attachedObject = qmlAttachedPropertiesObject<QQuickListView>(object); + return static_cast<QQuickItemViewAttached *>(attachedObject); +} + //---------------------------------------------------------------------------- /*! @@ -3186,6 +3194,13 @@ void QQuickListView::keyPressEvent(QKeyEvent *event) void QQuickListView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { Q_D(QQuickListView); + + if (d->model) { + // When the view changes size, we force the pool to + // shrink by releasing all pooled items. + d->model->drainReusableItemsPool(0); + } + if (d->isRightToLeft()) { // maintain position relative to the right edge qreal dx = newGeometry.width() - oldGeometry.width(); |