| Index: content/browser/android/overscroll_refresh.cc
|
| diff --git a/content/browser/android/overscroll_refresh.cc b/content/browser/android/overscroll_refresh.cc
|
| index 49145143e7215e9452cd8c25c6395c99487a6a9e..219749a0a3cd4e2523468993824549f1f2711695 100644
|
| --- a/content/browser/android/overscroll_refresh.cc
|
| +++ b/content/browser/android/overscroll_refresh.cc
|
| @@ -8,7 +8,9 @@
|
| #include "cc/trees/layer_tree_host.h"
|
| #include "content/browser/android/animation_utils.h"
|
| #include "ui/base/android/system_ui_resource_manager.h"
|
| +#include "ui/gfx/screen.h"
|
|
|
| +using std::abs;
|
| using std::max;
|
| using std::min;
|
|
|
| @@ -19,9 +21,19 @@ const ui::SystemUIResourceType kIdleResourceType = ui::OVERSCROLL_REFRESH_IDLE;
|
| const ui::SystemUIResourceType kActiveResourceType =
|
| ui::OVERSCROLL_REFRESH_ACTIVE;
|
|
|
| +// Default offset in dips from the top of the view to where the progress spinner
|
| +// should stop.
|
| +const int kDefaultSpinnerTargetDips = 64;
|
| +
|
| +// Drag movement multiplier between user input and effect translation.
|
| +const float kDragRate = .5f;
|
| +
|
| // Animation duration after the effect is released without triggering a refresh.
|
| const int kRecedeTimeMs = 300;
|
|
|
| +// Animation duration immediately after the effect is released and activated.
|
| +const int kActivationStartTimeMs = 150;
|
| +
|
| // Animation duration after the effect is released and triggers a refresh.
|
| const int kActivationTimeMs = 1000;
|
|
|
| @@ -31,24 +43,15 @@ const int kMaxActivationTimeMs = kActivationTimeMs * 3;
|
| // Animation duration after the refresh activated phase has completed.
|
| const int kActivationRecedeTimeMs = 300;
|
|
|
| -// Input threshold required to activate the refresh.
|
| -const float kPullActivationThreshold = .35f;
|
| -
|
| // Input threshold required to start glowing.
|
| -const float kGlowActivationThreshold = kPullActivationThreshold * 0.85f;
|
| +const float kGlowActivationThreshold = 0.85f;
|
|
|
| // Useful for avoiding accidental triggering when a scroll janks (is delayed),
|
| // capping the impulse per event.
|
| -const float kMaxNormalizedDeltaPerPull = kPullActivationThreshold / 4.f;
|
| -
|
| -// Maximum offset of the effect relative to the content size.
|
| -const float kMaxRelativeOffset = .3f;
|
| +const int kMinPullsToActivate = 4;
|
|
|
| // Minimum alpha for the effect layer.
|
| -const float kMinAlpha = 0.25f;
|
| -
|
| -// Controls spin velocity.
|
| -const float kPullRotationMultiplier = 180.f * (1.f / kPullActivationThreshold);
|
| +const float kMinAlpha = 0.3f;
|
|
|
| // Experimentally determined constant used to allow activation even if touch
|
| // release results in a small upward fling (quite common during a slow scroll).
|
| @@ -60,7 +63,7 @@ void UpdateLayer(cc::UIResourceLayer* layer,
|
| cc::Layer* parent,
|
| cc::UIResourceId res_id,
|
| const gfx::SizeF& viewport_size,
|
| - float relative_offset,
|
| + float offset,
|
| float opacity,
|
| float rotation) {
|
| if (layer->parent() != parent)
|
| @@ -96,11 +99,8 @@ void UpdateLayer(cc::UIResourceLayer* layer,
|
| layer->SetContentsOpaque(false);
|
| layer->SetOpacity(Clamp(opacity, 0.f, 1.f));
|
|
|
| - float min_viewport_size = min(viewport_size.width(), viewport_size.height());
|
| float offset_x = (viewport_size.width() - image_size.width()) * 0.5f;
|
| - float offset_y =
|
| - Damp(relative_offset, 1.2f) * min_viewport_size * kMaxRelativeOffset -
|
| - image_size.height();
|
| + float offset_y = offset - image_size.height();
|
| gfx::Transform transform;
|
| transform.Translate(offset_x, offset_y);
|
| transform.Rotate(rotation);
|
| @@ -115,6 +115,11 @@ class OverscrollRefresh::Effect {
|
| : resource_manager_(resource_manager),
|
| idle_layer_(cc::UIResourceLayer::Create()),
|
| active_layer_(cc::UIResourceLayer::Create()),
|
| + target_drag_(kDefaultSpinnerTargetDips *
|
| + gfx::Screen::GetNativeScreen()
|
| + ->GetPrimaryDisplay()
|
| + .device_scale_factor()),
|
| + drag_(0),
|
| idle_alpha_(0),
|
| active_alpha_(0),
|
| offset_(0),
|
| @@ -128,32 +133,51 @@ class OverscrollRefresh::Effect {
|
| rotation_start_(0),
|
| rotation_finish_(0),
|
| state_(STATE_IDLE) {
|
| + DCHECK(target_drag_);
|
| idle_layer_->SetIsDrawable(false);
|
| active_layer_->SetIsDrawable(false);
|
| }
|
|
|
| ~Effect() { Detach(); }
|
|
|
| - void Pull(float normalized_delta) {
|
| + void Pull(float delta) {
|
| if (state_ != STATE_PULL)
|
| - offset_ = 0;
|
| + drag_ = 0;
|
|
|
| state_ = STATE_PULL;
|
|
|
| - normalized_delta = Clamp(normalized_delta, -kMaxNormalizedDeltaPerPull,
|
| - kMaxNormalizedDeltaPerPull);
|
| + delta *= kDragRate;
|
| + float max_delta = target_drag_ / kMinPullsToActivate;
|
| + delta = Clamp(delta, -max_delta, max_delta);
|
| +
|
| + drag_ += delta;
|
| + drag_ = Clamp(drag_, 0.f, target_drag_ * 3.f);
|
|
|
| - offset_ += normalized_delta;
|
| - offset_ = Clamp(offset_, 0.f, 1.f);
|
| + // The following logic and constants were taken from Android's refresh
|
| + // effect (see SwipeRefreshLayout.java from v4 of the AppCompat library).
|
| + float original_drag_percent = drag_ / target_drag_;
|
| + float drag_percent = min(1.f, abs(original_drag_percent));
|
| + float adjusted_percent = max(drag_percent - .4f, 0.f) * 5.f / 3.f;
|
| + float extra_os = abs(drag_) - target_drag_;
|
| + float slingshot_dist = target_drag_;
|
| + float tension_slingshot_percent =
|
| + max(0.f, min(extra_os, slingshot_dist * 2) / slingshot_dist);
|
| + float tension_percent = ((tension_slingshot_percent / 4) -
|
| + std::pow((tension_slingshot_percent / 4), 2.f)) *
|
| + 2.f;
|
| + float extra_move = slingshot_dist * tension_percent * 2;
|
| +
|
| + offset_ = slingshot_dist * drag_percent + extra_move;
|
| +
|
| + rotation_ =
|
| + 360.f * ((-0.25f + .4f * adjusted_percent + tension_percent * 2) * .5f);
|
|
|
| idle_alpha_ =
|
| - kMinAlpha + (1.f - kMinAlpha) * offset_ / kGlowActivationThreshold;
|
| - active_alpha_ = (offset_ - kGlowActivationThreshold) /
|
| - (kPullActivationThreshold - kGlowActivationThreshold);
|
| + kMinAlpha + (1.f - kMinAlpha) * drag_percent / kGlowActivationThreshold;
|
| + active_alpha_ = (drag_percent - kGlowActivationThreshold) /
|
| + (1.f - kGlowActivationThreshold);
|
| idle_alpha_ = Clamp(idle_alpha_, 0.f, 1.f);
|
| active_alpha_ = Clamp(active_alpha_, 0.f, 1.f);
|
| -
|
| - rotation_ = kPullRotationMultiplier * Damp(offset_, 1.f);
|
| }
|
|
|
| bool Animate(base::TimeTicks current_time, bool still_refreshing) {
|
| @@ -164,8 +188,8 @@ class OverscrollRefresh::Effect {
|
| return true;
|
|
|
| const double dt = (current_time - start_time_).InMilliseconds();
|
| - const double t = min(dt / duration_.InMilliseconds(), 1.);
|
| - const float interp = static_cast<float>(Damp(t, 1.));
|
| + const double t = dt / duration_.InMilliseconds();
|
| + const float interp = static_cast<float>(Damp(min(t, 1.), 1.));
|
|
|
| idle_alpha_ = Lerp(idle_alpha_start_, idle_alpha_finish_, interp);
|
| active_alpha_ = Lerp(active_alpha_start_, active_alpha_finish_, interp);
|
| @@ -180,6 +204,18 @@ class OverscrollRefresh::Effect {
|
| case STATE_PULL:
|
| NOTREACHED() << "Invalidate state for animation.";
|
| break;
|
| + case STATE_ACTIVATED_START:
|
| + // Briefly pause the animation after the rapid initial translation.
|
| + if (t < 1.5f)
|
| + break;
|
| + state_ = STATE_ACTIVATED;
|
| + start_time_ = current_time;
|
| + duration_ = base::TimeDelta::FromMilliseconds(kActivationTimeMs);
|
| + activated_start_time_ = current_time;
|
| + offset_start_ = offset_finish_ = offset_;
|
| + rotation_start_ = rotation_;
|
| + rotation_finish_ = rotation_start_ + 360.f;
|
| + break;
|
| case STATE_ACTIVATED:
|
| start_time_ = current_time;
|
| if (still_refreshing &&
|
| @@ -211,11 +247,23 @@ class OverscrollRefresh::Effect {
|
| }
|
|
|
| bool Release(base::TimeTicks current_time, bool allow_activation) {
|
| - if (state_ != STATE_ACTIVATED && state_ != STATE_PULL)
|
| - return false;
|
| + switch (state_) {
|
| + case STATE_PULL:
|
| + break;
|
|
|
| - if (state_ == STATE_ACTIVATED && allow_activation)
|
| - return false;
|
| + case STATE_ACTIVATED:
|
| + case STATE_ACTIVATED_START:
|
| + // Avoid redundant activations.
|
| + if (allow_activation)
|
| + return false;
|
| + break;
|
| +
|
| + case STATE_IDLE:
|
| + case STATE_ACTIVATED_RECEDE:
|
| + case STATE_RECEDE:
|
| + // These states have already been "released" in some fashion.
|
| + return false;
|
| + }
|
|
|
| start_time_ = current_time;
|
| idle_alpha_start_ = idle_alpha_;
|
| @@ -223,7 +271,7 @@ class OverscrollRefresh::Effect {
|
| offset_start_ = offset_;
|
| rotation_start_ = rotation_;
|
|
|
| - if (offset_ < kPullActivationThreshold || !allow_activation) {
|
| + if (drag_ < target_drag_ || !allow_activation) {
|
| state_ = STATE_RECEDE;
|
| duration_ = base::TimeDelta::FromMilliseconds(kRecedeTimeMs);
|
| idle_alpha_finish_ = 0;
|
| @@ -233,13 +281,13 @@ class OverscrollRefresh::Effect {
|
| return false;
|
| }
|
|
|
| - state_ = STATE_ACTIVATED;
|
| - duration_ = base::TimeDelta::FromMilliseconds(kActivationTimeMs);
|
| + state_ = STATE_ACTIVATED_START;
|
| + duration_ = base::TimeDelta::FromMilliseconds(kActivationStartTimeMs);
|
| activated_start_time_ = current_time;
|
| idle_alpha_finish_ = idle_alpha_start_;
|
| active_alpha_finish_ = active_alpha_start_;
|
| - offset_finish_ = kPullActivationThreshold;
|
| - rotation_finish_ = rotation_start_ + 360.f;
|
| + offset_finish_ = target_drag_;
|
| + rotation_finish_ = rotation_start_;
|
| return true;
|
| }
|
|
|
| @@ -258,20 +306,12 @@ class OverscrollRefresh::Effect {
|
| if (IsFinished())
|
| return;
|
|
|
| - UpdateLayer(idle_layer_.get(),
|
| - parent,
|
| - resource_manager_->GetUIResourceId(kIdleResourceType),
|
| - size,
|
| - offset_,
|
| - idle_alpha_,
|
| - rotation_);
|
| - UpdateLayer(active_layer_.get(),
|
| - parent,
|
| - resource_manager_->GetUIResourceId(kActiveResourceType),
|
| - size,
|
| - offset_,
|
| - active_alpha_,
|
| - rotation_);
|
| + UpdateLayer(idle_layer_.get(), parent,
|
| + resource_manager_->GetUIResourceId(kIdleResourceType), size,
|
| + offset_, idle_alpha_, rotation_);
|
| + UpdateLayer(active_layer_.get(), parent,
|
| + resource_manager_->GetUIResourceId(kActiveResourceType), size,
|
| + offset_, active_alpha_, rotation_);
|
| }
|
|
|
| bool IsFinished() const { return state_ == STATE_IDLE; }
|
| @@ -280,6 +320,7 @@ class OverscrollRefresh::Effect {
|
| enum State {
|
| STATE_IDLE = 0,
|
| STATE_PULL,
|
| + STATE_ACTIVATED_START,
|
| STATE_ACTIVATED,
|
| STATE_ACTIVATED_RECEDE,
|
| STATE_RECEDE
|
| @@ -295,6 +336,8 @@ class OverscrollRefresh::Effect {
|
| scoped_refptr<cc::UIResourceLayer> idle_layer_;
|
| scoped_refptr<cc::UIResourceLayer> active_layer_;
|
|
|
| + const float target_drag_;
|
| + float drag_;
|
| float idle_alpha_;
|
| float active_alpha_;
|
| float offset_;
|
| @@ -369,9 +412,7 @@ bool OverscrollRefresh::WillHandleScrollUpdate(
|
| return false;
|
|
|
| case ENABLED: {
|
| - float normalized_delta = scroll_delta.y() / min(viewport_size_.height(),
|
| - viewport_size_.width());
|
| - effect_->Pull(normalized_delta);
|
| + effect_->Pull(scroll_delta.y());
|
| return true;
|
| }
|
| }
|
|
|