1 /*
2 * Copyright (c) 2023-2025 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_ng/pattern/scrollable/scrollable.h"
17
18 #include "base/log/jank_frame_report.h"
19 #include "base/perfmonitor/perf_constants.h"
20 #include "base/perfmonitor/perf_monitor.h"
21 #include "base/ressched/ressched_report.h"
22 #include "core/common/layout_inspector.h"
23 #include "core/components_ng/pattern/scrollable/scrollable_animation_consts.h"
24 #include "core/components_ng/pattern/scrollable/scrollable_theme.h"
25 #include "core/pipeline_ng/pipeline_context.h"
26 #include "base/log/event_report.h"
27 #include "core/pipeline/base/constants.h"
28
29 namespace OHOS::Ace::NG {
30 namespace {
31
32 constexpr double CAP_COEFFICIENT = 0.45;
33 constexpr int32_t FIRST_THRESHOLD = 4;
34 constexpr int32_t SECOND_THRESHOLD = 10;
35 constexpr double CAP_FIXED_VALUE = 16.0;
36 constexpr uint32_t DRAG_INTERVAL_TIME = 400;
37 constexpr uint32_t MULTI_FLING_DISTANCE = 125;
38
39 #ifndef WEARABLE_PRODUCT
40 constexpr double FRICTION = 0.6;
41 constexpr double API11_FRICTION = 0.7;
42 constexpr double API12_FRICTION = 0.75;
43 constexpr double SLOW_FRICTION_THRESHOLD = 3000.0;
44 constexpr double SLOW_FRICTION = 1.0;
45 constexpr double VELOCITY_SCALE = 1.0;
46 constexpr double SLOW_VELOCITY_SCALE = 1.2;
47 constexpr double NEW_VELOCITY_SCALE = 1.5;
48 constexpr double ADJUSTABLE_VELOCITY = 3000.0;
49 #else
50 constexpr double DISTANCE_EPSILON = 1.0;
51 constexpr double FRICTION = 0.9;
52 constexpr double VELOCITY_SCALE = 0.8;
53 constexpr double ADJUSTABLE_VELOCITY = 0.0;
54 #endif
55 constexpr uint64_t MILLOS_PER_NANO_SECONDS = 1000 * 1000 * 1000;
56 constexpr uint64_t MIN_DIFF_VSYNC = 1000 * 1000; // min is 1ms
57 constexpr float DEFAULT_THRESHOLD = 0.75f;
58 constexpr float DEFAULT_SPRING_RESPONSE = 0.416f;
59 constexpr float DEFAULT_SPRING_DAMP = 0.99f;
60 constexpr uint32_t MAX_VSYNC_DIFF_TIME = 100 * 1000 * 1000; // max 100 ms
61 constexpr float START_FRICTION_VELOCITY_THRESHOLD = 240.0f;
62 constexpr float FRICTION_VELOCITY_THRESHOLD = 120.0f;
63 constexpr float SPRING_ACCURACY = 0.1;
64 constexpr float DEFAULT_MINIMUM_AMPLITUDE_PX = 1.0f;
65 constexpr float SCROLL_SNAP_MIN_STEP_THRESHOLD = 10.0f;
66 constexpr float SCROLL_SNAP_MIN_STEP = 1.0f;
67 #ifdef OHOS_PLATFORM
68 constexpr int64_t INCREASE_CPU_TIME_ONCE = 4000000000; // 4s(unit: ns)
69 #endif
70
71 #ifdef SUPPORT_DIGITAL_CROWN
72 constexpr double ANGULAR_VELOCITY_FACTOR = 0.001f;
73 constexpr float ANGULAR_VELOCITY_SLOW = 0.07f;
74 constexpr float ANGULAR_VELOCITY_MEDIUM = 0.2f;
75 constexpr float ANGULAR_VELOCITY_FAST = 0.54f;
76 constexpr float DISPLAY_CONTROL_RATIO_VERY_SLOW = 1.19f;
77 constexpr float DISPLAY_CONTROL_RATIO_SLOW = 1.87f;
78 constexpr float DISPLAY_CONTROL_RATIO_MEDIUM = 1.67f;
79 constexpr float DISPLAY_CONTROL_RATIO_FAST = 1.59f;
80 constexpr float CROWN_SENSITIVITY_LOW = 0.8f;
81 constexpr float CROWN_SENSITIVITY_MEDIUM = 1.0f;
82 constexpr float CROWN_SENSITIVITY_HIGH = 1.2f;
83 constexpr float RESPONSIVE_SPRING_AMPLITUDE_RATIO = 0.00025f;
84
85 constexpr float CROWN_START_FRICTION_VELOCITY_THRESHOLD = 6.0f;
86 #else
87 constexpr float RESPONSIVE_SPRING_AMPLITUDE_RATIO = 0.001f;
88 #endif
89 } // namespace
90
GetVelocityScale()91 double Scrollable::GetVelocityScale()
92 {
93 return Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_ELEVEN) ? NEW_VELOCITY_SCALE
94 : VELOCITY_SCALE;
95 }
96
~Scrollable()97 Scrollable::~Scrollable()
98 {
99 // If animation still runs, force stop it.
100 if (!IsStopped()) {
101 PerfMonitor::GetPerfMonitor()->EndCommercial(PerfConstants::APP_LIST_FLING, false);
102 AceAsyncTraceEndCommercial(0, (TRAILING_ANIMATION + std::to_string(nodeId_) + std::string(" ") +
103 nodeTag_).c_str());
104 if (!context_.Invalid()) {
105 auto context = context_.Upgrade();
106 context->SetUiDvsyncSwitch(false);
107 }
108 }
109 StopFrictionAnimation();
110 StopSpringAnimation();
111 StopSnapAnimation();
112 }
113
Initialize(const RefPtr<FrameNode> & host)114 void Scrollable::Initialize(const RefPtr<FrameNode>& host)
115 {
116 CHECK_NULL_VOID(host);
117 weakHost_ = host;
118 auto pipeline = host->GetContextRefPtr();
119 context_ = pipeline;
120 InitPanRecognizerNG();
121 available_ = true;
122 CHECK_NULL_VOID(pipeline);
123 auto scrollableTheme = pipeline->GetTheme<ScrollableTheme>();
124 CHECK_NULL_VOID(scrollableTheme);
125 flingVelocityScale_ = pipeline->GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_ELEVEN)
126 ? scrollableTheme->GetFlingVelocityScale()
127 : VELOCITY_SCALE;
128 springVelocityScale_ = pipeline->GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_ELEVEN)
129 ? scrollableTheme->GetSpringVelocityScale()
130 : VELOCITY_SCALE;
131 ratio_ = scrollableTheme->GetRatio();
132 springResponse_ = scrollableTheme->GetSpringResponse();
133 touchPadVelocityScaleRate_ = scrollableTheme->GetTouchPadVelocityScaleRate();
134 if (friction_ == -1) {
135 if (pipeline->GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_THIRTEEN)) {
136 defaultFriction_ = scrollableTheme->GetFriction();
137 } else if (pipeline->GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
138 defaultFriction_ = API12_FRICTION;
139 } else if (pipeline->GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_ELEVEN)) {
140 defaultFriction_ = API11_FRICTION;
141 } else {
142 defaultFriction_ = FRICTION;
143 }
144 }
145 }
146
InitAxisAnimator()147 void Scrollable::InitAxisAnimator()
148 {
149 CHECK_NULL_VOID(!axisAnimator_);
150 auto axisAnimationCallback = [weak = WeakClaim(this)](float offset) {
151 auto scrollable = weak.Upgrade();
152 CHECK_NULL_VOID(scrollable);
153 scrollable->ReportToDragFRCScene(scrollable->currentVelocity_, NG::SceneStatus::RUNNING);
154 scrollable->ProcessScrollMotion(offset, SCROLL_FROM_AXIS);
155 };
156 auto axisAnimationStartCallback = [weak = WeakClaim(this)](float position) {
157 auto scrollable = weak.Upgrade();
158 CHECK_NULL_VOID(scrollable && scrollable->onScrollStartRec_);
159 scrollable->ReportToDragFRCScene(scrollable->currentVelocity_, NG::SceneStatus::START);
160 scrollable->onScrollStartRec_(position);
161 };
162 auto axisAnimationFinishCallback = [weak = WeakClaim(this)]() {
163 auto scrollable = weak.Upgrade();
164 CHECK_NULL_VOID(scrollable);
165 scrollable->ReportToDragFRCScene(scrollable->currentVelocity_, NG::SceneStatus::END);
166 scrollable->ProcessScrollMotionStop();
167 };
168 axisAnimator_ = AceType::MakeRefPtr<AxisAnimator>(std::move(axisAnimationCallback),
169 std::move(axisAnimationStartCallback), std::move(axisAnimationFinishCallback));
170 axisAnimator_->Initialize(context_);
171 }
172
InitPanRecognizerNG()173 void Scrollable::InitPanRecognizerNG()
174 {
175 PanDirection panDirection;
176 panDirection.type = axis_ == Axis::VERTICAL ? PanDirection::VERTICAL : PanDirection::HORIZONTAL;
177 double distance = SystemProperties::GetScrollableDistance();
178 PanDistanceMap distanceMap = { { SourceTool::UNKNOWN, distance } };
179 if (LessOrEqual(distance, 0.0)) {
180 distanceMap = { { SourceTool::UNKNOWN, DEFAULT_PAN_DISTANCE.ConvertToPx() },
181 { SourceTool::PEN, DEFAULT_PEN_PAN_DISTANCE.ConvertToPx() } };
182 }
183 panRecognizerNG_ =
184 AceType::MakeRefPtr<NG::PanRecognizer>(DEFAULT_PAN_FINGER, panDirection, distanceMap);
185 panRecognizerNG_->SetIsAllowMouse(false);
186 SetOnActionStart();
187 SetOnActionUpdate();
188 SetOnActionExtUpdate();
189 SetOnActionEnd();
190 SetOnActionCancel();
191 SetPanEndCallback();
192 }
193
SetOnActionStart()194 void Scrollable::SetOnActionStart()
195 {
196 CHECK_NULL_VOID(panRecognizerNG_);
197 auto actionStart = [weakScroll = AceType::WeakClaim(this)](const GestureEvent& info) {
198 auto scroll = weakScroll.Upgrade();
199 CHECK_NULL_VOID(scroll);
200 scroll->HandleDragStart(info);
201 };
202 panRecognizerNG_->SetOnActionStart(actionStart);
203 }
204
SetOnActionExtUpdate()205 void Scrollable::SetOnActionExtUpdate()
206 {
207 CHECK_NULL_VOID(panRecognizerNG_);
208 auto actionExtUpdate = [weakScroll = AceType::WeakClaim(this)](const GestureEvent& info) {
209 auto scroll = weakScroll.Upgrade();
210 CHECK_NULL_VOID(scroll);
211 scroll->HandleExtDragUpdate();
212 };
213 panRecognizerNG_->SetOnActionExtUpdate(actionExtUpdate);
214 }
215
SetOnActionUpdate()216 void Scrollable::SetOnActionUpdate()
217 {
218 CHECK_NULL_VOID(panRecognizerNG_);
219 auto actionUpdate = [weakScroll = AceType::WeakClaim(this)](const GestureEvent& info) {
220 auto scroll = weakScroll.Upgrade();
221 CHECK_NULL_VOID(scroll);
222 scroll->HandleDragUpdate(info);
223 };
224 panRecognizerNG_->SetOnActionUpdate(actionUpdate);
225 }
226
SetOnActionEnd()227 void Scrollable::SetOnActionEnd()
228 {
229 CHECK_NULL_VOID(panRecognizerNG_);
230 auto actionEnd = [weakScroll = AceType::WeakClaim(this)](GestureEvent& info) {
231 auto scroll = weakScroll.Upgrade();
232 CHECK_NULL_VOID(scroll);
233 scroll->HandleDragEnd(info);
234 scroll->ProcessPanActionEndEvents(info);
235 scroll->isDragging_ = false;
236 };
237 panRecognizerNG_->SetOnActionEnd(actionEnd);
238 }
239
SetPanEndCallback()240 void Scrollable::SetPanEndCallback()
241 {
242 CHECK_NULL_VOID(panRecognizerNG_);
243 auto panEndCallback = [weakScroll = AceType::WeakClaim(this)](GestureEvent& info) {
244 auto scroll = weakScroll.Upgrade();
245 if (scroll) {
246 auto tempInfo = info;
247 tempInfo.SetMainDelta(0.0);
248 tempInfo.SetMainVelocity(0.0);
249 ACE_SCOPED_TRACE("Trigger PanEndCallback, id:%d, tag:%s", scroll->nodeId_, scroll->nodeTag_.c_str());
250 scroll->HandleDragEnd(tempInfo, true);
251 scroll->ProcessPanActionEndEvents(tempInfo);
252 scroll->isDragging_ = false;
253 }
254 };
255 panRecognizerNG_->SetPanEndCallback(panEndCallback);
256 }
257
ProcessPanActionEndEvents(const GestureEvent & info)258 void Scrollable::ProcessPanActionEndEvents(const GestureEvent& info)
259 {
260 CHECK_NULL_VOID(!panActionEndEvents_.empty());
261 std::for_each(panActionEndEvents_.begin(), panActionEndEvents_.end(), [info](GestureEventFunc& event) {
262 auto gestureInfo = info;
263 event(gestureInfo);
264 });
265 }
266
SetOnActionCancel()267 void Scrollable::SetOnActionCancel()
268 {
269 CHECK_NULL_VOID(panRecognizerNG_);
270 auto actionCancel = [weakScroll = AceType::WeakClaim(this)](const GestureEvent& info) {
271 auto scroll = weakScroll.Upgrade();
272 CHECK_NULL_VOID(scroll);
273 if (scroll->dragCancelCallback_) {
274 scroll->dragCancelCallback_();
275 }
276 GestureEvent nullInfo;
277 scroll->HandleDragEnd(nullInfo);
278 if (scroll->panActionEndEvents_.empty()) {
279 scroll->isDragging_ = false;
280 return;
281 }
282 std::for_each(scroll->panActionEndEvents_.begin(), scroll->panActionEndEvents_.end(),
283 [nullInfo](GestureEventFunc& event) {
284 auto gestureInfo = nullInfo;
285 event(gestureInfo);
286 });
287 scroll->isDragging_ = false;
288 };
289 panRecognizerNG_->SetOnActionCancel(actionCancel);
290 }
291
292 #ifdef SUPPORT_DIGITAL_CROWN
ListenDigitalCrownEvent(const RefPtr<FrameNode> & frameNode)293 void Scrollable::ListenDigitalCrownEvent(const RefPtr<FrameNode>& frameNode)
294 {
295 CHECK_NULL_VOID(frameNode);
296 auto focusHub = frameNode->GetFocusHub();
297 CHECK_NULL_VOID(focusHub);
298
299 auto onCrownEvent = [weakScroll = AceType::WeakClaim(this), weakNode = AceType::WeakClaim(AceType::RawPtr(
300 frameNode))](const CrownEvent& event) -> bool {
301 auto scroll = weakScroll.Upgrade();
302 CHECK_NULL_RETURN(scroll, false);
303 auto node = weakNode.Upgrade();
304 CHECK_NULL_RETURN(node, false);
305
306 auto centerOffset = node->GetGeometryNode()->GetContentRect().Center();
307 scroll->HandleCrownEvent(event, centerOffset);
308 return true;
309 };
310 focusHub->SetOnCrownEventInternal(std::move(onCrownEvent));
311
312 focusHub->SetOnBlurReasonInternal([weak = WeakClaim(this)](const BlurReason& blurReason) {
313 auto scroll = weak.Upgrade();
314 CHECK_NULL_VOID(scroll);
315 GestureEvent info;
316 info.SetSourceDevice(SourceType::CROWN);
317 info.SetSourceTool(SourceTool::UNKNOWN);
318 scroll->HandleCrownActionCancel(info);
319 });
320 }
321
GetCrownRotatePx(const CrownEvent & event) const322 double Scrollable::GetCrownRotatePx(const CrownEvent& event) const
323 {
324 double velocity = std::abs(event.angularVelocity * ANGULAR_VELOCITY_FACTOR);
325 double px = 0.0;
326 if (LessOrEqualCustomPrecision(velocity, ANGULAR_VELOCITY_SLOW, 0.01f)) { // very slow
327 px = (Dimension(DISPLAY_CONTROL_RATIO_VERY_SLOW, DimensionUnit::VP) * event.degree).ConvertToPx();
328 } else if (LessOrEqualCustomPrecision(velocity, ANGULAR_VELOCITY_MEDIUM, 0.01f)) { // slow
329 px = (Dimension(DISPLAY_CONTROL_RATIO_SLOW, DimensionUnit::VP) * event.degree).ConvertToPx();
330 } else if (LessOrEqualCustomPrecision(velocity, ANGULAR_VELOCITY_FAST, 0.01f)) { // medium
331 px = (Dimension(DISPLAY_CONTROL_RATIO_MEDIUM, DimensionUnit::VP) * event.degree).ConvertToPx();
332 } else { // fast
333 px = (Dimension(DISPLAY_CONTROL_RATIO_FAST, DimensionUnit::VP) * event.degree).ConvertToPx();
334 }
335 switch (crownSensitivity_) {
336 case CrownSensitivity::LOW:
337 px *= CROWN_SENSITIVITY_LOW;
338 break;
339 case CrownSensitivity::MEDIUM:
340 px *= CROWN_SENSITIVITY_MEDIUM;
341 break;
342 case CrownSensitivity::HIGH:
343 px *= CROWN_SENSITIVITY_HIGH;
344 break;
345 default:
346 break;
347 }
348 return px;
349 }
350
UpdateCrownVelocity(const TimeStamp & timeStamp,double mainDelta,bool end)351 void Scrollable::UpdateCrownVelocity(const TimeStamp& timeStamp, double mainDelta, bool end)
352 {
353 if (axis_ == Axis::VERTICAL) {
354 accumulativeCrownPx_ += Offset(0, mainDelta);
355 } else {
356 accumulativeCrownPx_ += Offset(mainDelta, 0);
357 }
358 crownVelocityTracker_.UpdateTrackerPoint(accumulativeCrownPx_.GetX(), accumulativeCrownPx_.GetY(), timeStamp, end);
359 }
360
HandleCrownEvent(const CrownEvent & event,const OffsetF & center)361 void Scrollable::HandleCrownEvent(const CrownEvent& event, const OffsetF& center)
362 {
363 DimensionOffset centerDimension(center);
364 Offset globalLocation(centerDimension.GetX().ConvertToPx(), centerDimension.GetY().ConvertToPx());
365
366 GestureEvent info;
367 info.SetSourceDevice(SourceType::CROWN);
368 info.SetSourceTool(SourceTool::UNKNOWN);
369 info.SetGlobalLocation(globalLocation);
370 double mainDelta = GetCrownRotatePx(event);
371
372 switch (event.action) {
373 case CrownAction::BEGIN:
374 crownEventNum_ = 0;
375 TAG_LOGI(AceLogTag::ACE_SCROLLABLE, "-->BEGIN]");
376 HandleCrownActionBegin(event.timeStamp, mainDelta, info);
377 break;
378 case CrownAction::UPDATE:
379 TAG_LOGI(AceLogTag::ACE_SCROLLABLE, "-->UPDATE]");
380 HandleCrownActionUpdate(event.timeStamp, mainDelta, info);
381 break;
382 case CrownAction::END:
383 TAG_LOGI(AceLogTag::ACE_SCROLLABLE, "-->END]");
384 HandleCrownActionEnd(event.timeStamp, mainDelta, info);
385 break;
386 default:
387 HandleCrownActionCancel(info);
388 break;
389 }
390 }
391
HandleCrownActionBegin(const TimeStamp & timeStamp,double mainDelta,GestureEvent & info)392 void Scrollable::HandleCrownActionBegin(const TimeStamp& timeStamp, double mainDelta, GestureEvent& info)
393 {
394 if (isDragging_) {
395 return;
396 }
397 accumulativeCrownPx_.Reset();
398 crownVelocityTracker_.Reset();
399 UpdateCrownVelocity(timeStamp, mainDelta, false);
400 info.SetMainDelta(mainDelta);
401 info.SetMainVelocity(crownVelocityTracker_.GetMainAxisVelocity());
402 isDragging_ = true;
403 isCrownEventDragging_ = true;
404 isCrownDragging_ = true;
405 HandleDragStart(info);
406 }
407
HandleCrownActionUpdate(const TimeStamp & timeStamp,double mainDelta,GestureEvent & info)408 void Scrollable::HandleCrownActionUpdate(const TimeStamp& timeStamp, double mainDelta, GestureEvent& info)
409 {
410 if (!isCrownEventDragging_) {
411 return;
412 }
413 UpdateCrownVelocity(timeStamp, mainDelta, false);
414 info.SetMainDelta(mainDelta);
415 info.SetMainVelocity(crownVelocityTracker_.GetMainAxisVelocity());
416 HandleDragUpdate(info);
417 }
418
HandleCrownActionEnd(const TimeStamp & timeStamp,double mainDelta,GestureEvent & info)419 void Scrollable::HandleCrownActionEnd(const TimeStamp& timeStamp, double mainDelta, GestureEvent& info)
420 {
421 if (!isCrownEventDragging_) {
422 return;
423 }
424 if (NearZero(mainDelta)) {
425 info.SetMainDelta(crownVelocityTracker_.GetMainAxisDeltaPos());
426 info.SetMainVelocity(crownVelocityTracker_.GetMainAxisVelocity());
427 } else {
428 UpdateCrownVelocity(timeStamp, mainDelta, true);
429 info.SetMainDelta(mainDelta);
430 info.SetMainVelocity(crownVelocityTracker_.GetMainAxisVelocity());
431 }
432 HandleDragEnd(info);
433 std::for_each(panActionEndEvents_.begin(), panActionEndEvents_.end(),
434 [info](GestureEventFunc& event) {
435 auto gestureInfo = info;
436 event(gestureInfo);
437 });
438 isDragging_ = false;
439 isCrownDragging_ = false;
440 auto context = context_.Upgrade();
441 CHECK_NULL_VOID(context);
442 auto taskExecutor = SingleTaskExecutor::Make(context->GetTaskExecutor(), TaskExecutor::TaskType::UI);
443 crownTask_.Reset([weak = WeakClaim(this)] {
444 auto scrollable = weak.Upgrade();
445 if (scrollable) {
446 scrollable->SetCrownEventDragging(false);
447 }
448 });
449 taskExecutor.PostDelayedTask(crownTask_, CUSTOM_SPRING_ANIMATION_DURATION, "ArkUIUpdateCrownEventDrag");
450 }
451
HandleCrownActionCancel(GestureEvent & info)452 void Scrollable::HandleCrownActionCancel(GestureEvent& info)
453 {
454 if (!isDragging_ || !isCrownEventDragging_) {
455 return;
456 }
457
458 if (dragCancelCallback_) {
459 dragCancelCallback_();
460 }
461 info.SetMainDelta(0);
462 info.SetMainVelocity(0);
463 HandleDragEnd(info);
464 std::for_each(panActionEndEvents_.begin(), panActionEndEvents_.end(),
465 [info](GestureEventFunc& event) {
466 auto gestureInfo = info;
467 event(gestureInfo);
468 });
469 isDragging_ = false;
470 isCrownDragging_ = false;
471 }
472 #endif
473
SetAxis(Axis axis)474 void Scrollable::SetAxis(Axis axis)
475 {
476 axis_ = axis;
477 PanDirection panDirection;
478 if (axis_ == Axis::NONE) {
479 panDirection.type = PanDirection::NONE;
480 } else if (axis_ == Axis::VERTICAL) {
481 panDirection.type = PanDirection::VERTICAL;
482 } else {
483 panDirection.type = PanDirection::HORIZONTAL;
484 }
485 if (panRecognizerNG_) {
486 panRecognizerNG_->SetDirection(panDirection);
487 }
488 #ifdef SUPPORT_DIGITAL_CROWN
489 crownVelocityTracker_.SetMainAxis(axis_);
490 #endif
491 }
492
HandleTouchDown(bool fromcrown)493 void Scrollable::HandleTouchDown(bool fromcrown)
494 {
495 if (!fromcrown) {
496 isTouching_ = true;
497 }
498 // If animation still runs, first stop it.
499 ACE_SCOPED_TRACE("HandleTouchDown, panDirection:%u, id:%d, tag:%s", GetPanDirection(), nodeId_, nodeTag_.c_str());
500 StopSpringAnimation();
501 if (state_ == AnimationState::FRICTION) {
502 StopFrictionAnimation();
503 } else if (state_ == AnimationState::SNAP) {
504 StopSnapAnimation();
505 }
506 }
507
HandleTouchUp()508 void Scrollable::HandleTouchUp()
509 {
510 // Two fingers are alternately drag, one finger is released without triggering spring animation.
511 ACE_SCOPED_TRACE("HandleTouchUp, isDragging_:%u, nestedScrolling_:%u id:%d, tag:%s", isDragging_, nestedScrolling_,
512 nodeId_, nodeTag_.c_str());
513 if (isDragging_) {
514 return;
515 }
516 isTouching_ = false;
517 if (nestedScrolling_) {
518 return;
519 }
520 if (CanStayOverScroll()) {
521 return;
522 }
523 // outBoundaryCallback_ is only set in ScrollablePattern::SetEdgeEffect and when the edge effect is spring
524 if (outBoundaryCallback_ && outBoundaryCallback_()) {
525 if (state_ != AnimationState::SPRING && scrollOverCallback_) {
526 if (onScrollStartRec_) {
527 onScrollStartRec_(static_cast<float>(axis_));
528 }
529 ProcessScrollOverCallback(0.0);
530 }
531 return;
532 }
533 if (state_ != AnimationState::SNAP && startSnapAnimationCallback_) {
534 SnapAnimationOptions snapAnimationOptions;
535 startSnapAnimationCallback_(snapAnimationOptions);
536 }
537 }
538
HandleTouchCancel()539 void Scrollable::HandleTouchCancel()
540 {
541 if (isDragging_) {
542 return;
543 }
544 isTouching_ = false;
545 ACE_SCOPED_TRACE("HandleTouchCancel, id:%d, tag:%s", nodeId_, nodeTag_.c_str());
546 if (state_ != AnimationState::SPRING && scrollOverCallback_) {
547 ProcessScrollOverCallback(0.0);
548 }
549 }
550
IsAnimationNotRunning() const551 bool Scrollable::IsAnimationNotRunning() const
552 {
553 return !isTouching_ && state_ == AnimationState::IDLE;
554 }
555
Idle() const556 bool Scrollable::Idle() const
557 {
558 return !isTouching_ && state_ == AnimationState::IDLE && !nestedScrolling_ && !isCrownDragging_;
559 }
560
IsStopped() const561 bool Scrollable::IsStopped() const
562 {
563 return state_ == AnimationState::IDLE;
564 }
565
IsSpringStopped() const566 bool Scrollable::IsSpringStopped() const
567 {
568 return state_ != AnimationState::SPRING;
569 }
570
IsSnapStopped() const571 bool Scrollable::IsSnapStopped() const
572 {
573 return state_ != AnimationState::SNAP;
574 }
575
StopScrollable()576 void Scrollable::StopScrollable()
577 {
578 if (state_ == AnimationState::FRICTION) {
579 StopFrictionAnimation();
580 }
581 if (state_ == AnimationState::SPRING) {
582 StopSpringAnimation();
583 }
584 if (state_ == AnimationState::SNAP) {
585 StopSnapAnimation();
586 }
587 }
588
HandleScrollEnd(const std::optional<float> & velocity)589 void Scrollable::HandleScrollEnd(const std::optional<float>& velocity)
590 {
591 // priority:
592 // 1. onScrollEndRec_ (would internally call onScrollEnd)
593 // 2. scrollEndCallback_
594 if (onScrollEndRec_) {
595 onScrollEndRec_(velocity);
596 return;
597 }
598 if (scrollEndCallback_) {
599 scrollEndCallback_();
600 }
601 }
602
HandleExtDragUpdate()603 void Scrollable::HandleExtDragUpdate()
604 {
605 HandleExtScroll();
606 }
607
HandleDragStart(const OHOS::Ace::GestureEvent & info)608 void Scrollable::HandleDragStart(const OHOS::Ace::GestureEvent& info)
609 {
610 if (info.GetSourceTool() == SourceTool::TOUCHPAD) {
611 HandleTouchDown();
612 }
613 #ifdef SUPPORT_DIGITAL_CROWN
614 crownTask_.Cancel();
615 if (info.GetSourceDevice() != SourceType::CROWN) {
616 isCrownEventDragging_ = false;
617 }
618 if (isCrownEventDragging_) {
619 HandleTouchDown(true);
620 }
621 #endif
622 currentVelocity_ = info.GetMainVelocity();
623 auto isAxisEvent = IsMouseWheelScroll(info);
624 if (!isAxisEvent) {
625 ReportToDragFRCScene(currentVelocity_, NG::SceneStatus::START);
626 }
627 if (continuousDragStatus_) {
628 IncreaseContinueDragCount();
629 task_.Cancel();
630 }
631 SetDragStartPosition(GetMainOffset(Offset(info.GetGlobalPoint().GetX(), info.GetGlobalPoint().GetY())));
632 const double dragPositionInMainAxis =
633 axis_ == Axis::VERTICAL ? info.GetGlobalLocation().GetY() : info.GetGlobalLocation().GetX();
634 TAG_LOGI(AceLogTag::ACE_SCROLLABLE, "Scroll drag start, id:%{public}d, tag:%{public}s", nodeId_, nodeTag_.c_str());
635 skipRestartSpring_ = false; // reset flags. Extract method if more flags need to be reset
636 #ifdef OHOS_PLATFORM
637 // Increase the cpu frequency when sliding start.
638 auto currentTime = GetSysTimestamp();
639 auto increaseCpuTime = currentTime - startIncreaseTime_;
640 if (!moved_ || increaseCpuTime >= INCREASE_CPU_TIME_ONCE) {
641 startIncreaseTime_ = currentTime;
642 if (FrameReport::GetInstance().GetEnable()) {
643 FrameReport::GetInstance().BeginListFling();
644 }
645 }
646 #endif
647 JankFrameReport::GetInstance().SetFrameJankFlag(JANK_RUNNING_SCROLL);
648 ACE_SCOPED_TRACE("HandleDragStart, inputEventType:%d, sourceTool:%d, IsMouseWheelScroll:%u, "
649 "IsAxisAnimationRunning:%u, IsSnapAnimationRunning:%u, id:%d, tag:%s",
650 info.GetInputEventType(), info.GetSourceTool(), isAxisEvent, IsAxisAnimationRunning(), IsSnapAnimationRunning(),
651 nodeId_, nodeTag_.c_str());
652 if (isAxisEvent && !CanStayOverScroll()) {
653 if (!IsAxisAnimationRunning() && !IsSnapAnimationRunning()) {
654 axisSnapDistance_ = currentPos_;
655 snapDirection_ = SnapDirection::NONE;
656 }
657 return;
658 } else if (IsAxisAnimationRunning()) {
659 StopAxisAnimation();
660 }
661 isDragging_ = true;
662 if (onScrollStartRec_) {
663 onScrollStartRec_(static_cast<float>(dragPositionInMainAxis));
664 }
665 }
666
HandleExtScroll()667 void Scrollable::HandleExtScroll()
668 {
669 if (handleExtScrollCallback_ != nullptr) {
670 // call NestableScrollContainer::HandleExtScroll
671 handleExtScrollCallback_();
672 }
673 }
674
HandleScroll(double offset,int32_t source,NestedState state)675 ScrollResult Scrollable::HandleScroll(double offset, int32_t source, NestedState state)
676 {
677 if (!handleScrollCallback_) {
678 ExecuteScrollBegin(offset);
679 canOverScroll_ = false;
680 moved_ = UpdateScrollPosition(offset, source);
681 return { 0, false };
682 }
683 // call NestableScrollContainer::HandleScroll
684 return handleScrollCallback_(static_cast<float>(offset), source, state);
685 }
686
HandleDragUpdate(const GestureEvent & info)687 void Scrollable::HandleDragUpdate(const GestureEvent& info)
688 {
689 currentVelocity_ = info.GetMainVelocity();
690 ReportToDragFRCScene(currentVelocity_, NG::SceneStatus::RUNNING);
691 if (!NearZero(info.GetMainVelocity()) && dragCount_ >= FIRST_THRESHOLD) {
692 if (Negative(lastGestureVelocity_ / info.GetMainVelocity())) {
693 ResetContinueDragCount();
694 }
695 }
696 if (state_ != AnimationState::IDLE) {
697 // If animation still runs, first stop it.
698 isDragUpdateStop_ = true;
699 StopFrictionAnimation();
700 StopSpringAnimation();
701 if (!(IsMouseWheelScroll(info) && GetSnapType() != SnapType::NONE_SNAP)) {
702 StopSnapAnimation();
703 currentPos_ = 0.0;
704 }
705 }
706 #ifdef OHOS_PLATFORM
707 // Handle the case where you keep sliding past limit time(4s).
708 auto currentTime = GetSysTimestamp();
709 auto increaseCpuTime = currentTime - startIncreaseTime_;
710 if (increaseCpuTime >= INCREASE_CPU_TIME_ONCE) {
711 startIncreaseTime_ = currentTime;
712 if (FrameReport::GetInstance().GetEnable()) {
713 FrameReport::GetInstance().BeginListFling();
714 }
715 }
716 #endif
717 auto mainDelta = info.GetMainDelta();
718 lastMainDelta_ = mainDelta;
719 auto isReverse = isReverseCallback_ && isReverseCallback_();
720 mainDelta = isReverse ? Round(-mainDelta) : Round(mainDelta);
721 JankFrameReport::GetInstance().RecordFrameUpdate();
722 auto source = SCROLL_FROM_UPDATE;
723 auto isAxisEvent = IsMouseWheelScroll(info);
724 if (isAxisEvent) {
725 source = SCROLL_FROM_AXIS;
726 }
727 #ifdef SUPPORT_DIGITAL_CROWN
728 if (isCrownEventDragging_) {
729 source = SCROLL_FROM_CROWN;
730 }
731 #endif
732 ACE_SCOPED_TRACE(
733 "HandleDragUpdate, mainDelta:%f, source:%d, id:%d, tag:%s", mainDelta, source, nodeId_, nodeTag_.c_str());
734 if (isAxisEvent) {
735 CHECK_EQUAL_VOID(CanStayOverScroll(), true);
736 ProcessAxisUpdateEvent(mainDelta);
737 return;
738 }
739 HandleScroll(mainDelta, source, NestedState::GESTURE);
740 }
741
ProcessAxisUpdateEvent(float mainDelta,bool fromScrollBar)742 void Scrollable::ProcessAxisUpdateEvent(float mainDelta, bool fromScrollBar)
743 {
744 auto context = context_.Upgrade();
745 CHECK_NULL_VOID(context);
746 auto currentVsyncTime = context->GetVsyncTime();
747 auto snapType = GetSnapType();
748 if (snapType != SnapType::NONE_SNAP && startSnapAnimationCallback_) {
749 CHECK_NULL_VOID(lastAxisVsyncTime_ != currentVsyncTime);
750 lastAxisVsyncTime_ = currentVsyncTime;
751 auto snapDelta = 0.f;
752 auto snapDirection = SnapDirection::NONE;
753 auto isInitScroll = (snapType == SnapType::LIST_SNAP && snapDirection_ == SnapDirection::NONE) ||
754 (snapType == SnapType::SCROLL_SNAP && state_ == AnimationState::IDLE);
755 if (isInitScroll) {
756 snapDirection = Positive(mainDelta) ? SnapDirection::FORWARD : SnapDirection::BACKWARD;
757 } else {
758 if (snapDirection_ == SnapDirection::BACKWARD && !Positive(mainDelta)) {
759 snapDelta = endPos_ - currentPos_;
760 snapDirection = SnapDirection::BACKWARD;
761 } else if (snapDirection_ == SnapDirection::FORWARD && Positive(mainDelta)) {
762 snapDelta = endPos_ - currentPos_;
763 snapDirection = SnapDirection::FORWARD;
764 } else if ((snapDirection_ == SnapDirection::BACKWARD && Positive(mainDelta)) ||
765 (snapDirection_ == SnapDirection::FORWARD && !Positive(mainDelta))) {
766 snapDelta = 0.f;
767 snapDirection = SnapDirection::NONE;
768 }
769 }
770 ACE_SCOPED_TRACE("ProcessAxisUpdateEvent start SnapAnimation, snapDelta:%f, snapDirection:%d, "
771 "lastSnapDirection:%d, id:%d, tag:%s",
772 snapDelta, snapDirection, snapDirection_, nodeId_, nodeTag_.c_str());
773 SnapAnimationOptions snapAnimationOptions = { .snapDelta = snapDelta, .animationVelocity = currentVelocity_,
774 .snapDirection = snapDirection, .fromScrollBar = fromScrollBar };
775 startSnapAnimationCallback_(snapAnimationOptions);
776 auto isNeedAdjustDirection = (snapType == SnapType::SCROLL_SNAP && snapDirection == SnapDirection::NONE);
777 if (isNeedAdjustDirection) {
778 snapDirection_ = Positive(mainDelta) ? SnapDirection::FORWARD : SnapDirection::BACKWARD;
779 } else {
780 snapDirection_ = snapDirection;
781 }
782 return;
783 }
784 lastAxisVsyncTime_ = currentVsyncTime;
785 if (!axisAnimator_) {
786 InitAxisAnimator();
787 }
788 ACE_SCOPED_TRACE(
789 "ProcessAxisUpdateEvent onAxis, IsAxisAnimationRunning:%u, mainDelta:%f, currentPos_:%f, id:%d, tag:%s",
790 IsAxisAnimationRunning(), mainDelta, currentPos_, nodeId_, nodeTag_.c_str());
791 axisAnimator_->OnAxis(mainDelta, currentPos_);
792 }
793
LayoutDirectionEst(double gestureVelocity,double velocityScale,bool isScrollFromTouchPad)794 void Scrollable::LayoutDirectionEst(double gestureVelocity, double velocityScale, bool isScrollFromTouchPad)
795 {
796 auto defaultVelocityScale = isSlow_ ? SLOW_VELOCITY_SCALE : velocityScale;
797 double ret = SystemProperties::GetSrollableVelocityScale();
798 velocityScale = !NearZero(ret) ? ret : defaultVelocityScale;
799 velocityScale = isScrollFromTouchPad ? velocityScale * touchPadVelocityScaleRate_ : velocityScale;
800 double gain = GetGain(GetDragOffset());
801 if (isReverseCallback_ && isReverseCallback_()) {
802 currentVelocity_ = -gestureVelocity * velocityScale * gain;
803 } else {
804 currentVelocity_ = gestureVelocity * velocityScale * gain;
805 }
806 // Apply max fling velocity limit, it must be calculated after all fling velocity gain.
807 currentVelocity_ = std::clamp(currentVelocity_, -maxFlingVelocity_ + slipFactor_, maxFlingVelocity_ - slipFactor_);
808 slidInfo_.gestureVelocity = gestureVelocity;
809 slidInfo_.velocityScale = velocityScale;
810 slidInfo_.gain = gain;
811 slidInfo_.maxFlingVelocity = maxFlingVelocity_;
812 slidInfo_.slipFactor = slipFactor_;
813 }
814
HandleDragEnd(const GestureEvent & info,bool isFromPanEnd)815 void Scrollable::HandleDragEnd(const GestureEvent& info, bool isFromPanEnd)
816 {
817 TAG_LOGI(AceLogTag::ACE_SCROLLABLE, "Scroll drag end, velocity is %{public}f id:%{public}d, tag:%{public}s, "
818 "dragCnt:%{public}d", info.GetMainVelocity(), nodeId_, nodeTag_.c_str(), dragCount_);
819 auto isAxisEvent = IsMouseWheelScroll(info);
820 if (isAxisEvent) {
821 ProcessAxisEndEvent();
822 return;
823 }
824 if (CanStayOverScroll()) {
825 SetCanStayOverScroll(false);
826 }
827 // avoid no render frame when drag end
828 if (!isFromPanEnd) {
829 if (NearZero(info.GetMainDelta())) {
830 auto tempInfo = info;
831 tempInfo.SetMainDelta(lastMainDelta_);
832 HandleDragUpdate(tempInfo);
833 } else {
834 HandleDragUpdate(info);
835 }
836 }
837 if (onWillStopDraggingCallback_) {
838 onWillStopDraggingCallback_(info.GetMainVelocity());
839 }
840 ReportToDragFRCScene(info.GetMainVelocity(), NG::SceneStatus::END);
841 bool isScrollFromTouchPad = info.GetSourceTool() == SourceTool::TOUCHPAD;
842 isDragUpdateStop_ = false;
843 scrollPause_ = false;
844 lastGestureVelocity_ = GetPanDirection() == Axis::NONE ? 0.0 : info.GetMainVelocity();
845 isSlow_ = LessNotEqual(std::abs(lastGestureVelocity_), SLOW_FRICTION_THRESHOLD);
846 SetDragEndPosition(GetMainOffset(Offset(info.GetGlobalPoint().GetX(), info.GetGlobalPoint().GetY())));
847 lastPos_ = GetDragOffset();
848 JankFrameReport::GetInstance().ClearFrameJankFlag(JANK_RUNNING_SCROLL);
849 double mainPosition = Round(GetMainOffset(Offset(info.GetGlobalPoint().GetX(), info.GetGlobalPoint().GetY())));
850 if (!moved_ || isAxisEvent) {
851 LayoutDirectionEst(lastGestureVelocity_, flingVelocityScale_, isScrollFromTouchPad);
852 ResetContinueDragCount();
853 if (GetSnapType() == SnapType::SCROLL_SNAP) {
854 currentPos_ = mainPosition;
855 SnapAnimationOptions snapAnimationOptions = { .animationVelocity = currentVelocity_ };
856 if (startSnapAnimationCallback_ && startSnapAnimationCallback_(snapAnimationOptions)) {
857 isTouching_ = false;
858 return;
859 }
860 }
861 HandleScrollEnd(currentVelocity_);
862 currentVelocity_ = 0.f;
863 #ifdef OHOS_PLATFORM
864 if (FrameReport::GetInstance().GetEnable()) {
865 FrameReport::GetInstance().EndListFling();
866 }
867 #endif
868 } else if (canOverScroll_) {
869 LayoutDirectionEst(lastGestureVelocity_, springVelocityScale_, isScrollFromTouchPad);
870 CalcOverScrollVelocity();
871 ResetContinueDragCount();
872 HandleOverScroll(currentVelocity_);
873 } else {
874 LayoutDirectionEst(lastGestureVelocity_, flingVelocityScale_, isScrollFromTouchPad);
875 StartScrollAnimation(mainPosition, currentVelocity_, isScrollFromTouchPad);
876 }
877 ACE_SCOPED_TRACE(
878 "HandleDragEnd, mainPosition:%f, getureDelta:%lf, gestureVelocity:%f, currentVelocity:%f, moved_:%u "
879 "canOverScroll_:%u, id:%d, tag:%s",
880 mainPosition, info.GetMainDelta(), lastGestureVelocity_, currentVelocity_, moved_, canOverScroll_, nodeId_,
881 nodeTag_.c_str());
882 SetDelayedTask();
883 if (dragEndCallback_) {
884 dragEndCallback_();
885 }
886 if (info.GetSourceDevice() != SourceType::CROWN) {
887 isTouching_ = false;
888 }
889 SetDragStartPosition(0.0);
890 SetDragEndPosition(0.0);
891 }
892
ProcessAxisEndEvent()893 void Scrollable::ProcessAxisEndEvent()
894 {
895 scrollPause_ = false;
896 isTouching_ = false;
897 isDragUpdateStop_ = false;
898 JankFrameReport::GetInstance().ClearFrameJankFlag(JANK_RUNNING_SCROLL);
899 if (CanStayOverScroll()) {
900 HandleOverScroll(0);
901 SetCanStayOverScroll(false);
902 }
903 }
904
ReportToDragFRCScene(double velocity,NG::SceneStatus sceneStatus)905 void Scrollable::ReportToDragFRCScene(double velocity, NG::SceneStatus sceneStatus)
906 {
907 CHECK_NULL_VOID(dragFRCSceneCallback_);
908 dragFRCSceneCallback_(velocity, sceneStatus);
909 }
910
CalcOverScrollVelocity()911 void Scrollable::CalcOverScrollVelocity()
912 {
913 auto gamma = 0.0f;
914 if (overScrollOffsetCallback_ && continuousSlidingCallback_) {
915 gamma = overScrollOffsetCallback_() / continuousSlidingCallback_();
916 }
917 gamma = GreatOrEqual(gamma, 1.0) ? 1.0f : gamma;
918 currentVelocity_ = currentVelocity_ * exp(-ratio_ * gamma);
919 }
920
StartScrollAnimation(float mainPosition,float correctVelocity,bool isScrollFromTouchPad)921 void Scrollable::StartScrollAnimation(float mainPosition, float correctVelocity, bool isScrollFromTouchPad)
922 {
923 if (state_ == AnimationState::SPRING) {
924 StopSpringAnimation();
925 }
926 if (!frictionOffsetProperty_) {
927 GetFrictionProperty();
928 }
929 StopSnapController();
930 TAG_LOGD(AceLogTag::ACE_SCROLLABLE, "The position of scroll motion is %{public}f, velocity is %{public}f",
931 mainPosition, correctVelocity);
932 double frictionTmp = friction_;
933 if (NearEqual(friction_, -1.0)) {
934 auto defaultFriction = isSlow_ ? SLOW_FRICTION : defaultFriction_;
935 double ret = SystemProperties::GetSrollableFriction();
936 frictionTmp = !NearZero(ret) ? ret : defaultFriction;
937 }
938 float friction = frictionTmp;
939 slidInfo_.friction = friction;
940 EventReport::ReportPageSlidInfo(slidInfo_);
941 initVelocity_ = correctVelocity;
942 finalPosition_ = mainPosition + correctVelocity / (friction * -FRICTION_SCALE);
943 if (fixScrollParamCallback_) {
944 fixScrollParamCallback_(mainPosition, initVelocity_, finalPosition_);
945 correctVelocity = initVelocity_;
946 currentVelocity_ = correctVelocity;
947 }
948 currentPos_ = mainPosition;
949 SnapAnimationOptions snapAnimationOptions = {
950 .snapDelta = GetFinalPosition() - mainPosition,
951 .animationVelocity = correctVelocity,
952 .dragDistance = GetDragOffset(),
953 };
954 if (startSnapAnimationCallback_ && startSnapAnimationCallback_(snapAnimationOptions)) {
955 if (GetSnapType() == SnapType::LIST_SNAP) {
956 currentVelocity_ = 0.0;
957 }
958 return;
959 }
960 float threshold = START_FRICTION_VELOCITY_THRESHOLD;
961 #ifdef SUPPORT_DIGITAL_CROWN
962 threshold = isCrownEventDragging_ ? CROWN_START_FRICTION_VELOCITY_THRESHOLD : threshold;
963 #endif
964 if (NearZero(correctVelocity, threshold)) {
965 HandleScrollEnd(correctVelocity);
966 currentVelocity_ = 0.0;
967 #ifdef OHOS_PLATFORM
968 if (FrameReport::GetInstance().GetEnable()) {
969 FrameReport::GetInstance().EndListFling();
970 }
971 #endif
972 return;
973 }
974 TriggerFrictionAnimation(mainPosition, friction, correctVelocity);
975 }
976
TriggerFrictionAnimation(float mainPosition,float friction,float correctVelocity)977 void Scrollable::TriggerFrictionAnimation(float mainPosition, float friction, float correctVelocity)
978 {
979 // change motion param when list item need to be center of screen on watch
980 FixScrollMotion(mainPosition, correctVelocity);
981 currentVelocity_ = 0.0;
982 lastPosition_ = currentPos_;
983 frictionVelocity_ = initVelocity_;
984 frictionOffsetProperty_->Set(mainPosition);
985 float response = fabs(2 * ACE_PI / (FRICTION_SCALE * friction));
986 auto curve = AceType::MakeRefPtr<ResponsiveSpringMotion>(response, 1.0f, 0.0f);
987 AnimationOption option;
988 option.SetCurve(curve);
989 option.SetDuration(CUSTOM_SPRING_ANIMATION_DURATION);
990 option.SetFinishCallbackType(FinishCallbackType::LOGICALLY);
991 frictionOffsetProperty_->SetThresholdType(ThresholdType::LAYOUT);
992 frictionOffsetProperty_->SetPropertyUnit(PropertyUnit::PIXEL_POSITION);
993 ACE_SCOPED_TRACE("Scrollable friction animation start, start:%f, end:%f, vel:%f, id:%d, tag:%s", mainPosition,
994 finalPosition_, initVelocity_, nodeId_, nodeTag_.c_str());
995 frictionOffsetProperty_->AnimateWithVelocity(
996 option, finalPosition_, initVelocity_, [weak = AceType::WeakClaim(this), id = Container::CurrentId()]() {
997 ContainerScope scope(id);
998 auto scroll = weak.Upgrade();
999 CHECK_NULL_VOID(scroll);
1000 scroll->state_ = AnimationState::IDLE;
1001 ACE_SCOPED_TRACE(
1002 "Scrollable friction animation finish, id:%d, tag:%s", scroll->nodeId_, scroll->nodeTag_.c_str());
1003 scroll->ProcessScrollMotionStop();
1004 });
1005 state_ = AnimationState::FRICTION;
1006 auto context = context_.Upgrade();
1007 CHECK_NULL_VOID(context);
1008 context->RequestFrame();
1009 lastVsyncTime_ = context->GetVsyncTime();
1010 }
1011
SetDelayedTask()1012 void Scrollable::SetDelayedTask()
1013 {
1014 SetContinuousDragStatus(true);
1015 auto context = context_.Upgrade();
1016 CHECK_NULL_VOID(context);
1017 auto taskExecutor = SingleTaskExecutor::Make(context->GetTaskExecutor(), TaskExecutor::TaskType::UI);
1018 task_.Reset([weak = WeakClaim(this)] {
1019 auto drag = weak.Upgrade();
1020 if (drag) {
1021 drag->ResetContinueDragCount();
1022 drag->SetContinuousDragStatus(false);
1023 }
1024 });
1025 taskExecutor.PostDelayedTask(task_, DRAG_INTERVAL_TIME, "ArkUIScrollDragInterval");
1026 }
1027
MarkNeedFlushAnimationStartTime()1028 void Scrollable::MarkNeedFlushAnimationStartTime()
1029 {
1030 auto context = context_.Upgrade();
1031 CHECK_NULL_VOID(context);
1032 context->MarkNeedFlushAnimationStartTime();
1033 }
1034
ComputeCap(int dragCount)1035 double Scrollable::ComputeCap(int dragCount)
1036 {
1037 if (dragCount < FIRST_THRESHOLD) {
1038 return 1.0;
1039 }
1040 auto cap = ComputeCap(dragCount - 1) + CAP_COEFFICIENT * (dragCount - 1);
1041 return cap;
1042 }
1043
GetGain(double delta)1044 double Scrollable::GetGain(double delta)
1045 {
1046 auto cap = 1.0;
1047 auto gain = 1.0;
1048 if (std::abs(delta) < MULTI_FLING_DISTANCE) {
1049 ResetContinueDragCount();
1050 preGain_ = gain;
1051 return gain;
1052 }
1053 if (!continuousSlidingCallback_) {
1054 preGain_ = gain;
1055 return gain;
1056 }
1057 auto screenHeight = continuousSlidingCallback_();
1058 if (delta == 0 || screenHeight == 0) {
1059 preGain_ = gain;
1060 return gain;
1061 }
1062 if (dragCount_ >= FIRST_THRESHOLD && dragCount_ < SECOND_THRESHOLD) {
1063 if (Negative(lastPos_ / delta)) {
1064 ResetContinueDragCount();
1065 preGain_ = gain;
1066 return gain;
1067 }
1068 cap = CAP_COEFFICIENT * (dragCount_ - 1);
1069 gain = (LessNotEqual(cap, std::abs(delta) / screenHeight * (dragCount_ - 1)))
1070 ? preGain_ + cap
1071 : preGain_ + std::abs(delta) / screenHeight * (dragCount_ - 1);
1072 } else if (dragCount_ >= SECOND_THRESHOLD) {
1073 if (Negative(lastPos_ / delta)) {
1074 ResetContinueDragCount();
1075 preGain_ = gain;
1076 return gain;
1077 }
1078 cap = CAP_FIXED_VALUE;
1079 gain = (LessNotEqual(cap, preGain_ + std::abs(delta) / screenHeight * (dragCount_ - 1)))
1080 ? cap
1081 : preGain_ + std::abs(delta) / screenHeight * (dragCount_ - 1);
1082 }
1083 preGain_ = gain;
1084 return gain;
1085 }
1086
ExecuteScrollBegin(double & mainDelta)1087 void Scrollable::ExecuteScrollBegin(double& mainDelta)
1088 {
1089 auto context = context_.Upgrade();
1090 if (!scrollBeginCallback_ || !context) {
1091 return;
1092 }
1093
1094 ScrollInfo scrollInfo;
1095 if (axis_ == Axis::VERTICAL) {
1096 scrollInfo = scrollBeginCallback_(0.0_vp, Dimension(mainDelta / context->GetDipScale(), DimensionUnit::VP));
1097 mainDelta = context->NormalizeToPx(scrollInfo.dy);
1098 } else if (axis_ == Axis::HORIZONTAL) {
1099 scrollInfo = scrollBeginCallback_(Dimension(mainDelta / context->GetDipScale(), DimensionUnit::VP), 0.0_vp);
1100 mainDelta = context->NormalizeToPx(scrollInfo.dx);
1101 }
1102 }
1103
GetFrictionVelocityByFinalPosition(float final,float position,float friction,float signum,float threshold)1104 float Scrollable::GetFrictionVelocityByFinalPosition(
1105 float final, float position, float friction, float signum, float threshold)
1106 {
1107 return DEFAULT_THRESHOLD * threshold * signum - (final - position) * friction;
1108 }
1109
FixScrollMotion(float position,float initVelocity)1110 void Scrollable::FixScrollMotion(float position, float initVelocity)
1111 {
1112 #ifdef WEARABLE_PRODUCT
1113 float signum = 0.0;
1114 if (!NearZero(initVelocity)) {
1115 signum = GreatNotEqual(initVelocity, 0.0) ? 1.0 : -1.0;
1116 }
1117 if (frictionOffsetProperty_ && needCenterFix_ && watchFixCallback_) {
1118 float finalPosition = watchFixCallback_(GetFinalPosition(), position);
1119 if (!NearEqual(finalPosition, GetFinalPosition(), DISTANCE_EPSILON)) {
1120 float friction = friction_;
1121 float velocity = GetFrictionVelocityByFinalPosition(finalPosition, position, friction, signum);
1122
1123 // fix again when velocity is less than velocity threshold
1124 if (!NearEqual(finalPosition, GetFinalPosition(), DISTANCE_EPSILON)) {
1125 velocity = GetFrictionVelocityByFinalPosition(finalPosition, position, friction, signum, 0.0f);
1126 }
1127 initVelocity_ = velocity;
1128 finalPosition_ = mainPosition + initVelocity_ / (friction * -FRICTION_SCALE);
1129 }
1130 }
1131 #endif
1132 }
1133
StartListSnapAnimation(float predictSnapOffset,float scrollSnapVelocity,bool fromScrollBar)1134 void Scrollable::StartListSnapAnimation(float predictSnapOffset, float scrollSnapVelocity, bool fromScrollBar)
1135 {
1136 endPos_ = currentPos_ + predictSnapOffset;
1137 finalPosition_ = endPos_;
1138 snapAnimationFromScrollBar_ = fromScrollBar;
1139 AnimationOption option;
1140 option.SetDuration(CUSTOM_SPRING_ANIMATION_DURATION);
1141 auto curve = AceType::MakeRefPtr<ResponsiveSpringMotion>(DEFAULT_SPRING_RESPONSE, DEFAULT_SPRING_DAMP, 0.0f);
1142 option.SetCurve(curve);
1143 if (!snapOffsetProperty_) {
1144 GetSnapProperty();
1145 }
1146 snapOffsetProperty_->Set(currentPos_);
1147 snapOffsetProperty_->SetPropertyUnit(PropertyUnit::PIXEL_POSITION);
1148 ACE_SCOPED_TRACE("List snap animation start, start:%f, end:%f, vel:%f, id:%d", currentPos_, endPos_,
1149 scrollSnapVelocity, nodeId_);
1150 updateSnapAnimationCount_++;
1151 snapOffsetProperty_->AnimateWithVelocity(
1152 option, endPos_, scrollSnapVelocity, [weak = AceType::WeakClaim(this), id = Container::CurrentId()]() {
1153 ContainerScope scope(id);
1154 auto scroll = weak.Upgrade();
1155 CHECK_NULL_VOID(scroll);
1156 ACE_SCOPED_TRACE("List snap animation finish, id:%d", scroll->nodeId_);
1157 scroll->updateSnapAnimationCount_--;
1158 if (scroll->updateSnapAnimationCount_ == 0) {
1159 scroll->state_ = AnimationState::IDLE;
1160 scroll->axisSnapDistance_ = 0.f;
1161 scroll->ProcessScrollSnapStop();
1162 }
1163 scroll->snapAnimationFromScrollBar_ = false;
1164 });
1165 state_ = AnimationState::SNAP;
1166 auto context = context_.Upgrade();
1167 CHECK_NULL_VOID(context);
1168 lastVsyncTime_ = context->GetVsyncTime();
1169 MarkNeedFlushAnimationStartTime();
1170 }
1171
StartScrollSnapAnimation(float scrollSnapDelta,float scrollSnapVelocity,bool fromScrollBar)1172 void Scrollable::StartScrollSnapAnimation(float scrollSnapDelta, float scrollSnapVelocity, bool fromScrollBar)
1173 {
1174 TAG_LOGD(AceLogTag::ACE_SCROLLABLE,
1175 "The snap delta of scroll motion is %{public}f, "
1176 "The snap velocity of scroll motion is %{public}f",
1177 scrollSnapDelta, scrollSnapVelocity);
1178 if (NearZero(scrollSnapDelta)) {
1179 return;
1180 }
1181 endPos_ = currentPos_ + scrollSnapDelta;
1182 finalPosition_ = endPos_;
1183 snapAnimationFromScrollBar_ = fromScrollBar;
1184 ACE_SCOPED_TRACE("Scroll snap animation start, start:%f, end:%f, vel:%f, id:%d", currentPos_, endPos_,
1185 scrollSnapVelocity, nodeId_);
1186 AnimationOption option;
1187 option.SetDuration(CUSTOM_SPRING_ANIMATION_DURATION);
1188 auto curve = AceType::MakeRefPtr<ResponsiveSpringMotion>(DEFAULT_SPRING_RESPONSE, DEFAULT_SPRING_DAMP, 0.0f);
1189 auto minimumAmplitudeRatio = DEFAULT_MINIMUM_AMPLITUDE_PX / std::abs(scrollSnapDelta);
1190 minimumAmplitudeRatio = std::min(minimumAmplitudeRatio, RESPONSIVE_SPRING_AMPLITUDE_RATIO);
1191 if (LessNotEqualCustomPrecision(minimumAmplitudeRatio,
1192 ResponsiveSpringMotion::DEFAULT_RESPONSIVE_SPRING_AMPLITUDE_RATIO)) {
1193 curve->UpdateMinimumAmplitudeRatio(minimumAmplitudeRatio);
1194 }
1195 option.SetCurve(curve);
1196 if (!snapOffsetProperty_) {
1197 GetSnapProperty();
1198 }
1199 snapOffsetProperty_->Set(currentPos_);
1200 snapOffsetProperty_->SetPropertyUnit(PropertyUnit::PIXEL_POSITION);
1201 updateSnapAnimationCount_++;
1202 snapOffsetProperty_->AnimateWithVelocity(
1203 option, endPos_, scrollSnapVelocity, [weak = AceType::WeakClaim(this), id = Container::CurrentId()]() {
1204 ContainerScope scope(id);
1205 auto scroll = weak.Upgrade();
1206 CHECK_NULL_VOID(scroll);
1207 scroll->updateSnapAnimationCount_--;
1208 if (scroll->updateSnapAnimationCount_ == 0) {
1209 scroll->state_ = AnimationState::IDLE;
1210 scroll->nextStep_.reset();
1211 scroll->axisSnapDistance_ = 0.f;
1212 scroll->snapDirection_ = SnapDirection::NONE;
1213 ACE_SCOPED_TRACE("Scroll snap animation finish, id:%d", scroll->nodeId_);
1214 scroll->ProcessScrollMotionStop();
1215 }
1216 scroll->snapAnimationFromScrollBar_ = false;
1217 });
1218 state_ = AnimationState::SNAP;
1219 auto context = context_.Upgrade();
1220 CHECK_NULL_VOID(context);
1221 lastVsyncTime_ = context->GetVsyncTime();
1222 }
1223
UpdateScrollSnapStartOffset(double offset)1224 void Scrollable::UpdateScrollSnapStartOffset(double offset)
1225 {
1226 UpdateScrollSnapEndWithOffset(offset);
1227 }
1228
ProcessListSnapMotion(double position)1229 void Scrollable::ProcessListSnapMotion(double position)
1230 {
1231 TAG_LOGD(AceLogTag::ACE_SCROLLABLE, "Current Pos is %{public}f, position is %{public}f", currentPos_, position);
1232 currentVelocity_ = snapVelocity_;
1233 auto source = snapAnimationFromScrollBar_ ? SCROLL_FROM_BAR_FLING : SCROLL_FROM_ANIMATION;
1234 if (NearEqual(currentPos_, position)) {
1235 UpdateScrollPosition(0.0, source);
1236 } else {
1237 auto mainDelta = position - currentPos_;
1238 HandleScroll(mainDelta, source, NestedState::GESTURE);
1239 if (!moved_ && state_ == AnimationState::SNAP) {
1240 StopSnapAnimation();
1241 }
1242 }
1243 if (LessOrEqual(std::abs(currentPos_ - position), 1)) {
1244 // trace stop at OnScrollStop
1245 AceAsyncTraceBeginCommercial(
1246 nodeId_, (TRAILING_ANIMATION + std::to_string(nodeId_) + std::string(" ") + nodeTag_).c_str());
1247 }
1248 currentPos_ = position;
1249 if (canOverScroll_ && state_ == AnimationState::SNAP) {
1250 if (source != SCROLL_FROM_BAR_FLING) {
1251 scrollPause_ = true;
1252 skipRestartSpring_ = true;
1253 MarkNeedFlushAnimationStartTime();
1254 }
1255 StopSnapAnimation();
1256 }
1257 }
1258
ProcessScrollSnapStop()1259 void Scrollable::ProcessScrollSnapStop()
1260 {
1261 if (scrollPause_) {
1262 scrollPause_ = false;
1263 HandleOverScroll(currentVelocity_);
1264 } else {
1265 OnAnimateStop();
1266 }
1267 }
1268
OnAnimateStop()1269 void Scrollable::OnAnimateStop()
1270 {
1271 HandleScrollEnd(std::nullopt);
1272 currentVelocity_ = 0.0;
1273 if (isTouching_ || isDragUpdateStop_) {
1274 return;
1275 }
1276 moved_ = false;
1277 #ifdef OHOS_PLATFORM
1278 if (FrameReport::GetInstance().GetEnable()) {
1279 FrameReport::GetInstance().EndListFling();
1280 }
1281 #endif
1282 #if !defined(PREVIEW)
1283 LayoutInspector::SupportInspector();
1284 #endif
1285 }
1286
StartSpringMotion(double mainPosition,double mainVelocity,const ExtentPair & extent,const ExtentPair & initExtent)1287 void Scrollable::StartSpringMotion(
1288 double mainPosition, double mainVelocity, const ExtentPair& extent, const ExtentPair& initExtent)
1289 {
1290 TAG_LOGD(AceLogTag::ACE_SCROLLABLE,
1291 "position is %{public}f, mainVelocity is %{public}f, minExtent is "
1292 "%{public}f, maxExtent is %{public}f, initMinExtent is %{public}f, initMaxExtent is %{public}f",
1293 mainPosition, mainVelocity, extent.Leading(), extent.Trailing(), initExtent.Leading(), initExtent.Trailing());
1294 if (state_ == AnimationState::SPRING || (skipRestartSpring_ && NearEqual(mainVelocity, 0.0f, 0.001f))) {
1295 return;
1296 }
1297 currentPos_ = mainPosition;
1298 if (mainPosition > initExtent.Trailing() || NearEqual(mainPosition, initExtent.Trailing(), 0.01f)) {
1299 finalPosition_ = extent.Trailing();
1300 } else if (mainPosition < initExtent.Leading() || NearEqual(mainPosition, initExtent.Leading(), 0.01f)) {
1301 finalPosition_ = extent.Leading();
1302 } else {
1303 return;
1304 }
1305
1306 if (!springOffsetProperty_) {
1307 GetSpringProperty();
1308 }
1309 springAnimationCount_++;
1310 if (AnimationUtils::IsImplicitAnimationOpen()) {
1311 AnimationUtils::ExecuteWithoutAnimation([weak = AceType::WeakClaim(this), mainPosition]() {
1312 auto scrollable = weak.Upgrade();
1313 CHECK_NULL_VOID(scrollable);
1314 scrollable->springOffsetProperty_->Set(mainPosition);
1315 });
1316 } else {
1317 springOffsetProperty_->Set(mainPosition);
1318 }
1319 AnimationOption option;
1320 auto curve = AceType::MakeRefPtr<ResponsiveSpringMotion>(springResponse_, DEFAULT_SPRING_DAMP, 0.0f);
1321 option.SetCurve(curve);
1322 option.SetDuration(CUSTOM_SPRING_ANIMATION_DURATION);
1323 springOffsetProperty_->SetPropertyUnit(PropertyUnit::PIXEL_POSITION);
1324 ACE_SCOPED_TRACE("Scrollable spring animation start, start:%f, end:%f, vel:%f, id:%d, tag:%s", mainPosition,
1325 finalPosition_, mainVelocity, nodeId_, nodeTag_.c_str());
1326 lastVsyncTime_ = static_cast<uint64_t>(GetSysTimestamp());
1327 springOffsetProperty_->AnimateWithVelocity(
1328 option, finalPosition_, mainVelocity, [weak = AceType::WeakClaim(this), id = Container::CurrentId()]() {
1329 ContainerScope scope(id);
1330 auto scroll = weak.Upgrade();
1331 CHECK_NULL_VOID(scroll);
1332 scroll->springAnimationCount_--;
1333 ACE_SCOPED_TRACE(
1334 "Scrollable spring animation finish, id:%d, tag:%s", scroll->nodeId_, scroll->nodeTag_.c_str());
1335 // avoid current animation being interrupted by the prev animation's finish callback
1336 // and triggering onScrollStop when spring animation turns to friction animation.
1337 if (scroll->springAnimationCount_ > 0 || scroll->scrollPause_) {
1338 scroll->scrollPause_ = false;
1339 return;
1340 }
1341 scroll->state_ = AnimationState::IDLE;
1342 scroll->currentVelocity_ = 0.0;
1343 scroll->OnAnimateStop();
1344 });
1345 state_ = AnimationState::SPRING;
1346 skipRestartSpring_ = false;
1347 auto context = context_.Upgrade();
1348 CHECK_NULL_VOID(context);
1349 context->RequestFrame();
1350 }
1351
UpdateSpringMotion(double mainPosition,const ExtentPair & extent,const ExtentPair & initExtent)1352 void Scrollable::UpdateSpringMotion(double mainPosition, const ExtentPair& extent, const ExtentPair& initExtent)
1353 {
1354 TAG_LOGD(AceLogTag::ACE_SCROLLABLE,
1355 "position is %{public}f, minExtent is "
1356 "%{public}f, maxExtent is %{public}f, initMinExtent is %{public}f, initMaxExtent is %{public}f",
1357 mainPosition, extent.Leading(), extent.Trailing(), initExtent.Leading(), initExtent.Trailing());
1358 if (state_ != AnimationState::SPRING || !springOffsetProperty_) {
1359 return;
1360 }
1361 float finalPosition = 0.0f;
1362 if (mainPosition > initExtent.Trailing() || NearEqual(mainPosition, initExtent.Trailing())) {
1363 finalPosition = extent.Trailing();
1364 } else if (mainPosition < initExtent.Leading() || NearEqual(mainPosition, initExtent.Leading())) {
1365 finalPosition = extent.Leading();
1366 } else {
1367 return;
1368 }
1369
1370 finalPosition = finalPosition_ + (finalPosition - mainPosition) - (finalPosition_ - currentPos_);
1371 if (NearEqual(finalPosition, finalPosition_, SPRING_ACCURACY)) {
1372 return;
1373 }
1374 finalPosition_ = finalPosition;
1375 springAnimationCount_++;
1376 AnimationOption option;
1377 auto curve = AceType::MakeRefPtr<ResponsiveSpringMotion>(DEFAULT_SPRING_RESPONSE, DEFAULT_SPRING_DAMP, 0.0f);
1378 option.SetCurve(curve);
1379 option.SetDuration(CUSTOM_SPRING_ANIMATION_DURATION);
1380 springOffsetProperty_->SetPropertyUnit(PropertyUnit::PIXEL_POSITION);
1381 ACE_SCOPED_TRACE("Scrollable spring animation update, start:%f, end:%f, id:%d, tag:%s", mainPosition,
1382 finalPosition_, nodeId_, nodeTag_.c_str());
1383 AnimationUtils::StartAnimation(
1384 option,
1385 [weak = AceType::WeakClaim(this)]() {
1386 auto scroll = weak.Upgrade();
1387 CHECK_NULL_VOID(scroll);
1388 scroll->springOffsetProperty_->Set(scroll->finalPosition_);
1389 scroll->state_ = AnimationState::SPRING;
1390 },
1391 [weak = AceType::WeakClaim(this), id = Container::CurrentId()]() {
1392 ContainerScope scope(id);
1393 auto scroll = weak.Upgrade();
1394 CHECK_NULL_VOID(scroll);
1395 scroll->springAnimationCount_--;
1396 // avoid current animation being interrupted by the prev animation's finish callback
1397 if (scroll->springAnimationCount_ > 0) {
1398 return;
1399 }
1400 ACE_SCOPED_TRACE(
1401 "Scrollable updated spring animation finish, id:%d, tag:%s", scroll->nodeId_, scroll->nodeTag_.c_str());
1402 scroll->state_ = AnimationState::IDLE;
1403 scroll->currentVelocity_ = 0.0;
1404 scroll->OnAnimateStop();
1405 });
1406 state_ = AnimationState::SPRING;
1407 skipRestartSpring_ = false;
1408 }
1409
ProcessScrollMotionStop()1410 void Scrollable::ProcessScrollMotionStop()
1411 {
1412 if (needScrollSnapChange_ && startSnapAnimationCallback_ && frictionOffsetProperty_) {
1413 needScrollSnapChange_ = false;
1414 SnapAnimationOptions snapAnimationOptions = {
1415 .snapDelta = GetFinalPosition() - currentPos_,
1416 .animationVelocity = currentVelocity_,
1417 };
1418 CHECK_NULL_VOID(!startSnapAnimationCallback_(snapAnimationOptions));
1419 }
1420 // spring effect special process
1421 if (scrollPause_) {
1422 scrollPause_ = false;
1423 state_ = AnimationState::TRANSITION;
1424 HandleOverScroll(currentVelocity_);
1425 if (state_ == AnimationState::TRANSITION) {
1426 // didn't trigger spring animation
1427 state_ = AnimationState::IDLE;
1428 }
1429 return;
1430 }
1431
1432 if (isDragUpdateStop_) {
1433 return;
1434 }
1435 moved_ = false;
1436 HandleScrollEnd(std::nullopt);
1437 #ifdef OHOS_PLATFORM
1438 if (FrameReport::GetInstance().GetEnable()) {
1439 FrameReport::GetInstance().EndListFling();
1440 }
1441 #endif
1442 currentVelocity_ = 0.0;
1443 #if !defined(PREVIEW)
1444 LayoutInspector::SupportInspector();
1445 #endif
1446 }
1447
ProcessSpringMotion(double position)1448 void Scrollable::ProcessSpringMotion(double position)
1449 {
1450 TAG_LOGD(AceLogTag::ACE_SCROLLABLE, "Current Pos is %{public}f, position is %{public}f", currentPos_, position);
1451 auto context = context_.Upgrade();
1452 CHECK_NULL_VOID(context);
1453 uint64_t currentVsync = context->GetVsyncTime();
1454 uint64_t diff = currentVsync - lastVsyncTime_;
1455 if (diff < MAX_VSYNC_DIFF_TIME && diff > MIN_DIFF_VSYNC) {
1456 currentVelocity_ = (position - currentPos_) / diff * MILLOS_PER_NANO_SECONDS;
1457 }
1458 lastVsyncTime_ = currentVsync;
1459 if (LessOrEqual(std::abs(currentPos_ - position), 1)) {
1460 // trace stop at OnScrollStop
1461 if (!isFadingAway_) {
1462 AceAsyncTraceBeginCommercial(
1463 nodeId_, (TRAILING_ANIMATION + std::to_string(nodeId_) + std::string(" ") + nodeTag_).c_str());
1464 } else {
1465 ACE_SCOPED_TRACE("Spring to same position");
1466 }
1467 }
1468 auto distance = currentPos_ - finalPosition_;
1469 auto nextDistance = position - finalPosition_;
1470 isFadingAway_ = GreatNotEqual(std::abs(nextDistance), std::abs(distance));
1471 auto delta = position - currentPos_;
1472 if (distance * nextDistance < 0) {
1473 double currentVelocity = currentVelocity_;
1474 scrollPause_ = true;
1475 MarkNeedFlushAnimationStartTime();
1476 StopSpringAnimation();
1477 ACE_SCOPED_TRACE("change direction in spring animation and start fling animation, distance:%f, "
1478 "nextDistance:%f, nodeId:%d, tag:%s",
1479 distance, nextDistance, nodeId_, nodeTag_.c_str());
1480 // only handle offsets that are out of bounds
1481 delta = finalPosition_ - currentPos_;
1482 moved_ = UpdateScrollPosition(delta, SCROLL_FROM_ANIMATION_SPRING);
1483 // remainVelocityCallback_ will pass the velocity to the child component
1484 if (!remainVelocityCallback_ || !remainVelocityCallback_(currentVelocity)) {
1485 StartScrollAnimation(position, currentVelocity);
1486 }
1487 } else {
1488 moved_ = UpdateScrollPosition(delta, SCROLL_FROM_ANIMATION_SPRING);
1489 }
1490 if (!moved_) {
1491 StopSpringAnimation();
1492 }
1493 currentPos_ = position;
1494 }
1495
CalcNextStep(double position,double mainDelta)1496 double Scrollable::CalcNextStep(double position, double mainDelta)
1497 {
1498 auto finalDelta = finalPosition_ - currentPos_;
1499 if (GreatNotEqual(std::abs(finalDelta), SCROLL_SNAP_MIN_STEP_THRESHOLD)) {
1500 return mainDelta;
1501 }
1502 if (LessOrEqual(std::abs(finalDelta), SCROLL_SNAP_MIN_STEP)) {
1503 return finalDelta;
1504 }
1505 if (nextStep_.has_value()) {
1506 return nextStep_.value();
1507 }
1508 if (LessOrEqual(std::abs(mainDelta), SCROLL_SNAP_MIN_STEP)) {
1509 nextStep_ = Positive(finalDelta) ? SCROLL_SNAP_MIN_STEP : -SCROLL_SNAP_MIN_STEP;
1510 mainDelta = nextStep_.value();
1511 }
1512 return mainDelta;
1513 }
1514
ProcessScrollMotion(double position,int32_t source)1515 void Scrollable::ProcessScrollMotion(double position, int32_t source)
1516 {
1517 currentVelocity_ = state_ == AnimationState::SNAP ? snapVelocity_ : frictionVelocity_;
1518 auto mainDelta = position - currentPos_;
1519 #ifdef SUPPORT_DIGITAL_CROWN
1520 if (state_ == AnimationState::SNAP) {
1521 mainDelta = CalcNextStep(position, mainDelta);
1522 position = currentPos_ + mainDelta;
1523 }
1524 #endif
1525 if (needScrollSnapToSideCallback_) {
1526 needScrollSnapChange_ = needScrollSnapToSideCallback_(mainDelta);
1527 }
1528 TAG_LOGD(AceLogTag::ACE_SCROLLABLE, "position is %{public}f, currentVelocity_ is %{public}f, "
1529 "currentPos_ is %{public}f, needScrollSnapChange_ is %{public}u", position, currentVelocity_,
1530 currentPos_, needScrollSnapChange_);
1531 if (LessOrEqual(std::abs(mainDelta), 1)) {
1532 // trace stop at OnScrollStop
1533 AceAsyncTraceBeginCommercial(
1534 nodeId_, (TRAILING_ANIMATION + std::to_string(nodeId_) + std::string(" ") + nodeTag_).c_str());
1535 }
1536 // UpdateScrollPosition return false, means reach to scroll limit.
1537 source = snapAnimationFromScrollBar_ && state_ == AnimationState::SNAP ? SCROLL_FROM_BAR_FLING : source;
1538 HandleScroll(mainDelta, source, NestedState::GESTURE);
1539 if (!moved_) {
1540 ResetContinueDragCount();
1541 StopFrictionAnimation();
1542 StopSnapAnimation();
1543 }
1544 currentPos_ = position;
1545 #ifdef SUPPORT_DIGITAL_CROWN
1546 if (state_ == AnimationState::SNAP && NearEqual(currentPos_, finalPosition_)) {
1547 StopSnapAnimation();
1548 }
1549 #endif
1550 // spring effect special process
1551 if ((canOverScroll_ && source != SCROLL_FROM_AXIS) || needScrollSnapChange_) {
1552 ACE_SCOPED_TRACE("scrollPause set true to stop ProcessScrollMotion, canOverScroll:%u, needScrollSnapChange:%u, "
1553 "nodeId:%d, tag:%s",
1554 canOverScroll_, needScrollSnapChange_, nodeId_, nodeTag_.c_str());
1555 if (source != SCROLL_FROM_BAR_FLING) {
1556 scrollPause_ = true;
1557 skipRestartSpring_ = true;
1558 MarkNeedFlushAnimationStartTime();
1559 }
1560 ResetContinueDragCount();
1561 StopFrictionAnimation();
1562 StopSnapAnimation();
1563 }
1564 }
1565
UpdateScrollPosition(const double offset,int32_t source) const1566 bool Scrollable::UpdateScrollPosition(const double offset, int32_t source) const
1567 {
1568 bool ret = true;
1569 if (callback_) {
1570 ret = callback_(offset, source);
1571 }
1572 return ret;
1573 }
1574
ProcessScrollOverCallback(double velocity)1575 void Scrollable::ProcessScrollOverCallback(double velocity)
1576 {
1577 if (outBoundaryCallback_ && !outBoundaryCallback_() && !canOverScroll_) {
1578 return;
1579 }
1580 // In the case of chain animation enabled, you need to switch the control point first,
1581 // and then correct the offset value in notification process
1582 if (notifyScrollOverCallback_) {
1583 notifyScrollOverCallback_(velocity);
1584 }
1585 // then use corrected offset to make scroll motion.
1586 if (scrollOverCallback_) {
1587 scrollOverCallback_(velocity);
1588 }
1589 }
1590
HandleOverScroll(double velocity)1591 bool Scrollable::HandleOverScroll(double velocity)
1592 {
1593 if (!overScrollCallback_) {
1594 if (edgeEffect_ == EdgeEffect::SPRING) {
1595 ProcessScrollOverCallback(velocity);
1596 return true;
1597 }
1598 if (scrollEndCallback_) {
1599 scrollEndCallback_();
1600 }
1601 return false;
1602 }
1603 // call NestableScrollContainer::HandleOverScroll
1604 return overScrollCallback_(velocity);
1605 }
1606
SetSlipFactor(double SlipFactor)1607 void Scrollable::SetSlipFactor(double SlipFactor)
1608 {
1609 slipFactor_ = std::clamp(SlipFactor, -ADJUSTABLE_VELOCITY, ADJUSTABLE_VELOCITY);
1610 }
1611
UpdateScrollSnapEndWithOffset(double offset)1612 void Scrollable::UpdateScrollSnapEndWithOffset(double offset)
1613 {
1614 if (state_ == AnimationState::SNAP) {
1615 MarkNeedFlushAnimationStartTime();
1616 AnimationOption option;
1617 option.SetDuration(CUSTOM_SPRING_ANIMATION_DURATION);
1618 auto curve = AceType::MakeRefPtr<ResponsiveSpringMotion>(DEFAULT_SPRING_RESPONSE, DEFAULT_SPRING_DAMP, 0.0f);
1619 option.SetCurve(curve);
1620 if (!snapOffsetProperty_) {
1621 GetSnapProperty();
1622 }
1623 updateSnapAnimationCount_++;
1624 endPos_ -= offset;
1625 finalPosition_ = endPos_;
1626 snapOffsetProperty_->SetPropertyUnit(PropertyUnit::PIXEL_POSITION);
1627 AnimationUtils::StartAnimation(
1628 option,
1629 [weak = AceType::WeakClaim(this)]() {
1630 auto scroll = weak.Upgrade();
1631 CHECK_NULL_VOID(scroll);
1632 scroll->snapOffsetProperty_->Set(scroll->endPos_);
1633 scroll->state_ = AnimationState::SNAP;
1634 },
1635 [weak = AceType::WeakClaim(this), id = Container::CurrentId()]() {
1636 ContainerScope scope(id);
1637 auto scroll = weak.Upgrade();
1638 CHECK_NULL_VOID(scroll);
1639 scroll->updateSnapAnimationCount_--;
1640 // avoid current animation being interrupted by the prev animation's finish callback
1641 if (scroll->updateSnapAnimationCount_ == 0) {
1642 scroll->snapDirection_ = SnapDirection::NONE;
1643 scroll->state_ = AnimationState::IDLE;
1644 scroll->ProcessScrollSnapStop();
1645 }
1646 });
1647 state_ = AnimationState::SNAP;
1648 }
1649 }
1650
GetPredictSnapOffset() const1651 std::optional<float> Scrollable::GetPredictSnapOffset() const
1652 {
1653 if (state_ == AnimationState::SNAP) {
1654 return endPos_ - currentPos_;
1655 }
1656 return std::nullopt;
1657 }
1658
AttachAnimatableProperty(const RefPtr<NodeAnimatablePropertyFloat> & property)1659 void Scrollable::AttachAnimatableProperty(const RefPtr<NodeAnimatablePropertyFloat>& property)
1660 {
1661 auto host = weakHost_.Upgrade();
1662 CHECK_NULL_VOID(host);
1663 auto renderContext = host->GetRenderContext();
1664 CHECK_NULL_VOID(renderContext);
1665 renderContext->AttachNodeAnimatableProperty(property);
1666 }
1667
GetFrictionProperty()1668 RefPtr<NodeAnimatablePropertyFloat> Scrollable::GetFrictionProperty()
1669 {
1670 auto propertyCallback = [weak = AceType::WeakClaim(this)](float position) {
1671 auto scroll = weak.Upgrade();
1672 CHECK_NULL_VOID(scroll);
1673 if (scroll->state_ != AnimationState::FRICTION || scroll->isTouching_) {
1674 return;
1675 }
1676 scroll->ProcessScrollMotion(position);
1677 if (NearEqual(scroll->finalPosition_, position, 1.0)) {
1678 scroll->StopFrictionAnimation();
1679 }
1680 auto context = scroll->context_.Upgrade();
1681 CHECK_NULL_VOID(context);
1682 uint64_t currentVsync = context->GetVsyncTime();
1683 uint64_t diff = currentVsync - scroll->lastVsyncTime_;
1684 if (diff < MAX_VSYNC_DIFF_TIME && diff > MIN_DIFF_VSYNC) {
1685 scroll->frictionVelocity_ = (position - scroll->lastPosition_) / diff * MILLOS_PER_NANO_SECONDS;
1686 if (NearZero(scroll->frictionVelocity_, FRICTION_VELOCITY_THRESHOLD)) {
1687 scroll->StopFrictionAnimation();
1688 ResSchedReport::GetInstance().ResSchedDataReport("slide_off");
1689 }
1690 }
1691 scroll->lastVsyncTime_ = currentVsync;
1692 scroll->lastPosition_ = position;
1693 };
1694 frictionOffsetProperty_ = AceType::MakeRefPtr<NodeAnimatablePropertyFloat>(0.0, std::move(propertyCallback));
1695 AttachAnimatableProperty(frictionOffsetProperty_);
1696 return frictionOffsetProperty_;
1697 }
1698
GetSpringProperty()1699 RefPtr<NodeAnimatablePropertyFloat> Scrollable::GetSpringProperty()
1700 {
1701 auto propertyCallback = [weak = AceType::WeakClaim(this)](float position) {
1702 auto scroll = weak.Upgrade();
1703 CHECK_NULL_VOID(scroll);
1704 if (scroll->state_ != AnimationState::SPRING) {
1705 return;
1706 }
1707 if (!NearEqual(scroll->finalPosition_, position, SPRING_ACCURACY)) {
1708 scroll->ProcessSpringMotion(position);
1709 return;
1710 }
1711 /*
1712 * In order to prevent accumulation errors, the current position is re obtained to ensure that
1713 * the last frame can accurately stop at the top and bottom positions.
1714 */
1715 if (scroll->currentPositionCallback_) {
1716 double currPos = scroll->currentPositionCallback_();
1717 if (NearEqual(currPos, scroll->currentPos_, 0.5)) {
1718 scroll->currentPos_ = currPos;
1719 }
1720 }
1721 scroll->StopSpringAnimation(true);
1722 };
1723 springOffsetProperty_ = AceType::MakeRefPtr<NodeAnimatablePropertyFloat>(0.0, std::move(propertyCallback));
1724 AttachAnimatableProperty(springOffsetProperty_);
1725 return springOffsetProperty_;
1726 }
1727
GetSnapProperty()1728 RefPtr<NodeAnimatablePropertyFloat> Scrollable::GetSnapProperty()
1729 {
1730 auto propertyCallback = [weak = AceType::WeakClaim(this)](float position) {
1731 auto scroll = weak.Upgrade();
1732 CHECK_NULL_VOID(scroll);
1733 if (scroll->state_ != AnimationState::SNAP) {
1734 return;
1735 }
1736 auto context = scroll->context_.Upgrade();
1737 CHECK_NULL_VOID(context);
1738 uint64_t currentVsync = context->GetVsyncTime();
1739 uint64_t diff = currentVsync - scroll->lastVsyncTime_;
1740 if (diff < MAX_VSYNC_DIFF_TIME && diff > MIN_DIFF_VSYNC) {
1741 scroll->snapVelocity_ = (position - scroll->currentPos_) / diff * MILLOS_PER_NANO_SECONDS;
1742 }
1743 scroll->lastVsyncTime_ = currentVsync;
1744
1745 if (NearEqual(scroll->endPos_, position, SPRING_ACCURACY)) {
1746 if (scroll->GetSnapType() == SnapType::LIST_SNAP) {
1747 scroll->ProcessListSnapMotion(scroll->endPos_);
1748 } else if (scroll->GetSnapType() == SnapType::SCROLL_SNAP) {
1749 scroll->ProcessScrollMotion(scroll->endPos_);
1750 }
1751 scroll->StopSnapAnimation();
1752 } else {
1753 if (scroll->GetSnapType() == SnapType::LIST_SNAP) {
1754 scroll->ProcessListSnapMotion(position);
1755 } else if (scroll->GetSnapType() == SnapType::SCROLL_SNAP) {
1756 scroll->ProcessScrollMotion(position);
1757 }
1758 }
1759 };
1760 snapOffsetProperty_ = AceType::MakeRefPtr<NodeAnimatablePropertyFloat>(0.0, std::move(propertyCallback));
1761 AttachAnimatableProperty(snapOffsetProperty_);
1762 return snapOffsetProperty_;
1763 }
1764
StopFrictionAnimation()1765 void Scrollable::StopFrictionAnimation()
1766 {
1767 if (state_ == AnimationState::FRICTION) {
1768 ACE_SCOPED_TRACE("StopFrictionAnimation, id:%d, tag:%s", nodeId_, nodeTag_.c_str());
1769 state_ = AnimationState::IDLE;
1770 CHECK_NULL_VOID(frictionOffsetProperty_);
1771 AnimationOption option;
1772 option.SetCurve(Curves::EASE);
1773 option.SetDuration(0);
1774 AnimationUtils::StartAnimation(
1775 option,
1776 [weak = AceType::WeakClaim(this)]() {
1777 auto scroll = weak.Upgrade();
1778 CHECK_NULL_VOID(scroll);
1779 scroll->frictionOffsetProperty_->Set(scroll->currentPos_);
1780 },
1781 nullptr);
1782 }
1783 }
1784
StopSpringAnimation(bool reachFinalPosition)1785 void Scrollable::StopSpringAnimation(bool reachFinalPosition)
1786 {
1787 if (state_ == AnimationState::SPRING) {
1788 ACE_SCOPED_TRACE(
1789 "StopSpringAnimation, reachFinalPosition:%u, id:%d, tag:%s", reachFinalPosition, nodeId_, nodeTag_.c_str());
1790 state_ = AnimationState::IDLE;
1791 isFadingAway_ = false;
1792 CHECK_NULL_VOID(springOffsetProperty_);
1793 AnimationOption option;
1794 option.SetCurve(Curves::EASE);
1795 option.SetDuration(0);
1796 AnimationUtils::StartAnimation(
1797 option,
1798 [weak = AceType::WeakClaim(this), reachFinalPosition]() {
1799 auto scroll = weak.Upgrade();
1800 CHECK_NULL_VOID(scroll);
1801 if (reachFinalPosition) {
1802 // ensure that the spring animation is restored to its final position.
1803 scroll->ProcessSpringMotion(scroll->finalPosition_);
1804 // use non final position to stop animation, otherwise the animation cannot be stoped.
1805 scroll->springOffsetProperty_->Set(scroll->finalPosition_ - 1.f);
1806 } else {
1807 // avoid top edge spring can not stop
1808 scroll->springOffsetProperty_->Set(scroll->currentPos_);
1809 }
1810 },
1811 nullptr);
1812 }
1813 currentVelocity_ = 0.0;
1814 }
1815
StopSnapAnimation()1816 void Scrollable::StopSnapAnimation()
1817 {
1818 if (state_ == AnimationState::SNAP) {
1819 ACE_SCOPED_TRACE("StopSnapAnimation, animation state:%d, id:%d, tag:%s", state_, nodeId_, nodeTag_.c_str());
1820 state_ = AnimationState::IDLE;
1821 nextStep_.reset();
1822 CHECK_NULL_VOID(snapOffsetProperty_);
1823 AnimationOption option;
1824 option.SetCurve(Curves::EASE);
1825 option.SetDuration(0);
1826 AnimationUtils::StartAnimation(
1827 option,
1828 [weak = AceType::WeakClaim(this)]() {
1829 auto scroll = weak.Upgrade();
1830 CHECK_NULL_VOID(scroll);
1831 auto position = NearEqual(scroll->currentPos_, scroll->finalPosition_) ?
1832 scroll->finalPosition_ - 1.f : scroll->currentPos_;
1833 scroll->snapOffsetProperty_->Set(position);
1834 },
1835 nullptr);
1836 }
1837 }
1838
StopAxisAnimation()1839 void Scrollable::StopAxisAnimation()
1840 {
1841 CHECK_NULL_VOID(axisAnimator_);
1842 axisAnimator_->StopAxisAnimation();
1843 }
1844
OnCollectTouchTarget(TouchTestResult & result,const RefPtr<FrameNode> & frameNode,const RefPtr<TargetComponent> & targetComponent,ResponseLinkResult & responseLinkResult)1845 void Scrollable::OnCollectTouchTarget(TouchTestResult& result, const RefPtr<FrameNode>& frameNode,
1846 const RefPtr<TargetComponent>& targetComponent, ResponseLinkResult& responseLinkResult)
1847 {
1848 if (panRecognizerNG_) {
1849 panRecognizerNG_->SetNodeId(frameNode->GetId());
1850 panRecognizerNG_->AttachFrameNode(frameNode);
1851 panRecognizerNG_->SetTargetComponent(targetComponent);
1852 panRecognizerNG_->SetIsSystemGesture(true);
1853 panRecognizerNG_->SetRecognizerType(GestureTypeName::PAN_GESTURE);
1854 result.emplace_back(panRecognizerNG_);
1855 responseLinkResult.emplace_back(panRecognizerNG_);
1856 }
1857 }
1858
SetMaxFlingVelocity(double max)1859 void Scrollable::SetMaxFlingVelocity(double max)
1860 {
1861 double density = PipelineBase::GetCurrentDensity();
1862 maxFlingVelocity_ = max * density;
1863 }
1864 } // namespace OHOS::Ace::NG
1865