Creating a Circular ListView on BlackBerry 10

March 16, 2014 Ergin Babani

Circular ListViews are common UI Elements used in mobile apps and the web. Examples of this can be seen on the Netflix movie browsing interface or the Facebook album gallery. Users are able to scroll through a number of list items continuously, looping back to the beginning when they reach the end of the list.

Cascades on BlackBerry 10 does not have a native element to create a circular ListView. This post will describe one of the ways such an element can be created through the use of a custom DataModel. As a starting point we want the ability to create a list with our items, and the ability to add new items to it.

The Code

Start off by creating a CircularDataModel which will hold the data for our ListView to display. For storing the data internally we can use a QListDataModel. It provides some convenient function we’ll make use of later.

#include <bb/cascades/QListDataModel>
#include <QtCore/QList>

#define LIST_LENGTH 100000
template <typename Item>
class CircularDataModel : public bb::cascades::DataModel {
public:
    CircularDataModel(const QList<Item>& other) : mListDataModel(other) {
    }
    virtual ~CircularDataModel() {}

    virtual int childCount (const QVariantList &indexPath) {
    }

    QVariant data(const QVariantList& indexPath) {
    }

    bool hasChildren(const QVariantList &indexPath) {
        return mListDataModel.hasChildren(indexPath);
    }

private:
    bb::cascades::QListDataModel<Item> mListDataModel;
};

We now override the childCount method inherited from ListModel to make our list appear larger than it actually is.


virtual int childCount (const QVariantList &indexPath) {
	if (indexPath.length() == 0) {
		int realCount = mListDataModel.childCount(indexPath);
		return LIST_LENGTH > realCount ? LIST_LENGTH : realCount;
	} else {
		return mListDataModel.childCount(indexPath);
	}
}

The code above will make sure that the list appears to have LIST_LENGTH items,

Next we override the data function of DataModel.


QVariant data(const QVariantList& indexPath) {
	if (indexPath.length() != 1) {
		return mListDataModel.data(indexPath);
	}
	int index = indexPath[0].toInt();
	int actualIndex = index % mListDataModel.size();

	QVariantList actualIndexPath;
	actualIndexPath << actualIndex;

	return mListDataModel.data(actualIndexPath);
}

Assume we have a ListView with 3 items, but due to our changes in childCount the ListView thinks we have 100,000 items.

When the ListView requests data for the 4th item on the list, the above code will instead give it data for the first item. For the 5th item it provides data from the second item and so on. This makes it look as if items repeat themselves.

Theoretically a user would be able to reach the end of the list if they keep scrolling, but with a large enough LIST_LENGTH this becomes very unlikely and the list appears infinite.

Finally we can set up our signals for the DataModel item changes, and provide a utility function to append new items to the list.


void append(const Item& newItem) {
	mListDataModel.append(newItem);
	emit itemsChanged(bb::cascades::DataModelChangeType::Update);
}

void initSignals() {
	QObject::connect(&mListDataModel, SIGNAL(itemAdded(QVariantList)),
			this, SIGNAL(itemAdded(QVariantList)));

	QObject::connect(&mListDataModel, SIGNAL(itemUpdated(QVariantList)),
			this, SIGNAL(itemUpdated(QVariantList)));

	QObject::connect(&mListDataModel, SIGNAL(itemRemoved(QVariantList)),
			this, SIGNAL(itemRemoved(QVariantList)));

	QObject::connect(&mListDataModel,
			SIGNAL(itemsChanged(bb::cascades::DataModelChangeType::Type,QSharedPointer)),
			this,
			SIGNAL(itemsChanged(bb::cascades::DataModelChangeType::Type,QSharedPointer)));
}

When we append a new item to the list we need to force a refresh of all the items in the ListView cache. Due to this being a circular list insertion of a new item affects multiple spots on the list, so we need to update all cached items.

Usage

Using our CircularDataModel is as simple as using a regular DataModel. We just need to tell the ListView to use it and we get the circular list behaviour.


	QStringList list;
	list << "aaa" << "bbb" << "ccc";
	mDataModel = new CircularDataModel(list);
	qDebug() << mListView; 	mListView->setDataModel(mDataModel);

 

Sample circular ListView

What it looks like

All code for this example can be found on github at https://github.com/xtreme-ergin-babani/CircularListView

About the Author

Biography

Previous
Test After in Java: Subclass and Override
Test After in Java: Subclass and Override

On a recent project, my team inherited a large, lightly-tested Java/Spring codebase. As we began to modify...

Next
My First Three Months at Pivotal, and the Road Ahead to ApacheCON 2014
My First Three Months at Pivotal, and the Road Ahead to ApacheCON 2014

Pivotal's Apache Hadoop leader, Roman Shaposhnik, shares what he has been up to for the first three months ...