• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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