1 /*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "core/components/scroll/scrollable.h"
17
18 #include <chrono>
19
20 #include "base/log/ace_trace.h"
21 #include "base/log/log.h"
22 #include "base/ressched/ressched_report.h"
23
24 namespace OHOS::Ace {
25 namespace {
26
27 constexpr double SPRING_SCROLL_MASS = 0.5;
28 constexpr double SPRING_SCROLL_STIFFNESS = 100.0;
29 constexpr double SPRING_SCROLL_DAMPING = 15.55635;
30 constexpr double MAX_FRICTION = 0.766;
31 const RefPtr<SpringProperty> DEFAULT_OVER_SPRING_PROPERTY =
32 AceType::MakeRefPtr<SpringProperty>(SPRING_SCROLL_MASS, SPRING_SCROLL_STIFFNESS, SPRING_SCROLL_DAMPING);
33 constexpr std::chrono::milliseconds SCROLL_TIMEOUT = std::chrono::milliseconds(200);
34 #ifdef PRODUCT_RK
35 constexpr double FRICTION = 0.6;
36 constexpr double VELOCITY_SCALE = 1.0;
37 constexpr double MAX_VELOCITY = 2000.0;
38 constexpr double MIN_VELOCITY = -2000.0;
39 constexpr double ADJUSTABLE_VELOCITY = 3000.0;
40 #else
41 #ifndef WEARABLE_PRODUCT
42 constexpr double FRICTION = 1.0;
43 constexpr double VELOCITY_SCALE = 1.0;
44 constexpr double MAX_VELOCITY = 8000.0;
45 constexpr double MIN_VELOCITY = -8000.0;
46 constexpr double ADJUSTABLE_VELOCITY = 3000.0;
47 #else
48 constexpr double DISTANCE_EPSILON = 1.0;
49 constexpr double FRICTION = 0.9;
50 constexpr double VELOCITY_SCALE = 0.8;
51 constexpr double MAX_VELOCITY = 5000.0;
52 constexpr double MIN_VELOCITY = -5000.0;
53 constexpr double ADJUSTABLE_VELOCITY = 0.0;
54 #endif
55 #endif
56
57 } // namespace
58
59 // Static Functions.
60 double Scrollable::sFriction_ = FRICTION;
61 double Scrollable::sVelocityScale_ = VELOCITY_SCALE;
62
SetVelocityScale(double sVelocityScale)63 void Scrollable::SetVelocityScale(double sVelocityScale)
64 {
65 if (LessOrEqual(sVelocityScale, 0.0)) {
66 LOGW("Invalid velocity scale: %{public}lf", sVelocityScale);
67 return;
68 }
69 sVelocityScale_ = sVelocityScale;
70 }
71
SetFriction(double sFriction)72 void Scrollable::SetFriction(double sFriction)
73 {
74 if (LessOrEqual(sFriction, 0.0)) {
75 LOGW("Invalid friction value: %{public}lf", sFriction);
76 return;
77 }
78 sFriction_ = sFriction;
79 }
80
~Scrollable()81 Scrollable::~Scrollable()
82 {
83 // If animation still runs, force stop it.
84 controller_->Stop();
85 springController_->Stop();
86 }
87
Initialize(const WeakPtr<PipelineContext> & context)88 void Scrollable::Initialize(const WeakPtr<PipelineContext>& context)
89 {
90 context_ = context;
91 if (axis_ == Axis::VERTICAL) {
92 dragRecognizer_ = AceType::MakeRefPtr<VerticalDragRecognizer>();
93 } else {
94 dragRecognizer_ = AceType::MakeRefPtr<HorizontalDragRecognizer>();
95 }
96
97 dragRecognizer_->SetContext(context);
98
99 timeoutRecognizer_ = AceType::MakeRefPtr<TimeoutRecognizer>(context, dragRecognizer_, SCROLL_TIMEOUT);
100
101 dragRecognizer_->SetOnDragStart([weakScroll = AceType::WeakClaim(this)](const DragStartInfo& info) {
102 auto scroll = weakScroll.Upgrade();
103 if (scroll) {
104 // Send event to accessibility when scroll start.
105 auto context = scroll->GetContext().Upgrade();
106 if (context) {
107 AccessibilityEvent scrollEvent;
108 scrollEvent.nodeId = scroll->nodeId_;
109 scrollEvent.eventType = "scrollstart";
110 context->SendEventToAccessibility(scrollEvent);
111 }
112 scroll->moved_ = false;
113 scroll->HandleDragStart(info);
114 }
115 });
116 dragRecognizer_->SetOnDragUpdate([weakScroll = AceType::WeakClaim(this)](const DragUpdateInfo& info) {
117 auto scroll = weakScroll.Upgrade();
118 if (scroll) {
119 scroll->HandleDragUpdate(info);
120 }
121 });
122 dragRecognizer_->SetOnDragEnd([weakScroll = AceType::WeakClaim(this)](const DragEndInfo& info) {
123 auto scroll = weakScroll.Upgrade();
124 if (scroll) {
125 scroll->HandleDragEnd(info);
126 // Send event to accessibility when scroll stop.
127 auto context = scroll->GetContext().Upgrade();
128 if (context && scroll->IsStopped()) {
129 AccessibilityEvent scrollEvent;
130 scrollEvent.nodeId = scroll->nodeId_;
131 scrollEvent.eventType = "scrollend";
132 context->SendEventToAccessibility(scrollEvent);
133 }
134 }
135 });
136 dragRecognizer_->SetOnDragCancel([weakScroll = AceType::WeakClaim(this)]() {
137 auto scroll = weakScroll.Upgrade();
138 if (scroll && scroll->dragCancelCallback_) {
139 scroll->dragCancelCallback_();
140 }
141 });
142
143 // use RawRecognizer to receive next touch down event to stop animation.
144 rawRecognizer_ = AceType::MakeRefPtr<RawRecognizer>();
145
146 rawRecognizer_->SetOnTouchDown([weakScroll = AceType::WeakClaim(this)](const TouchEventInfo&) {
147 auto scroll = weakScroll.Upgrade();
148 if (scroll) {
149 scroll->HandleTouchDown();
150 }
151 });
152 rawRecognizer_->SetOnTouchUp([weakScroll = AceType::WeakClaim(this)](const TouchEventInfo&) {
153 auto scroll = weakScroll.Upgrade();
154 if (scroll) {
155 scroll->HandleTouchUp();
156 }
157 });
158
159 controller_ = AceType::MakeRefPtr<Animator>(context);
160 springController_ = AceType::MakeRefPtr<Animator>(context);
161
162 spring_ = GetDefaultOverSpringProperty();
163 available_ = true;
164 }
165
HandleTouchDown()166 void Scrollable::HandleTouchDown()
167 {
168 LOGD("handle touch down");
169 isTouching_ = true;
170
171 // If animation still runs, first stop it.
172 springController_->Stop();
173
174 if (!controller_->IsStopped()) {
175 controller_->Stop();
176 if (motion_) {
177 // Don't stop immediately, keep moving with a big friction.
178 motion_->Reset(MAX_FRICTION, motion_->GetCurrentPosition(), motion_->GetCurrentVelocity() / 2);
179 FixScrollMotion(motion_->GetCurrentPosition());
180 controller_->PlayMotion(motion_);
181 currentPos_ = motion_->GetCurrentPosition();
182 }
183 } else {
184 // Resets values.
185 currentPos_ = 0.0;
186 }
187 }
188
HandleTouchUp()189 void Scrollable::HandleTouchUp()
190 {
191 LOGD("handle touch up");
192 isTouching_ = false;
193 if (springController_->IsStopped()) {
194 if (scrollOverCallback_ && outBoundaryCallback_ && outBoundaryCallback_()) {
195 LOGD("need scroll to boundary");
196 ProcessScrollOverCallback(0.0);
197 }
198 }
199 }
200
IsAnimationNotRunning() const201 bool Scrollable::IsAnimationNotRunning() const
202 {
203 return !isTouching_ && !controller_->IsRunning() && !springController_->IsRunning();
204 }
205
Idle() const206 bool Scrollable::Idle() const
207 {
208 return !isTouching_ && controller_->IsStopped() && springController_->IsStopped();
209 }
210
IsStopped() const211 bool Scrollable::IsStopped() const
212 {
213 return (springController_ ? (springController_->IsStopped()) : true) &&
214 (controller_ ? (controller_->IsStopped()) : true);
215 }
216
StopScrollable()217 void Scrollable::StopScrollable()
218 {
219 if (controller_) {
220 controller_->Stop();
221 }
222 if (springController_) {
223 springController_->Stop();
224 }
225 }
226
HandleDragStart(const OHOS::Ace::DragStartInfo & info)227 void Scrollable::HandleDragStart(const OHOS::Ace::DragStartInfo& info)
228 {
229 ACE_FUNCTION_TRACE();
230 const auto dragPositionInMainAxis =
231 axis_ == Axis::VERTICAL ? info.GetGlobalLocation().GetY() : info.GetGlobalLocation().GetX();
232 LOGD("HandleDragStart. LocalLocation: %{public}s, GlobalLocation: %{public}s",
233 info.GetLocalLocation().ToString().c_str(), info.GetGlobalLocation().ToString().c_str());
234 #ifdef OHOS_PLATFORM
235 // Increase the cpu frequency when sliding.
236 ResSchedReport::GetInstance().ResSchedDataReport("slide_on");
237 #endif
238 UpdateScrollPosition(dragPositionInMainAxis, SCROLL_FROM_START);
239 RelatedEventStart();
240 auto node = scrollableNode_.Upgrade();
241 if (node) {
242 node->DispatchCancelPressAnimation();
243 }
244 }
245
HandleDragUpdate(const DragUpdateInfo & info)246 void Scrollable::HandleDragUpdate(const DragUpdateInfo& info)
247 {
248 ACE_FUNCTION_TRACE();
249 if (!springController_->IsStopped() || !controller_->IsStopped()) {
250 // If animation still runs, first stop it.
251 controller_->Stop();
252 springController_->Stop();
253 currentPos_ = 0.0;
254 }
255 LOGD("handle drag update, offset is %{public}lf", info.GetMainDelta());
256 if (RelatedScrollEventPrepare(Offset(0.0, info.GetMainDelta()))) {
257 return;
258 }
259 if (UpdateScrollPosition(info.GetMainDelta(), SCROLL_FROM_UPDATE)) {
260 moved_ = true;
261 }
262 }
263
HandleDragEnd(const DragEndInfo & info)264 void Scrollable::HandleDragEnd(const DragEndInfo& info)
265 {
266 LOGD("handle drag end, position is %{public}lf and %{public}lf, velocity is %{public}lf",
267 info.GetGlobalLocation().GetX(), info.GetGlobalLocation().GetY(), info.GetMainVelocity());
268 controller_->Stop();
269 springController_->Stop();
270 controller_->ClearAllListeners();
271 springController_->ClearAllListeners();
272 touchUp_ = false;
273 scrollPause_ = false;
274 double correctVelocity = std::clamp(info.GetMainVelocity(), MIN_VELOCITY + slipFactor_,
275 MAX_VELOCITY - slipFactor_);
276 correctVelocity = correctVelocity * sVelocityScale_;
277 currentVelocity_ = correctVelocity;
278 #ifdef OHOS_PLATFORM
279 ResSchedReport::GetInstance().ResSchedDataReport("slide_off");
280 #endif
281 if (dragEndCallback_) {
282 dragEndCallback_();
283 }
284 RelatedEventEnd();
285 bool isOutBoundary = outBoundaryCallback_ ? outBoundaryCallback_() : false;
286 if (isOutBoundary && scrollOverCallback_) {
287 ProcessScrollOverCallback(correctVelocity);
288 } else {
289 double mainPosition = GetMainOffset(info.GetGlobalLocation());
290 LOGD("[scrollMotion]position(%{public}lf), velocity(%{public}lf)", mainPosition, correctVelocity);
291 if (motion_) {
292 motion_->Reset(sFriction_, mainPosition, correctVelocity);
293 } else {
294 motion_ = AceType::MakeRefPtr<FrictionMotion>(sFriction_, mainPosition, correctVelocity);
295 motion_->AddListener([weakScroll = AceType::WeakClaim(this)](double value) {
296 auto scroll = weakScroll.Upgrade();
297 if (scroll) {
298 scroll->ProcessScrollMotion(value);
299 }
300 });
301 }
302
303 // change motion param when list item need to be center of screen on watch
304 FixScrollMotion(mainPosition);
305
306 // Resets values.
307 currentPos_ = mainPosition;
308 currentVelocity_ = 0.0;
309
310 // Starts motion.
311 controller_->ClearStartListeners();
312 controller_->ClearStopListeners();
313 controller_->AddStartListener([weak = AceType::WeakClaim(this)]() {
314 auto scroll = weak.Upgrade();
315 if (scroll) {
316 scroll->moved_ = true;
317 }
318 });
319 controller_->PlayMotion(motion_);
320 controller_->AddStopListener([weak = AceType::WeakClaim(this)]() {
321 auto scroll = weak.Upgrade();
322 if (scroll) {
323 scroll->ProcessScrollMotionStop();
324 // Send event to accessibility when scroll stop.
325 auto context = scroll->GetContext().Upgrade();
326 if (context && scroll->Idle()) {
327 AccessibilityEvent scrollEvent;
328 scrollEvent.nodeId = scroll->nodeId_;
329 scrollEvent.eventType = "scrollend";
330 context->SendEventToAccessibility(scrollEvent);
331 }
332 }
333 });
334 }
335 }
336
FixScrollMotion(double position)337 void Scrollable::FixScrollMotion(double position)
338 {
339 #ifdef WEARABLE_PRODUCT
340 if (motion_ && needCenterFix_ && watchFixCallback_) {
341 double finalPoisition = watchFixCallback_(motion_->GetFinalPosition(), position);
342 LOGD("final position before fix(%{public}lf), need to fix to position(%{public}lf)",
343 motion_->GetFinalPosition(), finalPoisition);
344 if (!NearEqual(finalPoisition, motion_->GetFinalPosition(), DISTANCE_EPSILON)) {
345 double velocity = motion_->GetVelocityByFinalPosition(finalPoisition);
346 motion_->Reset(sFriction_, position, velocity);
347
348 // fix again when velocity is less than velocity threshold
349 if (!NearEqual(finalPoisition, motion_->GetFinalPosition(), DISTANCE_EPSILON)) {
350 velocity = motion_->GetVelocityByFinalPosition(finalPoisition, 0.0);
351 motion_->Reset(sFriction_, position, velocity, 0.0);
352 }
353 LOGD("final position after fix (%{public}lf), ", motion_->GetFinalPosition());
354 }
355 }
356 #endif
357 };
358
StartSpringMotion(double mainPosition,double mainVelocity,const ExtentPair & extent,const ExtentPair & initExtent)359 void Scrollable::StartSpringMotion(
360 double mainPosition, double mainVelocity, const ExtentPair& extent, const ExtentPair& initExtent)
361 {
362 LOGD("[scroll] position(%{public}lf), mainVelocity(%{public}lf), minExtent(%{public}lf), maxExtent(%{public}lf), "
363 "initMinExtent(%{public}lf), initMaxExtent(%{public}lf",
364 mainPosition, mainVelocity, extent.Leading(), extent.Trailing(), initExtent.Leading(), initExtent.Trailing());
365 scrollMotion_ = AceType::MakeRefPtr<ScrollMotion>(mainPosition, mainVelocity, extent, initExtent, spring_);
366 if (!scrollMotion_->IsValid()) {
367 LOGE("scrollMotion is invalid, no available spring motion.");
368 return;
369 }
370 scrollMotion_->AddListener([weakScroll = AceType::WeakClaim(this)](double position) {
371 auto scroll = weakScroll.Upgrade();
372 if (scroll) {
373 scroll->ProcessSpringMotion(position);
374 }
375 });
376 currentPos_ = mainPosition;
377 springController_->ClearStopListeners();
378 springController_->PlayMotion(scrollMotion_);
379 springController_->AddStopListener([weak = AceType::WeakClaim(this)]() {
380 auto scroll = weak.Upgrade();
381 if (scroll) {
382 if (scroll->moved_ && scroll->scrollEndCallback_) {
383 scroll->scrollEndCallback_();
384 }
385 scroll->currentVelocity_ = 0.0;
386 scroll->moved_ = false;
387 if (scroll->scrollEnd_) {
388 scroll->scrollEnd_();
389 }
390 // Send event to accessibility when scroll stop.
391 auto context = scroll->GetContext().Upgrade();
392 if (context) {
393 AccessibilityEvent scrollEvent;
394 scrollEvent.nodeId = scroll->nodeId_;
395 scrollEvent.eventType = "scrollend";
396 context->SendEventToAccessibility(scrollEvent);
397 }
398 }
399 });
400 }
401
ProcessScrollMotionStop()402 void Scrollable::ProcessScrollMotionStop()
403 {
404 if (!scrollPause_ && moved_ && scrollEndCallback_) {
405 scrollEndCallback_();
406 }
407
408 // spring effect special process
409 if (scrollPause_ && scrollOverCallback_) {
410 scrollPause_ = false;
411 ProcessScrollOverCallback(currentVelocity_);
412 } else {
413 currentVelocity_ = 0.0;
414 moved_ = false;
415 if (scrollEnd_) {
416 scrollEnd_();
417 }
418 }
419 }
420
ProcessSpringMotion(double position)421 void Scrollable::ProcessSpringMotion(double position)
422 {
423 LOGD("[scroll] currentPos_(%{public}lf), position(%{public}lf)", currentPos_, position);
424 currentVelocity_ = scrollMotion_->GetCurrentVelocity();
425 if (NearEqual(currentPos_, position)) {
426 UpdateScrollPosition(0.0, SCROLL_FROM_ANIMATION_SPRING);
427 } else {
428 if (!UpdateScrollPosition(position - currentPos_, SCROLL_FROM_ANIMATION_SPRING)) {
429 springController_->Stop();
430 } else if (!touchUp_) {
431 if (scrollTouchUpCallback_) {
432 scrollTouchUpCallback_();
433 }
434 touchUp_ = true;
435 }
436 }
437 currentPos_ = position;
438 }
439
ProcessScrollMotion(double position)440 void Scrollable::ProcessScrollMotion(double position)
441 {
442 currentVelocity_ = motion_->GetCurrentVelocity();
443 LOGD("[scrolling] position(%{public}lf), currentVelocity_(%{public}lf)", position, currentVelocity_);
444 if ((NearEqual(currentPos_, position))) {
445 UpdateScrollPosition(0.0, SCROLL_FROM_ANIMATION);
446 } else {
447 // UpdateScrollPosition return false, means reach to scroll limit.
448 if (!UpdateScrollPosition(position - currentPos_, SCROLL_FROM_ANIMATION)) {
449 controller_->Stop();
450 } else if (!touchUp_) {
451 if (scrollTouchUpCallback_) {
452 scrollTouchUpCallback_();
453 }
454 touchUp_ = true;
455 }
456 }
457 currentPos_ = position;
458
459 // spring effect special process
460 if (outBoundaryCallback_ && outBoundaryCallback_()) {
461 scrollPause_ = true;
462 controller_->Stop();
463 }
464 }
465
UpdateScrollPosition(const double offset,int32_t source) const466 bool Scrollable::UpdateScrollPosition(const double offset, int32_t source) const
467 {
468 bool ret = true;
469 if (callback_) {
470 ret = callback_(offset, source);
471 }
472 return ret;
473 }
474
ProcessScrollOverCallback(double velocity)475 void Scrollable::ProcessScrollOverCallback(double velocity)
476 {
477 if (outBoundaryCallback_ && !outBoundaryCallback_()) {
478 return;
479 }
480 // In the case of chain animation enabled, you need to switch the control point first,
481 // and then correct the offset value in notification process
482 if (notifyScrollOverCallback_) {
483 notifyScrollOverCallback_(velocity);
484 }
485 // then use corrected offset to make scroll motion.
486 if (scrollOverCallback_) {
487 scrollOverCallback_(velocity);
488 }
489 }
490
SetSlipFactor(double SlipFactor)491 void Scrollable::SetSlipFactor(double SlipFactor)
492 {
493 slipFactor_ = std::clamp(SlipFactor, 0.0, ADJUSTABLE_VELOCITY);
494 }
495
GetDefaultOverSpringProperty()496 const RefPtr<SpringProperty>& Scrollable::GetDefaultOverSpringProperty()
497 {
498 return DEFAULT_OVER_SPRING_PROPERTY;
499 }
500
501 } // namespace OHOS::Ace
502