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

Unified Diff: content/browser/android/overscroll_controller_android.cc

Issue 679493002: [Android] Add a native pull-to-refresh overscroll effect (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Code review Created 6 years, 1 month 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 side-by-side diff with in-line comments
Download patch
Index: content/browser/android/overscroll_controller_android.cc
diff --git a/content/browser/android/overscroll_controller_android.cc b/content/browser/android/overscroll_controller_android.cc
new file mode 100644
index 0000000000000000000000000000000000000000..4e3b5fb5a67cf56303ff8ed9dcae466ede3a7151
--- /dev/null
+++ b/content/browser/android/overscroll_controller_android.cc
@@ -0,0 +1,228 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/android/overscroll_controller_android.h"
+
+#include "base/android/build_info.h"
+#include "cc/layers/layer.h"
+#include "cc/output/compositor_frame_metadata.h"
+#include "content/browser/android/content_view_core_impl.h"
+#include "content/browser/android/edge_effect.h"
+#include "content/browser/android/edge_effect_l.h"
+#include "content/common/input/did_overscroll_params.h"
+#include "ui/base/android/window_android.h"
+#include "ui/base/android/window_android_compositor.h"
+
+namespace content {
+namespace {
+
+// Used for conditional creation of EdgeEffect types for the overscroll glow.
+const int kAndroidLSDKVersion = 21;
+
+ui::SystemUIResourceManager* GetSystemUIResourceManager(
+ ContentViewCore* content_view_core) {
+ ui::WindowAndroid* window = content_view_core->GetWindowAndroid();
+ DCHECK(window);
+ ui::WindowAndroidCompositor* compositor = window->GetCompositor();
+ DCHECK(compositor);
+ return &compositor->GetSystemUIResourceManager();
+}
+
+scoped_ptr<EdgeEffectBase> CreateGlowEdgeEffect(
+ ui::SystemUIResourceManager* resource_manager,
+ float dpi_scale) {
+ DCHECK(resource_manager);
+ static bool use_l_flavoured_effect =
+ base::android::BuildInfo::GetInstance()->sdk_int() >= kAndroidLSDKVersion;
+ if (use_l_flavoured_effect)
+ return scoped_ptr<EdgeEffectBase>(new EdgeEffectL(resource_manager));
+
+ return scoped_ptr<EdgeEffectBase>(
+ new EdgeEffect(resource_manager, dpi_scale));
+}
+
+} // namespace
+
+OverscrollControllerAndroid::OverscrollControllerAndroid(
+ ContentViewCoreImpl* content_view_core)
+ : WebContentsObserver(content_view_core->GetWebContents()),
+ content_view_core_(content_view_core),
+ enabled_(true),
+ glow_effect_(base::Bind(&CreateGlowEdgeEffect,
+ GetSystemUIResourceManager(content_view_core),
+ content_view_core->GetDpiScale())),
+ refresh_effect_(GetSystemUIResourceManager(content_view_core), this),
+ triggered_refresh_active_(false) {
+}
+
+OverscrollControllerAndroid::~OverscrollControllerAndroid() {
+}
+
+bool OverscrollControllerAndroid::WillHandleGestureEvent(
+ const blink::WebGestureEvent& event) {
+ if (!enabled_)
+ return false;
+
+ bool handled = false;
+ bool maybe_needs_animate = false;
+ switch (event.type) {
+ case blink::WebInputEvent::GestureScrollBegin:
+ refresh_effect_.OnScrollBegin();
+ break;
+
+ case blink::WebInputEvent::GestureScrollUpdate: {
+ gfx::Vector2dF scroll_delta(event.data.scrollUpdate.deltaX,
+ event.data.scrollUpdate.deltaY);
+ scroll_delta.Scale(GetDpiScale());
+ maybe_needs_animate = true;
+ handled = refresh_effect_.WillHandleScrollUpdate(scroll_delta);
+ } break;
+
+ case blink::WebInputEvent::GestureScrollEnd:
+ refresh_effect_.OnScrollEnd(gfx::Vector2dF());
+ maybe_needs_animate = true;
+ break;
+
+ case blink::WebInputEvent::GestureFlingStart: {
+ gfx::Vector2dF scroll_velocity(event.data.flingStart.velocityX,
+ event.data.flingStart.velocityY);
+ scroll_velocity.Scale(GetDpiScale());
+ refresh_effect_.OnScrollEnd(scroll_velocity);
+ if (refresh_effect_.IsActive()) {
+ // TODO(jdduke): Figure out a cleaner way of suppressing a fling.
+ // It's important that the any downstream code sees a scroll-ending
+ // event (in this case GestureFlingStart) if it has seen a scroll begin.
+ // Thus, we cannot simply consume the fling. Changing the event type to
+ // a GestureScrollEnd might work in practice, but could lead to
+ // unexpected results. For now, simply truncate the fling velocity, but
+ // not to zero as downstream code may not expect a zero-velocity fling.
+ blink::WebGestureEvent& modified_event =
+ const_cast<blink::WebGestureEvent&>(event);
+ modified_event.data.flingStart.velocityX = .01f;
+ modified_event.data.flingStart.velocityY = .01f;
+ }
+ maybe_needs_animate = true;
+ } break;
+
+ default:
+ break;
+ }
+
+ if (maybe_needs_animate && refresh_effect_.IsActive())
+ content_view_core_->SetNeedsAnimate();
+
+ return handled;
+}
+
+void OverscrollControllerAndroid::OnGestureEventAck(
+ const blink::WebGestureEvent& event,
+ InputEventAckState ack_result) {
+ if (!enabled_)
+ return;
+
+ if (event.type == blink::WebInputEvent::GestureScrollUpdate) {
+ // The effect should only be allowed if both the causal touch events go
+ // unconsumed and the generated scroll events go unconsumed.
+ // TODO(jdduke): Prevent activation if the first touchmove was consumed,
+ // i.e., the first GSU was prevented.
+ bool scroll_consumed = ack_result == INPUT_EVENT_ACK_STATE_CONSUMED;
+ OverscrollRefresh::ActivationAllowance activation_allowance =
+ scroll_consumed ? OverscrollRefresh::PREVENT_ACTIVATION
+ : OverscrollRefresh::ALLOW_ACTIVATION;
+ refresh_effect_.OnScrollUpdateAck(activation_allowance);
+ }
+}
+
+void OverscrollControllerAndroid::OnOverscrolled(
+ const DidOverscrollParams& params) {
+ if (!enabled_)
+ return;
+
+ if (refresh_effect_.IsActive() ||
+ refresh_effect_.IsPendingScrollUpdateAck()) {
+ // An active (or potentially active) refresh effect should always pre-empt
+ // the passive glow effect.
+ return;
+ }
+
+ const float dpi_scale = GetDpiScale();
+ if (glow_effect_.OnOverscrolled(
+ base::TimeTicks::Now(),
+ gfx::ScaleVector2d(params.accumulated_overscroll, dpi_scale),
+ gfx::ScaleVector2d(params.latest_overscroll_delta, dpi_scale),
+ gfx::ScaleVector2d(params.current_fling_velocity, dpi_scale),
+ gfx::ScaleVector2d(
+ params.causal_event_viewport_point.OffsetFromOrigin(),
+ dpi_scale))) {
+ content_view_core_->SetNeedsAnimate();
+ }
+}
+
+bool OverscrollControllerAndroid::Animate(base::TimeTicks current_time) {
+ if (!enabled_)
+ return false;
+
+ scoped_refptr<cc::Layer> parent = content_view_core_->GetLayer();
+ bool needs_animate = refresh_effect_.Animate(current_time, parent.get());
+ needs_animate |= glow_effect_.Animate(current_time, parent.get());
+ return needs_animate;
+}
+
+void OverscrollControllerAndroid::OnFrameMetadataUpdated(
+ const cc::CompositorFrameMetadata& frame_metadata) {
+ const float scale_factor =
+ frame_metadata.page_scale_factor * frame_metadata.device_scale_factor;
+ gfx::SizeF viewport_size =
+ gfx::ScaleSize(frame_metadata.scrollable_viewport_size, scale_factor);
+ gfx::SizeF content_size =
+ gfx::ScaleSize(frame_metadata.root_layer_size, scale_factor);
+ gfx::Vector2dF content_scroll_offset =
+ gfx::ScaleVector2d(frame_metadata.root_scroll_offset, scale_factor);
+
+ refresh_effect_.UpdateDisplay(viewport_size, content_scroll_offset);
+ glow_effect_.UpdateDisplay(viewport_size, content_size,
+ content_scroll_offset);
+}
+
+void OverscrollControllerAndroid::Enable() {
+ enabled_ = true;
+}
+
+void OverscrollControllerAndroid::Disable() {
+ if (!enabled_)
+ return;
+ enabled_ = false;
+ if (!enabled_) {
+ refresh_effect_.Reset();
+ glow_effect_.Reset();
+ }
+}
+
+void OverscrollControllerAndroid::DidNavigateMainFrame(
+ const LoadCommittedDetails& details,
+ const FrameNavigateParams& params) {
+ // Once the main frame has navigated, there's little need to further animate
+ // the reload effect. Note that the effect will naturally time out should the
+ // reload be interruped for any reason.
+ triggered_refresh_active_ = false;
+}
+
+void OverscrollControllerAndroid::TriggerRefresh() {
+ triggered_refresh_active_ = false;
+ WebContents* web_contents = content_view_core_->GetWebContents();
+ if (web_contents) {
+ triggered_refresh_active_ = true;
+ web_contents->ReloadFocusedFrame(false);
+ }
+}
+
+bool OverscrollControllerAndroid::IsTriggeredRefreshActive() const {
+ return triggered_refresh_active_;
+}
+
+float OverscrollControllerAndroid::GetDpiScale() const {
+ return content_view_core_->GetDpiScale();
+}
+
+} // namespace content

Powered by Google App Engine
This is Rietveld 408576698