Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(330)

Side by Side Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackScroller.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.compositor.layouts.phone.stack;
6
7 import android.content.Context;
8 import android.hardware.SensorManager;
9 import android.util.Log;
10 import android.view.ViewConfiguration;
11
12 /**
13 * This class is vastly copied from {@link android.widget.OverScroller} but deco uples the time
14 * from the app time so it can be specified manually.
15 */
16 public class StackScroller {
17 private int mMode;
18
19 private final SplineStackScroller mScrollerX;
20 private final SplineStackScroller mScrollerY;
21
22 private final boolean mFlywheel;
23
24 private static final int SCROLL_MODE = 0;
25 private static final int FLING_MODE = 1;
26
27 private static float sViscousFluidScale;
28 private static float sViscousFluidNormalize;
29
30 /**
31 * Creates an StackScroller with a viscous fluid scroll interpolator and fly wheel.
32 * @param context
33 */
34 public StackScroller(Context context) {
35 mFlywheel = true;
36 mScrollerX = new SplineStackScroller(context);
37 mScrollerY = new SplineStackScroller(context);
38 initContants();
39 }
40
41 private static void initContants() {
42 // This controls the viscous fluid effect (how much of it)
43 sViscousFluidScale = 8.0f;
44 // must be set to 1.0 (used in viscousFluid())
45 sViscousFluidNormalize = 1.0f;
46 sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
47 }
48
49 /**
50 *
51 * Returns whether the scroller has finished scrolling.
52 *
53 * @return True if the scroller has finished scrolling, false otherwise.
54 */
55 public final boolean isFinished() {
56 return mScrollerX.mFinished && mScrollerY.mFinished;
57 }
58
59 /**
60 * Force the finished field to a particular value. Contrary to
61 * {@link #abortAnimation()}, forcing the animation to finished
62 * does NOT cause the scroller to move to the final x and y
63 * position.
64 *
65 * @param finished The new finished value.
66 */
67 public final void forceFinished(boolean finished) {
68 mScrollerX.mFinished = mScrollerY.mFinished = finished;
69 }
70
71 /**
72 * Returns the current X offset in the scroll.
73 *
74 * @return The new X offset as an absolute distance from the origin.
75 */
76 public final int getCurrX() {
77 return mScrollerX.mCurrentPosition;
78 }
79
80 /**
81 * Returns the current Y offset in the scroll.
82 *
83 * @return The new Y offset as an absolute distance from the origin.
84 */
85 public final int getCurrY() {
86 return mScrollerY.mCurrentPosition;
87 }
88
89 /**
90 * Returns where the scroll will end. Valid only for "fling" scrolls.
91 *
92 * @return The final X offset as an absolute distance from the origin.
93 */
94 public final int getFinalX() {
95 return mScrollerX.mFinal;
96 }
97
98 /**
99 * Returns where the scroll will end. Valid only for "fling" scrolls.
100 *
101 * @return The final Y offset as an absolute distance from the origin.
102 */
103 public final int getFinalY() {
104 return mScrollerY.mFinal;
105 }
106
107 /**
108 * Sets where the scroll will end. Valid only for "fling" scrolls.
109 *
110 * @param x The final X offset as an absolute distance from the origin.
111 */
112 public final void setFinalX(int x) {
113 mScrollerX.setFinalPosition(x);
114 }
115
116 private static float viscousFluid(float x) {
117 x *= sViscousFluidScale;
118 if (x < 1.0f) {
119 x -= (1.0f - (float) Math.exp(-x));
120 } else {
121 float start = 0.36787944117f; // 1/e == exp(-1)
122 x = 1.0f - (float) Math.exp(1.0f - x);
123 x = start + x * (1.0f - start);
124 }
125 x *= sViscousFluidNormalize;
126 return x;
127 }
128
129 /**
130 * Call this when you want to know the new location. If it returns true, the
131 * animation is not yet finished.
132 */
133 public boolean computeScrollOffset(long time) {
134 if (isFinished()) {
135 return false;
136 }
137
138 switch (mMode) {
139 case SCROLL_MODE:
140 // Any scroller can be used for time, since they were started
141 // together in scroll mode. We use X here.
142 final long elapsedTime = time - mScrollerX.mStartTime;
143
144 final int duration = mScrollerX.mDuration;
145 if (elapsedTime < duration) {
146 float q = (float) (elapsedTime) / duration;
147 q = viscousFluid(q);
148 mScrollerX.updateScroll(q);
149 mScrollerY.updateScroll(q);
150 } else {
151 abortAnimation();
152 }
153 break;
154
155 case FLING_MODE:
156 if (!mScrollerX.mFinished) {
157 if (!mScrollerX.update(time)) {
158 if (!mScrollerX.continueWhenFinished(time)) {
159 mScrollerX.finish();
160 }
161 }
162 }
163
164 if (!mScrollerY.mFinished) {
165 if (!mScrollerY.update(time)) {
166 if (!mScrollerY.continueWhenFinished(time)) {
167 mScrollerY.finish();
168 }
169 }
170 }
171
172 break;
173
174 default:
175 break;
176 }
177
178 return true;
179 }
180
181 /**
182 * Start scrolling by providing a starting point and the distance to travel.
183 *
184 * @param startX Starting horizontal scroll offset in pixels. Positive
185 * numbers will scroll the content to the left.
186 * @param startY Starting vertical scroll offset in pixels. Positive numbers
187 * will scroll the content up.
188 * @param dx Horizontal distance to travel. Positive numbers will scroll the
189 * content to the left.
190 * @param dy Vertical distance to travel. Positive numbers will scroll the
191 * content up.
192 * @param duration Duration of the scroll in milliseconds.
193 */
194 public void startScroll(int startX, int startY, int dx, int dy, long startTi me, int duration) {
195 mMode = SCROLL_MODE;
196 mScrollerX.startScroll(startX, dx, startTime, duration);
197 mScrollerY.startScroll(startY, dy, startTime, duration);
198 }
199
200 /**
201 * Call this when you want to 'spring back' into a valid coordinate range.
202 *
203 * @param startX Starting X coordinate
204 * @param startY Starting Y coordinate
205 * @param minX Minimum valid X value
206 * @param maxX Maximum valid X value
207 * @param minY Minimum valid Y value
208 * @param maxY Minimum valid Y value
209 * @return true if a springback was initiated, false if startX and startY we re
210 * already within the valid range.
211 */
212 public boolean springBack(
213 int startX, int startY, int minX, int maxX, int minY, int maxY, long time) {
214 mMode = FLING_MODE;
215
216 // Make sure both methods are called.
217 final boolean spingbackX = mScrollerX.springback(startX, minX, maxX, tim e);
218 final boolean spingbackY = mScrollerY.springback(startY, minY, maxY, tim e);
219 return spingbackX || spingbackY;
220 }
221
222 /**
223 * Start scrolling based on a fling gesture. The distance traveled will
224 * depend on the initial velocity of the fling.
225 *
226 * @param startX Starting point of the scroll (X)
227 * @param startY Starting point of the scroll (Y)
228 * @param velocityX Initial velocity of the fling (X) measured in pixels per second.
229 * @param velocityY Initial velocity of the fling (Y) measured in pixels per second
230 * @param minX Minimum X value. The scroller will not scroll past this point
231 * unless overX > 0. If overfling is allowed, it will use minX as
232 * a springback boundary.
233 * @param maxX Maximum X value. The scroller will not scroll past this point
234 * unless overX > 0. If overfling is allowed, it will use maxX as
235 * a springback boundary.
236 * @param minY Minimum Y value. The scroller will not scroll past this point
237 * unless overY > 0. If overfling is allowed, it will use minY as
238 * a springback boundary.
239 * @param maxY Maximum Y value. The scroller will not scroll past this point
240 * unless overY > 0. If overfling is allowed, it will use maxY as
241 * a springback boundary.
242 * @param overX Overfling range. If > 0, horizontal overfling in either
243 * direction will be possible.
244 * @param overY Overfling range. If > 0, vertical overfling in either
245 * direction will be possible.
246 */
247 public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX,
248 int minY, int maxY, int overX, int overY, long time) {
249 // Continue a scroll or fling in progress
250 if (mFlywheel && !isFinished()) {
251 float oldVelocityX = mScrollerX.mCurrVelocity;
252 float oldVelocityY = mScrollerY.mCurrVelocity;
253 if (Math.signum(velocityX) == Math.signum(oldVelocityX)
254 && Math.signum(velocityY) == Math.signum(oldVelocityY)) {
255 velocityX += oldVelocityX;
256 velocityY += oldVelocityY;
257 }
258 }
259
260 mMode = FLING_MODE;
261 mScrollerX.fling(startX, velocityX, minX, maxX, overX, time);
262 mScrollerY.fling(startY, velocityY, minY, maxY, overY, time);
263 }
264
265 /**
266 * Stops the animation. Contrary to {@link #forceFinished(boolean)},
267 * aborting the animating causes the scroller to move to the final x and y
268 * positions.
269 *
270 * @see #forceFinished(boolean)
271 */
272 public void abortAnimation() {
273 mScrollerX.finish();
274 mScrollerY.finish();
275 }
276
277 static class SplineStackScroller {
278 // Initial position
279 private int mStart;
280
281 // Current position
282 private int mCurrentPosition;
283
284 // Final position
285 private int mFinal;
286
287 // Initial velocity
288 private int mVelocity;
289
290 // Current velocity
291 private float mCurrVelocity;
292
293 // Constant current deceleration
294 private float mDeceleration;
295
296 // Animation starting time, in system milliseconds
297 private long mStartTime;
298
299 // Animation duration, in milliseconds
300 private int mDuration;
301
302 // Duration to complete spline component of animation
303 private int mSplineDuration;
304
305 // Distance to travel along spline animation
306 private int mSplineDistance;
307
308 // Whether the animation is currently in progress
309 private boolean mFinished;
310
311 // The allowed overshot distance before boundary is reached.
312 private int mOver;
313
314 // Fling friction
315 private final float mFlingFriction = ViewConfiguration.getScrollFriction ();
316
317 // Current state of the animation.
318 private int mState = SPLINE;
319
320 // Constant gravity value, used in the deceleration phase.
321 private static final float GRAVITY = 2000.0f;
322
323 // A context-specific coefficient adjusted to physical values.
324 private final float mPhysicalCoeff;
325
326 private static final float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
327 private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
328 private static final float START_TENSION = 0.5f;
329 private static final float END_TENSION = 1.0f;
330 private static final float P1 = START_TENSION * INFLEXION;
331 private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
332
333 private static final int NB_SAMPLES = 100;
334 private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1] ;
335 private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
336
337 private static final int SPLINE = 0;
338 private static final int CUBIC = 1;
339 private static final int BALLISTIC = 2;
340
341 static {
342 float xMin = 0.0f;
343 float yMin = 0.0f;
344 for (int i = 0; i < NB_SAMPLES; i++) {
345 final float alpha = (float) i / NB_SAMPLES;
346
347 float xMax = 1.0f;
348 float x, tx, coef;
349 while (true) {
350 x = xMin + (xMax - xMin) / 2.0f;
351 coef = 3.0f * x * (1.0f - x);
352 tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
353 if (Math.abs(tx - alpha) < 1E-5) break;
354 if (tx > alpha) {
355 xMax = x;
356 } else {
357 xMin = x;
358 }
359 }
360 SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
361
362 float yMax = 1.0f;
363 float y, dy;
364 while (true) {
365 y = yMin + (yMax - yMin) / 2.0f;
366 coef = 3.0f * y * (1.0f - y);
367 dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
368 if (Math.abs(dy - alpha) < 1E-5) break;
369 if (dy > alpha) {
370 yMax = y;
371 } else {
372 yMin = y;
373 }
374 }
375 SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
376 }
377 SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
378 }
379
380 SplineStackScroller(Context context) {
381 mFinished = true;
382 final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
383 mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
384 * 39.37f // inch/meter
385 * ppi * 0.84f; // look and feel tuning
386 }
387
388 void updateScroll(float q) {
389 mCurrentPosition = mStart + Math.round(q * (mFinal - mStart));
390 }
391
392 /*
393 * Get a signed deceleration that will reduce the velocity.
394 */
395 private static float getDeceleration(int velocity) {
396 return velocity > 0 ? -GRAVITY : GRAVITY;
397 }
398
399 /*
400 * Modifies mDuration to the duration it takes to get from start to newF inal using the
401 * spline interpolation. The previous duration was needed to get to oldF inal.
402 */
403 private void adjustDuration(int start, int oldFinal, int newFinal) {
404 final int oldDistance = oldFinal - start;
405 final int newDistance = newFinal - start;
406 final float x = Math.abs((float) newDistance / oldDistance);
407 final int index = (int) (NB_SAMPLES * x);
408 if (index < NB_SAMPLES) {
409 final float xInf = (float) index / NB_SAMPLES;
410 final float xSup = (float) (index + 1) / NB_SAMPLES;
411 final float tInf = SPLINE_TIME[index];
412 final float tSup = SPLINE_TIME[index + 1];
413 final float timeCoef = tInf + (x - xInf) / (xSup - xInf) * (tSup - tInf);
414 mDuration *= timeCoef;
415 }
416 }
417
418 void startScroll(int start, int distance, long startTime, int duration) {
419 mFinished = false;
420
421 mStart = start;
422 mFinal = start + distance;
423
424 mStartTime = startTime;
425 mDuration = duration;
426
427 // Unused
428 mDeceleration = 0.0f;
429 mVelocity = 0;
430 }
431
432 void finish() {
433 mCurrentPosition = mFinal;
434 // Not reset since WebView relies on this value for fast fling.
435 // TODO: restore when WebView uses the fast fling implemented in thi s class.
436 // mCurrVelocity = 0.0f;
437 mFinished = true;
438 }
439
440 void setFinalPosition(int position) {
441 mFinal = position;
442 mFinished = false;
443 }
444
445 boolean springback(int start, int min, int max, long time) {
446 mFinished = true;
447
448 mStart = mFinal = start;
449 mVelocity = 0;
450
451 mStartTime = time;
452 mDuration = 0;
453
454 if (start < min) {
455 startSpringback(start, min, 0);
456 } else if (start > max) {
457 startSpringback(start, max, 0);
458 }
459
460 return !mFinished;
461 }
462
463 private void startSpringback(int start, int end, int velocity) {
464 // mStartTime has been set
465 mFinished = false;
466 mState = CUBIC;
467 mStart = start;
468 mFinal = end;
469 final int delta = start - end;
470 mDeceleration = getDeceleration(delta);
471 // TODO take velocity into account
472 mVelocity = -delta; // only sign is used
473 mOver = Math.abs(delta);
474 mDuration = (int) (1000.0 * Math.sqrt(-2.0 * delta / mDeceleration)) ;
475 }
476
477 void fling(int start, int velocity, int min, int max, int over, long tim e) {
478 mOver = over;
479 mFinished = false;
480 mCurrVelocity = mVelocity = velocity;
481 mDuration = mSplineDuration = 0;
482 mStartTime = time;
483 mCurrentPosition = mStart = start;
484
485 if (start > max || start < min) {
486 startAfterEdge(start, min, max, velocity, time);
487 return;
488 }
489
490 mState = SPLINE;
491 double totalDistance = 0.0;
492
493 if (velocity != 0) {
494 mDuration = mSplineDuration = getSplineFlingDuration(velocity);
495 totalDistance = getSplineFlingDistance(velocity);
496 }
497
498 mSplineDistance = (int) (totalDistance * Math.signum(velocity));
499 mFinal = start + mSplineDistance;
500
501 // Clamp to a valid final position
502 if (mFinal < min) {
503 adjustDuration(mStart, mFinal, min);
504 mFinal = min;
505 }
506
507 if (mFinal > max) {
508 adjustDuration(mStart, mFinal, max);
509 mFinal = max;
510 }
511 }
512
513 private double getSplineDeceleration(int velocity) {
514 return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * m PhysicalCoeff));
515 }
516
517 private double getSplineFlingDistance(int velocity) {
518 final double l = getSplineDeceleration(velocity);
519 final double decelMinusOne = DECELERATION_RATE - 1.0;
520 return mFlingFriction * mPhysicalCoeff
521 * Math.exp(DECELERATION_RATE / decelMinusOne * l);
522 }
523
524 /* Returns the duration, expressed in milliseconds */
525 private int getSplineFlingDuration(int velocity) {
526 final double l = getSplineDeceleration(velocity);
527 final double decelMinusOne = DECELERATION_RATE - 1.0;
528 return (int) (1000.0 * Math.exp(l / decelMinusOne));
529 }
530
531 private void fitOnBounceCurve(int start, int end, int velocity) {
532 // Simulate a bounce that started from edge
533 final float durationToApex = -velocity / mDeceleration;
534 final float distanceToApex = velocity * velocity / 2.0f / Math.abs(m Deceleration);
535 final float distanceToEdge = Math.abs(end - start);
536 final float totalDuration = (float) Math.sqrt(
537 2.0 * (distanceToApex + distanceToEdge) / Math.abs(mDecelera tion));
538 mStartTime -= (int) (1000.0f * (totalDuration - durationToApex));
539 mStart = end;
540 mVelocity = (int) (-mDeceleration * totalDuration);
541 }
542
543 private void startBounceAfterEdge(int start, int end, int velocity) {
544 mDeceleration = getDeceleration(velocity == 0 ? start - end : veloci ty);
545 fitOnBounceCurve(start, end, velocity);
546 onEdgeReached();
547 }
548
549 private void startAfterEdge(int start, int min, int max, int velocity, l ong time) {
550 if (start > min && start < max) {
551 Log.e("StackScroller", "startAfterEdge called from a valid posit ion");
552 mFinished = true;
553 return;
554 }
555 final boolean positive = start > max;
556 final int edge = positive ? max : min;
557 final int overDistance = start - edge;
558 boolean keepIncreasing = overDistance * velocity >= 0;
559 if (keepIncreasing) {
560 // Will result in a bounce or a to_boundary depending on velocit y.
561 startBounceAfterEdge(start, edge, velocity);
562 } else {
563 final double totalDistance = getSplineFlingDistance(velocity);
564 if (totalDistance > Math.abs(overDistance)) {
565 fling(start, velocity, positive ? min : start, positive ? st art : max, mOver,
566 time);
567 } else {
568 startSpringback(start, edge, velocity);
569 }
570 }
571 }
572
573 private void onEdgeReached() {
574 // mStart, mVelocity and mStartTime were adjusted to their values wh en edge was reached.
575 float distance = mVelocity * mVelocity / (2.0f * Math.abs(mDecelerat ion));
576 final float sign = Math.signum(mVelocity);
577
578 if (distance > mOver) {
579 // Default deceleration is not sufficient to slow us down before boundary
580 mDeceleration = -sign * mVelocity * mVelocity / (2.0f * mOver);
581 distance = mOver;
582 }
583
584 mOver = (int) distance;
585 mState = BALLISTIC;
586 mFinal = mStart + (int) (mVelocity > 0 ? distance : -distance);
587 mDuration = -(int) (1000.0f * mVelocity / mDeceleration);
588 }
589
590 boolean continueWhenFinished(long time) {
591 switch (mState) {
592 case SPLINE:
593 // Duration from start to null velocity
594 if (mDuration < mSplineDuration) {
595 // If the animation was clamped, we reached the edge
596 mStart = mFinal;
597 // TODO Better compute speed when edge was reached
598 mVelocity = (int) mCurrVelocity;
599 mDeceleration = getDeceleration(mVelocity);
600 mStartTime += mDuration;
601 onEdgeReached();
602 } else {
603 // Normal stop, no need to continue
604 return false;
605 }
606 break;
607 case BALLISTIC:
608 mStartTime += mDuration;
609 startSpringback(mFinal, mStart, 0);
610 break;
611 case CUBIC:
612 return false;
613 }
614
615 update(time);
616 return true;
617 }
618
619 /*
620 * Update the current position and velocity for current time. Returns
621 * true if update has been done and false if animation duration has been
622 * reached.
623 */
624 boolean update(long time) {
625 final long currentTime = time - mStartTime;
626
627 if (currentTime > mDuration) {
628 return false;
629 }
630
631 double distance = 0.0;
632 switch (mState) {
633 case SPLINE: {
634 final float t = (float) currentTime / mSplineDuration;
635 final int index = (int) (NB_SAMPLES * t);
636 float distanceCoef = 1.f;
637 float velocityCoef = 0.f;
638 if (index < NB_SAMPLES) {
639 final float tInf = (float) index / NB_SAMPLES;
640 final float tSup = (float) (index + 1) / NB_SAMPLES;
641 final float dInf = SPLINE_POSITION[index];
642 final float dSup = SPLINE_POSITION[index + 1];
643 velocityCoef = (dSup - dInf) / (tSup - tInf);
644 distanceCoef = dInf + (t - tInf) * velocityCoef;
645 }
646
647 distance = distanceCoef * mSplineDistance;
648 mCurrVelocity = velocityCoef * mSplineDistance / mSplineDura tion * 1000.0f;
649 break;
650 }
651
652 case BALLISTIC: {
653 final float t = currentTime / 1000.0f;
654 mCurrVelocity = mVelocity + mDeceleration * t;
655 distance = mVelocity * t + mDeceleration * t * t / 2.0f;
656 break;
657 }
658
659 case CUBIC: {
660 final float t = (float) (currentTime) / mDuration;
661 final float t2 = t * t;
662 final float sign = Math.signum(mVelocity);
663 distance = sign * mOver * (3.0f * t2 - 2.0f * t * t2);
664 mCurrVelocity = sign * mOver * 6.0f * (-t + t2);
665 break;
666 }
667 }
668
669 mCurrentPosition = mStart + (int) Math.round(distance);
670
671 return true;
672 }
673 }
674 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698