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

Side by Side Diff: content/browser/android/overscroll_refresh.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: Tests! 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 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 #include "content/browser/android/overscroll_refresh.h"
6
7 #include "cc/layers/ui_resource_layer.h"
8 #include "cc/trees/layer_tree_host.h"
9 #include "content/browser/android/animation_utils.h"
10 #include "ui/base/android/system_ui_resource_manager.h"
11
12 using std::max;
13 using std::min;
14
15 namespace content {
16 namespace {
17
18 const ui::SystemUIResourceType kIdleResourceType = ui::OVERSCROLL_REFRESH_IDLE;
19 const ui::SystemUIResourceType kActiveResourceType =
20 ui::OVERSCROLL_REFRESH_ACTIVE;
21
22 // Animation duration after the effect is released without triggering a refresh.
23 const int kRecedeTimeMs = 300;
24
25 // Animation duration after the effect is released and triggers a refresh.
26 const int kActivationTimeMs = 1000;
27
28 // Max animation duration after the effect is released and triggers a refresh.
29 const int kMaxActivationTimeMs = kActivationTimeMs * 3;
30
31 // Animation duration after the refresh activated phase has completed.
32 const int kActivationRecedeTimeMs = 300;
33
34 // Useful for avoiding accidental triggering when a scroll janks (is delayed),
35 // capping the impulse per event.
36 const float kMaxNormalizedDeltaPerPull = .05f;
37
38 // Input threshold required to activate the refresh.
39 const float kPullActivationThreshold = .25f;
Ted C 2014/10/31 23:52:05 What is this a percentage of? Screen size? If so
jdduke (slow) 2014/11/06 00:10:37 I think it's important to be at least somewhat cou
Ted C 2014/11/06 19:25:40 In general, we should match the refresh widget use
jdduke (slow) 2014/11/07 18:33:44 Yeah, they use a threshold invariant to the orient
40
41 // Input threshold required to start glowing.
42 const float kGlowActivationThreshold = kPullActivationThreshold * 0.85f;
43
44 // Maximum height of the effect relative to the content height.
45 const float kMaxRelativeHeight = .3f;
46
47 // Minimum alpha for the effect layer.
48 const float kMinAlpha = 0.25f;
49
50 // Controls spin velocity.
51 const float kPullRotationMultiplier = 180.f * (1.f / kPullActivationThreshold);
52
53 const float kEpsilon = 0.005f;
54
55 void UpdateLayer(cc::UIResourceLayer* layer,
56 cc::Layer* parent,
57 cc::UIResourceId res_id,
58 const gfx::SizeF& viewport_size,
59 float relative_offset,
60 float opacity,
61 float rotation) {
62 // An empty window size, while meaningless, is also relatively harmless, and
63 // will simply prevent any drawing of the layers.
64 if (viewport_size.IsEmpty()) {
65 layer->SetIsDrawable(false);
66 return;
67 }
68
69 if (!res_id) {
70 layer->SetIsDrawable(false);
71 return;
72 }
73
74 if (opacity == 0) {
75 layer->SetIsDrawable(false);
76 layer->SetOpacity(0);
77 return;
78 }
79
80 if (layer->parent() != parent)
81 parent->AddChild(layer);
82
83 if (!layer->layer_tree_host())
84 return;
85
86 gfx::Size image_size = layer->layer_tree_host()->GetUIResourceSize(res_id);
87 layer->SetUIResourceId(res_id);
88 layer->SetIsDrawable(true);
89 layer->SetTransformOrigin(
90 gfx::Point3F(image_size.width() * 0.5f, image_size.height() * 0.5f, 0));
91 layer->SetBounds(image_size);
92 layer->SetContentsOpaque(false);
93 layer->SetOpacity(Clamp(opacity, 0.f, 1.f));
94
95 float offset_x = (viewport_size.width() - image_size.width()) * 0.5f;
96 float offset_y =
97 Damp(relative_offset, 1.f) * viewport_size.height() * kMaxRelativeHeight -
98 image_size.height();
99 gfx::Transform transform;
100 transform.Translate(offset_x, offset_y);
101 transform.Rotate(rotation);
102 layer->SetTransform(transform);
103 }
104
105 } // namespace
106
107 class OverscrollRefresh::Effect {
108 public:
109 Effect(ui::SystemUIResourceManager* resource_manager)
110 : resource_manager_(resource_manager),
111 idle_layer_(cc::UIResourceLayer::Create()),
112 active_layer_(cc::UIResourceLayer::Create()),
113 idle_alpha_(0),
114 active_alpha_(0),
115 offset_(0),
116 rotation_(0),
117 idle_alpha_start_(0),
118 idle_alpha_finish_(0),
119 active_alpha_start_(0),
120 active_alpha_finish_(0),
121 offset_start_(0),
122 offset_finish_(0),
123 rotation_start_(0),
124 rotation_finish_(0),
125 state_(STATE_IDLE) {
126 idle_layer_->SetIsDrawable(false);
127 active_layer_->SetIsDrawable(false);
128 }
129
130 ~Effect() { Detach(); }
131
132 void Pull(float normalized_delta) {
133 if (state_ != STATE_PULL)
134 offset_ = 0;
135
136 state_ = STATE_PULL;
137
138 normalized_delta = Clamp(normalized_delta,
139 -kMaxNormalizedDeltaPerPull,
140 kMaxNormalizedDeltaPerPull);
141
142 offset_ += normalized_delta;
143 offset_ = Clamp(offset_, 0.f, 1.f);
144
145 idle_alpha_ =
146 kMinAlpha + (1.f - kMinAlpha) * offset_ / kGlowActivationThreshold;
147 active_alpha_ = (offset_ - kGlowActivationThreshold) /
148 (kPullActivationThreshold - kGlowActivationThreshold);
149 idle_alpha_ = Clamp(idle_alpha_, 0.f, 1.f);
150 active_alpha_ = Clamp(active_alpha_, 0.f, 1.f);
151
152 rotation_ = kPullRotationMultiplier * offset_;
153 }
154
155 bool Update(base::TimeTicks current_time, bool is_triggered_refresh_active) {
156 if (IsFinished())
157 return false;
158
159 if (state_ == STATE_PULL)
160 return true;
161
162 const double dt = (current_time - start_time_).InMilliseconds();
163 const double t = min(dt / duration_.InMilliseconds(), 1.);
164 const float interp = static_cast<float>(Damp(t, 1.));
165
166 idle_alpha_ = Lerp(idle_alpha_start_, idle_alpha_finish_, interp);
167 active_alpha_ = Lerp(active_alpha_start_, active_alpha_finish_, interp);
168 offset_ = Lerp(offset_start_, offset_finish_, interp);
169 rotation_ = Lerp(rotation_start_, rotation_finish_, interp);
170
171 if (t < 1.f - kEpsilon)
172 return true;
173
174 switch (state_) {
175 case STATE_IDLE:
176 case STATE_PULL:
177 NOTREACHED() << "Invalidate state for animation.";
178 break;
179 case STATE_ACTIVATED:
180 start_time_ = current_time;
181 if (is_triggered_refresh_active &&
182 (current_time - activated_start_time_ <
183 base::TimeDelta::FromMilliseconds(kMaxActivationTimeMs))) {
184 offset_start_ = offset_finish_ = offset_;
185 rotation_start_ = rotation_;
186 rotation_finish_ = rotation_start_ + 360.f;
187 break;
188 }
189 state_ = STATE_ACTIVATED_RECEDE;
190 duration_ = base::TimeDelta::FromMilliseconds(kActivationRecedeTimeMs);
191 idle_alpha_start_ = idle_alpha_;
192 active_alpha_start_ = active_alpha_;
193 idle_alpha_finish_ = 0;
194 active_alpha_finish_ = 0;
195 rotation_start_ = rotation_finish_ = rotation_;
196 offset_start_ = offset_finish_ = offset_;
197 break;
198 case STATE_ACTIVATED_RECEDE:
199 Finish();
200 break;
201 case STATE_RECEDE:
202 Finish();
203 break;
204 };
205
206 return !IsFinished();
207 }
208
209 bool Release(base::TimeTicks current_time, bool allow_activation) {
210 if (state_ != STATE_ACTIVATED && state_ != STATE_PULL)
211 return false;
212
213 if (state_ == STATE_ACTIVATED && allow_activation)
214 return false;
215
216 start_time_ = current_time;
217 idle_alpha_start_ = idle_alpha_;
218 active_alpha_start_ = active_alpha_;
219 offset_start_ = offset_;
220 rotation_start_ = rotation_;
221
222 if (offset_ < kPullActivationThreshold || !allow_activation) {
223 state_ = STATE_RECEDE;
224 duration_ = base::TimeDelta::FromMilliseconds(kRecedeTimeMs);
225 idle_alpha_finish_ = 0;
226 active_alpha_finish_ = 0;
227 offset_finish_ = 0;
228 rotation_finish_ = 0;
229 return false;
230 }
231
232 state_ = STATE_ACTIVATED;
233 duration_ = base::TimeDelta::FromMilliseconds(kActivationTimeMs);
234 activated_start_time_ = current_time;
235 idle_alpha_finish_ = idle_alpha_start_;
236 active_alpha_finish_ = active_alpha_start_;
237 offset_finish_ = kPullActivationThreshold;
238 rotation_finish_ = rotation_start_ + 360.f;
239 return true;
240 }
241
242 void Finish() {
243 Detach();
244 idle_layer_->SetIsDrawable(false);
245 active_layer_->SetIsDrawable(false);
246 offset_ = 0;
247 idle_alpha_ = 0;
248 active_alpha_ = 0;
249 rotation_ = 0;
250 state_ = STATE_IDLE;
251 }
252
253 void ApplyToLayers(const gfx::SizeF& size, cc::Layer* parent) {
254 if (IsFinished())
255 return;
256
257 UpdateLayer(idle_layer_.get(),
258 parent,
259 resource_manager_->GetUIResourceId(kIdleResourceType),
260 size,
261 offset_,
262 idle_alpha_,
263 rotation_);
264 UpdateLayer(active_layer_.get(),
265 parent,
266 resource_manager_->GetUIResourceId(kActiveResourceType),
267 size,
268 offset_,
269 active_alpha_,
270 rotation_);
271 }
272
273 bool IsFinished() const { return state_ == STATE_IDLE; }
274
275 private:
276 enum State {
277 STATE_IDLE = 0,
278 STATE_PULL,
279 STATE_ACTIVATED,
280 STATE_ACTIVATED_RECEDE,
281 STATE_RECEDE
282 };
283
284 void Detach() {
285 idle_layer_->RemoveFromParent();
286 active_layer_->RemoveFromParent();
287 }
288
289 ui::SystemUIResourceManager* const resource_manager_;
290
291 scoped_refptr<cc::UIResourceLayer> idle_layer_;
292 scoped_refptr<cc::UIResourceLayer> active_layer_;
293
294 float idle_alpha_;
295 float active_alpha_;
296 float offset_;
297 float rotation_;
298
299 float idle_alpha_start_;
300 float idle_alpha_finish_;
301 float active_alpha_start_;
302 float active_alpha_finish_;
303 float offset_start_;
304 float offset_finish_;
305 float rotation_start_;
306 float rotation_finish_;
307
308 base::TimeTicks start_time_;
309 base::TimeTicks activated_start_time_;
310 base::TimeDelta duration_;
311
312 State state_;
313 };
314
315 OverscrollRefresh::OverscrollRefresh(
316 ui::SystemUIResourceManager* resource_manager,
317 OverscrollRefreshClient* client)
318 : client_(client),
319 scroll_consumption_state_(DISABLED),
320 effect_(new Effect(resource_manager)) {
321 DCHECK(client);
322 }
323
324 OverscrollRefresh::~OverscrollRefresh() {
325 }
326
327 void OverscrollRefresh::Reset() {
328 scroll_consumption_state_ = DISABLED;
329 effect_->Finish();
330 }
331
332 void OverscrollRefresh::OnScrollBegin() {
333 bool allow_activation = false;
334 Release(allow_activation);
335 if (!scroll_offset_.y())
336 scroll_consumption_state_ = PENDING_SCROLL_UPDATE_ACK;
337 }
338
339 void OverscrollRefresh::OnScrollUpdateAck(bool consumed) {
340 // Intentionally skip the first scroll update to avoid issues
341 if (scroll_consumption_state_ == PENDING_SCROLL_UPDATE_ACK)
342 scroll_consumption_state_ = consumed ? DISABLED : ENABLED;
343 }
344
345 void OverscrollRefresh::OnScrollEnd(const gfx::Vector2dF& scroll_velocity) {
346 bool allow_activation = scroll_velocity.y() > -500;
Ted C 2014/10/31 23:52:05 where does -500 come from? should it be a constan
jdduke (slow) 2014/11/06 00:10:37 I'll pull it out as a constant, it was from trial
347 Release(allow_activation);
348 }
349
350 bool OverscrollRefresh::WillHandleScrollUpdate(
351 const gfx::Vector2dF& scroll_delta) {
352 if (!viewport_size_.height())
353 return false;
354
355 if (scroll_consumption_state_ == ENABLED) {
356 float normalized_delta = scroll_delta.y() / viewport_size_.height();
357 effect_->Pull(normalized_delta);
358 return true;
359 }
360
361 if (scroll_consumption_state_ == PENDING_SCROLL_UPDATE_ACK &&
362 scroll_delta.y() < 0) {
aelias_OOO_until_Jul13 2014/10/30 03:53:22 I'm having trouble following what this is for, cou
jdduke (slow) 2014/11/06 00:10:37 Done.
363 scroll_consumption_state_ = DISABLED;
364 }
365
366 return false;
367 }
368
369 bool OverscrollRefresh::Animate(base::TimeTicks current_time,
370 cc::Layer* parent_layer) {
371 DCHECK(parent_layer);
372 if (effect_->IsFinished())
373 return false;
374
375 bool is_triggered_refresh_active = client_->IsTriggeredRefreshActive();
376 if (effect_->Update(current_time, is_triggered_refresh_active))
377 effect_->ApplyToLayers(viewport_size_, parent_layer);
378
379 return !effect_->IsFinished();
380 }
381
382 bool OverscrollRefresh::IsActive() const {
383 return scroll_consumption_state_ == ENABLED || !effect_->IsFinished();
384 }
385
386 bool OverscrollRefresh::IsPendingScrollUpdateAck() const {
387 return scroll_consumption_state_ == PENDING_SCROLL_UPDATE_ACK;
388 }
389
390 void OverscrollRefresh::UpdateDisplay(
391 const gfx::SizeF& viewport_size,
392 const gfx::Vector2dF& content_scroll_offset) {
393 viewport_size_ = viewport_size;
394 scroll_offset_ = content_scroll_offset;
395 }
396
397 void OverscrollRefresh::Release(bool allow_activation) {
398 if (scroll_consumption_state_ == ENABLED) {
399 if (effect_->Release(base::TimeTicks::Now(), allow_activation))
400 client_->TriggerRefresh();
401 }
402 scroll_consumption_state_ = DISABLED;
403 }
404
405 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698