I got the formula wrong.

Assume an initial swipe speed of S. Then the speed at time t is:

S_{t} = S – C * t,

where C is a coefficient of friction.

This means the position P_{t} at time T is:

P_{t} = S * t – C * t^{2} / 2.

The total animation time T is when S_{t} = 0, at time S / C. This also implies that at the end of the animation, the final resting position is:

P_{end} = P_{S/C} = S^{2}/(2*C).

It is easy enough to adjust the speed so that at the final end time the animation stops at an even location; simply update the resting location P’_{end} and solve for S’: S’ = sqrt(2 * C * P’_{end}).

I had out-thought myself by creating a formula with a constant animation time, but it resulted in “floating” which felt unsatisfactory. This morning I realized that it’s a matter of simply solving for a coefficient of friction, which is close enough to how the real world works that the animation for FlowCover feels “right.”

Doh!

*Footnote:* Beware that this assumes speed is positive. If speed is negative (because you’re swiping the other way) you need to set a flag that indicates direction, or substitute -C for C in the formulas above. A corner case, of course, is if you adjust the speed and wind up going in the opposite direction. And of course in the last formula, by convention sqrt() returns the positive root, though mathematically there are two roots–a positive and a negative root. (That’s because -S^{2} = S^{2}.) So you need to use the negative root if P_{end} is negative.