Kinetic scrolling is the popular term to denote the scrolling of a long list with a bit of physics so that user feels like moving a wheel. Such a list view is then often referred as a flick list, caused the scrolling involves some sort of flicking gestures. Made popular in iPhone, flick list quickly invades other mobile platforms with touch screen because it just feels so natural and more usable than using the conventional approach of scroll bars.
How about Qt-based GUI applications? Up to now we do not offer the flickable list in any standard widgets. A practical approach would be to subclass QAbstractScrollArea and then implement your own. This does not really solve the problem if you have already tons of widgets based on, say, QListView, as you might not want to change the class structure and inheritance. Is there another solution?
Enter Flick Charm, today’s dojo example. This charm is only a proof of concept and will not have all the goodies found in iPhone (e.g. no overshoot or bouncing). However, making your list flickable is a matter of writing the code:
FlickCharm charm; charm.activateOn(widget);
where widget is any class derived from QAbstractScrollArea and this of course includes very useful classes like QScrollArea, QAbstractItemView (and thus also QListView, QTableView, QTreeView, and friends) and even QGraphicsView. As a bonus, QWebView is also supported so your QtWebKit-based browser can be easily made flickable, too. The beauty of Flick Charm is it works on any of those widgets and that you do not even need to change your code!
For the code and the example, check out:
svn checkout svn://labs.trolltech.com/svn/graphics/dojo/flickcharm cd flickcharm && qmake && make && ./flickcharm
The secret of Flick Charm is the use of QObject::eventFilter to intercept the mouse events. From this, a state machine tracks the changes and apply the scrolling to the target widget. It was fairly straightforward to implement, evidenced from the class implementation that weighs around 300 lines of code only.
No kinetic scrolling demo is complete with a screencast. You can also watch it on YouTube or blip.tv.
Note: this Flick Charm automagically hides the scroll bars in the scroll area or web view. This is done because it does not make sense to have flickable list with still showing the scroll bars. If you are unhappy with this, enable the scroll bars again or just change the code.
Update: for PyQt users, check out the Python version of this charm, ported by Akos Polster.
15 Responses to “Flick list or kinetic scrolling”
Well, I guess that handles the request I had in the last post!
Nice! Is it meant to be integrated to Qt 4.5?…
Nice stuff. Unfortunately left clicks are not handled very good. You have to perform a double-click to get a single-click.
I think the proper (or atleast better) way would be to look for a QEvent::MouseButtonRelease and if there is no movement (within some range) since the last QEvent::MouseButtonPressed a mouse click should be propagated.
It would be neat if this responded to wheel events too.
@Philippe: 4.5 is in feature freeze.
@Richard: this is only a dojo, not a complete solution, so wheel event support is left as an exercise for the reader ![]()
Hint: the state machine needs to be modified for wheel support and this would make the charm more complicated than necessary, hence I left it just like that.
Hi,
Still a little confused. Will this work with 4.4.x or will this require 4.5?
thanks!
@Ku T: Are you sure left-click does not work? I spent a great deal of time to ensure that both single-click and double-click still works. See e.g. that single-click on a link still opens that web site in the web view.
@qtuser: I didn’t mention anything 4.5-specific in the article. Thus you can safely assume that this works for both 4.4 and 4.5.
@ariya:
Really strange. I just tried it under Windows (XP/Qt 4.4.3) and it works as intended.
While with Qt 4.4.3 on Ubuntu Intrepid, for both the WebKit and the GraphicsView I have to click twice to activate an item or a link. Maybe it is a Ubuntu (or qtcopy) patch?! From your video I see that you use Linux too, so it can’t be that alone. I have no Qt 4.5 build for Linux at the moment to test it with.
Another little thing:
You can’t really accelerate if you flick from the same spot. The view jumps back again and then scrolls over the same area. This happens on both Windows and Linux. For example, just flick constantly from the center to the top. You would expect that it accelerates more and more and scrolls down further. Instead it roughly stays around one position and jumps up and down.
I really would like to see this implemented in Qt 4.6 at least ![]()
Very nice Ariya. Saves me a ton of work ![]()
Unfortunately it doesn’t works as nice for list views like it does for the canvas view.
You just can not control the scrolling in a list view it just going wild [Qt4.4.2 - VS2005].
Jacek wrote the code for this two years ago. It’s in dev/research/minibrowser, or at least was (also, his version supported bouncing on edges)
thanks a lot!
Ever since i’ve laid hands on an iPhone, i wanted to make this work for our touchscreen-based project as well.
Hmm, looks like i can use my time on other goodies now
like “slide to activate” widgets etc.
@Zack: I will be shocked if nobody has written something like this before
From what I saw, this one is different because it aims on making any scroll area flickable without touching its code.
Thanks for this…
I’ve made also a little patch to show the scroll-bars only while scrolling:
Index: flickcharm.cpp
===================================================================
— flickcharm.cpp (revisione 858)
+++ flickcharm.cpp (copia locale)
@@ -164,6 +164,22 @@
frame->evaluateJavaScript(QString(”window.scrollTo(%1,%2);”).arg(p.x()).arg(p.y()));
}
+static void showScrollBars(QWidget *widget) {
+ QAbstractScrollArea *scrollArea = dynamic_cast(widget);
+ if (scrollArea) {
+ scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ }
+}
+
+static void hideScrollBars(QWidget *widget) {
+ QAbstractScrollArea *scrollArea = dynamic_cast(widget);
+ if (scrollArea) {
+ scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ }
+}
+
static QPoint deaccelerate(const QPoint &speed, int a = 1, int max = 64)
{
int x = qBound(-max, speed.x(), max);
@@ -283,6 +299,9 @@
while (item.hasNext()) {
item.next();
FlickData *data = item.value();
+
+ if (data->state == FlickData::ManualScroll || data->state == FlickData::AutoScroll)
+ showScrollBars(data->widget);
if (data->state == FlickData::ManualScroll) {
count++;
@@ -295,8 +314,10 @@
data->speed = deaccelerate(data->speed);
QPoint p = scrollOffset(data->widget);
setScrollOffset(data->widget, p - data->speed);
- if (data->speed == QPoint(0, 0))
+ if (data->speed == QPoint(0, 0)) {
data->state = FlickData::Steady;
+ hideScrollBars(data->widget);
+ }
}
}
Btw an implementation of kinetic scrolling should be also available on Qt Extended (and it has bouncing and more)…
A little bit tuned: http://pastebin.ubuntu.com/78596/