开发者

Calculating scroll inertia/momentum?

How would I calculate the scrolling momentum for a scroll event?

I understand that there have to be two timestamps at the beginning of the scrolling at the end. There also has to be an "axis change" variable which is 开发者_如何学编程basically the amount to scroll by without inertia.

This is my current code responsible for ending of scrolling:

if ((type == kMXTEnd || type == kMXTMovedOut) && _isScrolling)
    {
        long int finishTime = MXTimestamp();
        printf("SCEnd: Ending scroll at %ld\n",finishTime-_beginTime);

        /* scrollX is the change in X axis */
        /* finishTime is time from touch down to touch up */

        printf("  * Time: %ld ChangeX: %f\n",finishTime,scrollX);

        _isScrolling = FALSE;
        _originalScrollPoint = _scrollPoint;
    }

Is it possible to calculate an "inertia addition" for that? Like an additional offset gained by inertia which I can scroll by in addition to the primary scroll value. Or do I need to get additional variables?

I need this because I'm writing my own UI toolkit, which isn't really based on anything.


What I've done with good results is the following.

On each mouse drag event (or touch event), you store the velocity (so the amount of movement divided by the time since the last frame) and a timestamp. You only need the last one, so that's just two variables.

When the mouse/touch is released, check to see if the last timestamp is recent enough (I use 0.3 seconds). If so, set a variable inertialVelocity to the last calculated velocity; otherwise set it to 0 to prevent scrolling if the user carefully selected a position.

Then on every update (either through a timer, or each render call, depending on how you're rendering), scroll by inertialVelocity * INERTIA_SCROLL_FACTOR (I use 0.9) and multiply inertialVelocity by INERTIA_ACCELERATION (I use 0.98).

You'll probably want to add a threshold, so scrolling stops if inertialVelocity becomes too small. I use 1 as a threshold, as my rendering lib uses floats as coordinates. If coordinates are integrals, it'll drop to zero by itself.

One thing to keep in mind is that inertialVelocity can be either positive or negative, depending on the direction.

So, in pseudo code:

OnMouseMove:
    inertialVelocity = moveDistance / (now - timeOfLastEvent)
    timeOfLastEvent = now

OnMouseUp:
    if (now - timeSinceLastEvent > 0.3f)
        inertialVelocity = 0

OnTimer/OnRender:
    // timeDelta is needed only when doing this on render events, just to make
    // it independent of the render speed. It is the time since the previous render
    scrollPosition += inertialVelocity * INERTIA_SCROLL_FACTOR * timeDelta
    inertialVelocity *= INERTIA_ACCELERATION * timeDelta
    // Keep in mind that velocity can be negative as well, hence the abs
    if (abs(inertialVelocity) < INERTIA_THRESHOLD)
        inertialVelocity = 0


You could simulate this with a "recent axis changes" queue.

If you store say the last half a second of changes with the corresponding timestamps, you can then test if the queue is longer than a value N (ie if the user dragged it quicker than usual towards the end). You know the total distance traveled in the last half a second, the time, from those you can get a speed.

Scale the speed to something reasonable (say.. for 15px/.5sec, map to ~25px/sec) and apply a negative acceleration (also appropiately scaled, for the example above, say -20px/sec) every couple of milliseconds (or as fast as your system can easily handle it, don't overstress it with this).

Then run a timer, updating the speed at each tick (speed+=accel*time_scale), then the position (position+=speed*time_scale). When the speed reaches 0 (or goes below it) kill the timer.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜