Skip to content

Commit

Permalink
QQuickListView: implement support for reusing items
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Richard Moe Gustavsen committed Dec 4, 2019
1 parent 8c72e63 commit 1841a9e
Show file tree
Hide file tree
Showing 13 changed files with 718 additions and 28 deletions.
4 changes: 2 additions & 2 deletions src/quick/items/qquickgridview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -576,7 +576,7 @@ void QQuickGridViewPrivate::removeItem(FxViewItem *item)
item->releaseAfterTransition = true;
releasePendingTransition.append(item);
} else {
releaseItem(item);
releaseItem(item, QQmlDelegateModel::NotReusable);
}
}

Expand Down
76 changes: 60 additions & 16 deletions src/quick/items/qquickitemview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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)
Expand All @@ -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();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1916,7 +1942,7 @@ void QQuickItemViewPrivate::layout()
continue;
}
if (!success) {
releaseItem(*it);
releaseItem(*it, reusableFlag);
it = releasePendingTransition.erase(it);
continue;
}
Expand Down Expand Up @@ -2063,7 +2089,7 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult
prepareRemoveTransitions(&currentChanges.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();

Expand All @@ -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();
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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)
Expand All @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions src/quick/items/qquickitemview_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -226,6 +228,9 @@ class Q_QUICK_PRIVATE_EXPORT QQuickItemView : public QQuickFlickable
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)

Expand Down Expand Up @@ -281,6 +286,8 @@ class Q_QUICK_PRIVATE_EXPORT QQuickItemView : public QQuickFlickable
void preferredHighlightEndChanged();
void highlightMoveDurationChanged();

Q_REVISION(15) void reuseItemsChanged();

protected:
void updatePolish() override;
void componentComplete() override;
Expand All @@ -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();

Expand Down Expand Up @@ -399,6 +408,9 @@ class Q_QUICK_PRIVATE_EXPORT QQuickItemViewAttached : public QObject
void prevSectionChanged();
void nextSectionChanged();

void pooled();
void reused();

public:
QPointer<QQuickItemView> m_view;
bool m_isCurrent : 1;
Expand Down
13 changes: 10 additions & 3 deletions src/quick/items/qquickitemview_p_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class Q_AUTOTEST_EXPORT QQuickItemViewPrivate : public QQuickFlickablePrivate, p
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;
Expand Down Expand Up @@ -238,15 +238,17 @@ class Q_AUTOTEST_EXPORT QQuickItemViewPrivate : public QQuickFlickablePrivate, p
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;
Expand Down Expand Up @@ -288,6 +290,11 @@ class Q_AUTOTEST_EXPORT QQuickItemViewPrivate : public QQuickFlickablePrivate, p
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;
Expand Down
27 changes: 21 additions & 6 deletions src/quick/items/qquicklistview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class QQuickListViewPrivate : public QQuickItemViewPrivate

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;
Expand Down Expand Up @@ -139,6 +139,8 @@ class QQuickListViewPrivate : public QQuickItemViewPrivate
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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);
}

//----------------------------------------------------------------------------

/*!
Expand Down Expand Up @@ -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();
Expand Down
Loading

0 comments on commit 1841a9e

Please sign in to comment.