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 <chrono>
19
20 #include "base/log/ace_trace.h"
21 #include "base/log/frame_report.h"
22 #include "base/log/jank_frame_report.h"
23 #include "base/log/log.h"
24 #include "base/perfmonitor/perf_constants.h"
25 #include "base/perfmonitor/perf_monitor.h"
26 #include "base/ressched/ressched_report.h"
27 #include "base/utils/time_util.h"
28 #include "base/utils/utils.h"
29 #include "core/common/container.h"
30 #include "core/common/layout_inspector.h"
31 #include "core/event/ace_events.h"
32 #include "core/pipeline_ng/pipeline_context.h"
33
34 namespace OHOS::Ace::NG {
35 namespace {
36
37 constexpr double CAP_COEFFICIENT = 0.45;
38 constexpr int32_t FIRST_THRESHOLD = 4;
39 constexpr int32_t SECOND_THRESHOLD = 10;
40 constexpr double CAP_FIXED_VALUE = 16.0;
41 constexpr uint32_t DRAG_INTERVAL_TIME = 400;
42 constexpr uint32_t MULTI_FLING_DISTANCE = 125;
43
44 #ifndef WEARABLE_PRODUCT
45 constexpr double FRICTION = 0.6;
46 constexpr double API11_FRICTION = 0.7;
47 constexpr double API12_FRICTION = 0.75;
48 constexpr double SLOW_FRICTION_THRESHOLD = 3000.0;
49 constexpr double SLOW_FRICTION = 1.0;
50 constexpr double VELOCITY_SCALE = 1.0;
51 constexpr double SLOW_VELOCITY_SCALE = 1.2;
52 constexpr double NEW_VELOCITY_SCALE = 1.5;
53 constexpr double ADJUSTABLE_VELOCITY = 3000.0;
54 #else
55 constexpr double DISTANCE_EPSILON = 1.0;
56 constexpr double FRICTION = 0.9;
57 constexpr double VELOCITY_SCALE = 0.8;
58 constexpr double ADJUSTABLE_VELOCITY = 0.0;
59 #endif
60 constexpr float FRICTION_SCALE = -4.2f;
61 constexpr uint32_t CUSTOM_SPRING_ANIMATION_DURATION = 1000;
62 constexpr uint64_t MILLOS_PER_NANO_SECONDS = 1000 * 1000 * 1000;
63 constexpr uint64_t MIN_DIFF_VSYNC = 1000 * 1000; // min is 1ms
64 constexpr float DEFAULT_THRESHOLD = 0.75f;
65 constexpr float DEFAULT_SPRING_RESPONSE = 0.416f;
66 constexpr float DEFAULT_SPRING_DAMP = 0.99f;
67 constexpr uint32_t MAX_VSYNC_DIFF_TIME = 100 * 1000 * 1000; // max 100 ms
68 constexpr float FRICTION_VELOCITY_THRESHOLD = 120.0f;
69 constexpr float SPRING_ACCURACY = 0.1;
70 #ifdef OHOS_PLATFORM
71 constexpr int64_t INCREASE_CPU_TIME_ONCE = 4000000000; // 4s(unit: ns)
72 #endif
73
74 } // namespace
75
76 // Static Functions.
77 std::optional<double> Scrollable::sFriction_ = std::nullopt;
78 std::optional<double> Scrollable::sVelocityScale_ = std::nullopt;
79
SetVelocityScale(double sVelocityScale)80 void Scrollable::SetVelocityScale(double sVelocityScale)
81 {
82 if (LessOrEqual(sVelocityScale, 0.0)) {
83 return;
84 }
85 sVelocityScale_ = sVelocityScale;
86 }
87
GetVelocityScale()88 double Scrollable::GetVelocityScale()
89 {
90 return Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_ELEVEN) ?
91 NEW_VELOCITY_SCALE : VELOCITY_SCALE;
92 }
93
SetFriction(double sFriction)94 void Scrollable::SetFriction(double sFriction)
95 {
96 if (LessOrEqual(sFriction, 0.0)) {
97 return;
98 }
99 sFriction_ = sFriction;
100 }
101
Scrollable()102 Scrollable::Scrollable()
103 {
104 friction_ = Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_ELEVEN) ? API11_FRICTION : FRICTION;
105 friction_ = Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE) ? API12_FRICTION : friction_;
106 velocityScale_ =
107 Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_ELEVEN) ? NEW_VELOCITY_SCALE : VELOCITY_SCALE;
108 }
109
Scrollable(ScrollPositionCallback && callback,Axis axis)110 Scrollable::Scrollable(ScrollPositionCallback&& callback, Axis axis) : callback_(std::move(callback)), axis_(axis)
111 {
112 friction_ = Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_ELEVEN) ? API11_FRICTION : FRICTION;
113 friction_ = Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE) ? API12_FRICTION : friction_;
114 velocityScale_ =
115 Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_ELEVEN) ? NEW_VELOCITY_SCALE : VELOCITY_SCALE;
116 }
117
Scrollable(const ScrollPositionCallback & callback,Axis axis)118 Scrollable::Scrollable(const ScrollPositionCallback& callback, Axis axis) : callback_(callback), axis_(axis)
119 {
120 friction_ = Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_ELEVEN) ? API11_FRICTION : FRICTION;
121 friction_ = Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE) ? API12_FRICTION : friction_;
122 velocityScale_ =
123 Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_ELEVEN) ? NEW_VELOCITY_SCALE : VELOCITY_SCALE;
124 }
125
~Scrollable()126 Scrollable::~Scrollable()
127 {
128 // If animation still runs, force stop it.
129 if (!IsStopped()) {
130 PerfMonitor::GetPerfMonitor()->End(PerfConstants::APP_LIST_FLING, false);
131 AceAsyncTraceEnd(0, (TRAILING_ANIMATION + std::to_string(nodeId_) + std::string(" ") + nodeTag_).c_str());
132 }
133 StopFrictionAnimation();
134 StopSpringAnimation();
135 StopSnapAnimation();
136 }
137
Initialize(PipelineContext * context)138 void Scrollable::Initialize(PipelineContext* context)
139 {
140 auto weakContext = WeakClaim(context);
141 Initialize(weakContext);
142 }
143
Initialize(const WeakPtr<PipelineBase> & context)144 void Scrollable::Initialize(const WeakPtr<PipelineBase>& context)
145 {
146 context_ = context;
147 PanDirection panDirection;
148 if (axis_ == Axis::VERTICAL) {
149 panDirection.type = PanDirection::VERTICAL;
150 } else {
151 panDirection.type = PanDirection::HORIZONTAL;
152 }
153
154 auto actionStart = [weakScroll = AceType::WeakClaim(this)](const GestureEvent& info) {
155 auto scroll = weakScroll.Upgrade();
156 if (scroll) {
157 scroll->isDragging_ = true;
158 scroll->HandleDragStart(info);
159 }
160 };
161
162 auto actionUpdate = [weakScroll = AceType::WeakClaim(this)](const GestureEvent& info) {
163 auto scroll = weakScroll.Upgrade();
164 if (scroll) {
165 scroll->HandleDragUpdate(info);
166 }
167 };
168
169 auto actionEnd = [weakScroll = AceType::WeakClaim(this)](GestureEvent& info) {
170 auto scroll = weakScroll.Upgrade();
171 if (scroll) {
172 scroll->HandleDragEnd(info);
173 if (scroll->panActionEndEvents_.empty()) {
174 scroll->isDragging_ = false;
175 return;
176 }
177 std::for_each(scroll->panActionEndEvents_.begin(), scroll->panActionEndEvents_.end(),
178 [info](GestureEventFunc& event) {
179 auto gestureInfo = info;
180 event(gestureInfo);
181 });
182 scroll->isDragging_ = false;
183 }
184 };
185
186 auto actionCancel = [weakScroll = AceType::WeakClaim(this)]() {
187 auto scroll = weakScroll.Upgrade();
188 if (!scroll) {
189 return;
190 }
191 if (scroll->dragCancelCallback_) {
192 scroll->dragCancelCallback_();
193 }
194 GestureEvent info;
195 scroll->HandleDragEnd(info);
196 if (scroll->panActionEndEvents_.empty()) {
197 scroll->isDragging_ = false;
198 return;
199 }
200 std::for_each(scroll->panActionEndEvents_.begin(), scroll->panActionEndEvents_.end(),
201 [info](GestureEventFunc& event) {
202 auto gestureInfo = info;
203 event(gestureInfo);
204 });
205 scroll->isDragging_ = false;
206 };
207
208 if (Container::IsCurrentUseNewPipeline()) {
209 panRecognizerNG_ = AceType::MakeRefPtr<NG::PanRecognizer>(
210 DEFAULT_PAN_FINGER, panDirection, DEFAULT_PAN_DISTANCE.ConvertToPx());
211 panRecognizerNG_->SetIsAllowMouse(false);
212 panRecognizerNG_->SetOnActionStart(actionStart);
213 panRecognizerNG_->SetOnActionUpdate(actionUpdate);
214 panRecognizerNG_->SetOnActionEnd(actionEnd);
215 panRecognizerNG_->SetOnActionCancel(actionCancel);
216 }
217 available_ = true;
218 }
219
SetAxis(Axis axis)220 void Scrollable::SetAxis(Axis axis)
221 {
222 axis_ = axis;
223 PanDirection panDirection;
224 if (axis_ == Axis::NONE) {
225 panDirection.type = PanDirection::NONE;
226 } else if (axis_ == Axis::VERTICAL) {
227 panDirection.type = PanDirection::VERTICAL;
228 } else {
229 panDirection.type = PanDirection::HORIZONTAL;
230 }
231 if (panRecognizerNG_) {
232 panRecognizerNG_->SetDirection(panDirection);
233 }
234 }
235
HandleTouchDown()236 void Scrollable::HandleTouchDown()
237 {
238 isTouching_ = true;
239 // If animation still runs, first stop it.
240 ACE_SCOPED_TRACE("HandleTouchDown, panDirection:%u, id:%d, tag:%s", GetPanDirection(), nodeId_, nodeTag_.c_str());
241 StopSpringAnimation();
242 if (!isFrictionAnimationStop_) {
243 StopFrictionAnimation();
244 } else if (!isSnapAnimationStop_ || !isSnapScrollAnimationStop_) {
245 StopSnapAnimation();
246 } else {
247 // Resets values.
248 currentPos_ = 0.0;
249 }
250 }
251
HandleTouchUp()252 void Scrollable::HandleTouchUp()
253 {
254 // Two fingers are alternately drag, one finger is released without triggering spring animation.
255 ACE_SCOPED_TRACE("HandleTouchUp, isDragging_:%u, nestedScrolling_:%u id:%d, tag:%s",
256 isDragging_, nestedScrolling_, nodeId_, nodeTag_.c_str());
257 if (isDragging_) {
258 return;
259 }
260 isTouching_ = false;
261 if (nestedScrolling_) {
262 return;
263 }
264 // outBoundaryCallback_ is only set in ScrollablePattern::SetEdgeEffect and when the edge effect is spring
265 if (outBoundaryCallback_ && outBoundaryCallback_()) {
266 if (isSpringAnimationStop_ && scrollOverCallback_) {
267 if (onScrollStartRec_) {
268 onScrollStartRec_(static_cast<float>(axis_));
269 }
270 ProcessScrollOverCallback(0.0);
271 }
272 return;
273 }
274 if (isSnapScrollAnimationStop_ && scrollSnapCallback_) {
275 scrollSnapCallback_(0.0, 0.0);
276 }
277 }
278
HandleTouchCancel()279 void Scrollable::HandleTouchCancel()
280 {
281 isTouching_ = false;
282 ACE_SCOPED_TRACE("HandleTouchCancel, id:%d, tag:%s", nodeId_, nodeTag_.c_str());
283 if (isSpringAnimationStop_ && scrollOverCallback_) {
284 ProcessScrollOverCallback(0.0);
285 }
286 }
287
IsAnimationNotRunning() const288 bool Scrollable::IsAnimationNotRunning() const
289 {
290 return !isTouching_ && isFrictionAnimationStop_ && isSpringAnimationStop_
291 && isSnapAnimationStop_ && isSnapScrollAnimationStop_;
292 }
293
Idle() const294 bool Scrollable::Idle() const
295 {
296 return !isTouching_ && isFrictionAnimationStop_ && isSpringAnimationStop_
297 && isSnapAnimationStop_ && isSnapScrollAnimationStop_ && !nestedScrolling_;
298 }
299
IsStopped() const300 bool Scrollable::IsStopped() const
301 {
302 return isSpringAnimationStop_ && isFrictionAnimationStop_
303 && isSnapAnimationStop_ && isSnapScrollAnimationStop_;
304 }
305
IsSpringStopped() const306 bool Scrollable::IsSpringStopped() const
307 {
308 return isSpringAnimationStop_;
309 }
310
IsSnapStopped() const311 bool Scrollable::IsSnapStopped() const
312 {
313 return isSnapAnimationStop_;
314 }
315
StopScrollable()316 void Scrollable::StopScrollable()
317 {
318 if (!isFrictionAnimationStop_) {
319 StopFrictionAnimation();
320 }
321 if (!isSpringAnimationStop_) {
322 StopSpringAnimation();
323 }
324 if (!isSnapAnimationStop_ || !isSnapScrollAnimationStop_) {
325 StopSnapAnimation();
326 }
327 }
328
HandleScrollEnd(const std::optional<float> & velocity)329 void Scrollable::HandleScrollEnd(const std::optional<float>& velocity)
330 {
331 // priority:
332 // 1. onScrollEndRec_ (would internally call onScrollEnd)
333 // 2. scrollEndCallback_
334 if (onScrollEndRec_) {
335 onScrollEndRec_(velocity);
336 return;
337 }
338 if (scrollEndCallback_) {
339 scrollEndCallback_();
340 }
341 }
342
HandleDragStart(const OHOS::Ace::GestureEvent & info)343 void Scrollable::HandleDragStart(const OHOS::Ace::GestureEvent& info)
344 {
345 ACE_SCOPED_TRACE("HandleDragStart, id:%d, tag:%s", nodeId_, nodeTag_.c_str());
346 if (info.GetSourceTool() == SourceTool::TOUCHPAD) {
347 HandleTouchDown();
348 }
349 currentVelocity_ = info.GetMainVelocity();
350 if (dragFRCSceneCallback_) {
351 dragFRCSceneCallback_(currentVelocity_, NG::SceneStatus::START);
352 }
353 if (continuousDragStatus_) {
354 IncreaseContinueDragCount();
355 task_.Cancel();
356 }
357 SetDragStartPosition(GetMainOffset(Offset(info.GetGlobalPoint().GetX(), info.GetGlobalPoint().GetY())));
358 const double dragPositionInMainAxis =
359 axis_ == Axis::VERTICAL ? info.GetGlobalLocation().GetY() : info.GetGlobalLocation().GetX();
360 TAG_LOGI(AceLogTag::ACE_SCROLLABLE, "Scroll drag start, id:%{public}d, tag:%{public}s", nodeId_, nodeTag_.c_str());
361
362 skipRestartSpring_ = false; // reset flags. Extract method if more flags need to be reset
363
364 #ifdef OHOS_PLATFORM
365 // Increase the cpu frequency when sliding start.
366 auto currentTime = GetSysTimestamp();
367 auto increaseCpuTime = currentTime - startIncreaseTime_;
368 if (!moved_ || increaseCpuTime >= INCREASE_CPU_TIME_ONCE) {
369 startIncreaseTime_ = currentTime;
370 if (FrameReport::GetInstance().GetEnable()) {
371 FrameReport::GetInstance().BeginListFling();
372 }
373 }
374 #endif
375 JankFrameReport::GetInstance().SetFrameJankFlag(JANK_RUNNING_SCROLL);
376 if (onScrollStartRec_) {
377 onScrollStartRec_(static_cast<float>(dragPositionInMainAxis));
378 }
379 }
380
HandleScroll(double offset,int32_t source,NestedState state)381 ScrollResult Scrollable::HandleScroll(double offset, int32_t source, NestedState state)
382 {
383 if (!handleScrollCallback_) {
384 ExecuteScrollBegin(offset);
385 canOverScroll_ = false;
386 moved_ = UpdateScrollPosition(offset, source);
387 return { 0, false };
388 }
389 // call NestableScrollContainer::HandleScroll
390 return handleScrollCallback_(static_cast<float>(offset), source, state);
391 }
392
HandleDragUpdate(const GestureEvent & info)393 void Scrollable::HandleDragUpdate(const GestureEvent& info)
394 {
395 currentVelocity_ = info.GetMainVelocity();
396 if (dragFRCSceneCallback_) {
397 dragFRCSceneCallback_(currentVelocity_, NG::SceneStatus::RUNNING);
398 }
399 if (!NearZero(info.GetMainVelocity()) && dragCount_ >= FIRST_THRESHOLD) {
400 if (Negative(lastVelocity_ / info.GetMainVelocity())) {
401 ResetContinueDragCount();
402 }
403 }
404 if (!isSpringAnimationStop_ || !isFrictionAnimationStop_ ||
405 !isSnapAnimationStop_ || !isSnapScrollAnimationStop_) {
406 // If animation still runs, first stop it.
407 isDragUpdateStop_ = true;
408 StopFrictionAnimation();
409 StopSpringAnimation();
410 StopSnapAnimation();
411 currentPos_ = 0.0;
412 }
413 #ifdef OHOS_PLATFORM
414 // Handle the case where you keep sliding past limit time(4s).
415 auto currentTime = GetSysTimestamp();
416 auto increaseCpuTime = currentTime - startIncreaseTime_;
417 if (increaseCpuTime >= INCREASE_CPU_TIME_ONCE) {
418 startIncreaseTime_ = currentTime;
419 if (FrameReport::GetInstance().GetEnable()) {
420 FrameReport::GetInstance().BeginListFling();
421 }
422 }
423 #endif
424 auto mainDelta = info.GetMainDelta();
425 lastMainDelta_ = mainDelta;
426 auto source = IsMouseWheelScroll(info) ? SCROLL_FROM_AXIS : SCROLL_FROM_UPDATE;
427 ACE_SCOPED_TRACE(
428 "HandleDragUpdate, mainDelta:%f, source:%d, id:%d, tag:%s", mainDelta, source, nodeId_, nodeTag_.c_str());
429 if (isReverseCallback_ && isReverseCallback_()) {
430 mainDelta = Round(-mainDelta);
431 } else {
432 mainDelta = Round(mainDelta);
433 }
434 JankFrameReport::GetInstance().RecordFrameUpdate();
435 HandleScroll(mainDelta, source, NestedState::GESTURE);
436 }
437
LayoutDirectionEst(double & correctVelocity)438 void Scrollable::LayoutDirectionEst(double& correctVelocity)
439 {
440 auto defaultVelocityScale = isSlow_ ? SLOW_VELOCITY_SCALE : velocityScale_;
441 if (isReverseCallback_ && isReverseCallback_()) {
442 correctVelocity = -correctVelocity * sVelocityScale_.value_or(defaultVelocityScale) * GetGain(GetDragOffset());
443 } else {
444 correctVelocity = correctVelocity * sVelocityScale_.value_or(defaultVelocityScale) * GetGain(GetDragOffset());
445 }
446 }
447
HandleDragEnd(const GestureEvent & info)448 void Scrollable::HandleDragEnd(const GestureEvent& info)
449 {
450 // avoid no render frame when drag end
451 if (NearZero(info.GetMainDelta())) {
452 auto tempInfo = info;
453 tempInfo.SetMainDelta(lastMainDelta_);
454 HandleDragUpdate(tempInfo);
455 } else {
456 HandleDragUpdate(info);
457 }
458 TAG_LOGI(AceLogTag::ACE_SCROLLABLE, "Scroll drag end, velocity is %{public}f id:%{public}d, tag:%{public}s",
459 info.GetMainVelocity(), nodeId_, nodeTag_.c_str());
460 if (dragFRCSceneCallback_) {
461 dragFRCSceneCallback_(info.GetMainVelocity(), NG::SceneStatus::END);
462 }
463 isDragUpdateStop_ = false;
464 touchUp_ = false;
465 scrollPause_ = false;
466 lastVelocity_ = GetPanDirection() == Axis::NONE ? 0.0 : info.GetMainVelocity();
467 double gestureVelocity = GetPanDirection() == Axis::NONE ? 0.0 : info.GetMainVelocity();
468 isSlow_ = LessNotEqual(std::abs(gestureVelocity), SLOW_FRICTION_THRESHOLD);
469 SetDragEndPosition(GetMainOffset(Offset(info.GetGlobalPoint().GetX(), info.GetGlobalPoint().GetY())));
470 LayoutDirectionEst(gestureVelocity);
471 // Apply max fling velocity limit, it must be calculated after all fling velocity gain.
472 currentVelocity_ = std::clamp(gestureVelocity, -maxFlingVelocity_ + slipFactor_, maxFlingVelocity_ - slipFactor_);
473
474 lastPos_ = GetDragOffset();
475 JankFrameReport::GetInstance().ClearFrameJankFlag(JANK_RUNNING_SCROLL);
476 double mainPosition = GetMainOffset(Offset(info.GetGlobalPoint().GetX(), info.GetGlobalPoint().GetY()));
477 mainPosition = Round(mainPosition);
478 ACE_SCOPED_TRACE(
479 "HandleDragEnd, mainPosition:%f, getureDelta:%lf, gestureVelocity:%lf, currentVelocity:%f, moved_:%u "
480 "canOverScroll_:%u, id:%d, tag:%s",
481 mainPosition, info.GetMainDelta(), gestureVelocity, currentVelocity_, moved_, canOverScroll_, nodeId_,
482 nodeTag_.c_str());
483 if (!moved_ || IsMouseWheelScroll(info)) {
484 ResetContinueDragCount();
485 if (calePredictSnapOffsetCallback_) {
486 std::optional<float> predictSnapOffset = calePredictSnapOffsetCallback_(0.0f, 0.0f, 0.0f);
487 if (predictSnapOffset.has_value() && !NearZero(predictSnapOffset.value())) {
488 currentPos_ = mainPosition;
489 ProcessScrollSnapSpringMotion(predictSnapOffset.value(), currentVelocity_);
490 isTouching_ = false;
491 return;
492 }
493 }
494 HandleScrollEnd(currentVelocity_);
495 currentVelocity_ = 0.0;
496 #ifdef OHOS_PLATFORM
497 if (FrameReport::GetInstance().GetEnable()) {
498 FrameReport::GetInstance().EndListFling();
499 }
500 #endif
501 } else if (!Container::IsCurrentUseNewPipeline() && outBoundaryCallback_ && outBoundaryCallback_() &&
502 scrollOverCallback_) {
503 ResetContinueDragCount();
504 ProcessScrollOverCallback(currentVelocity_);
505 } else if (canOverScroll_) {
506 ResetContinueDragCount();
507 HandleOverScroll(currentVelocity_);
508 } else {
509 StartScrollAnimation(mainPosition, currentVelocity_);
510 }
511 SetDelayedTask();
512 if (dragEndCallback_) {
513 dragEndCallback_();
514 }
515 isSlow_ = false;
516 isTouching_ = false;
517 }
518
StartScrollAnimation(float mainPosition,float correctVelocity)519 void Scrollable::StartScrollAnimation(float mainPosition, float correctVelocity)
520 {
521 if (!isSpringAnimationStop_) {
522 StopSpringAnimation();
523 }
524 if (!frictionOffsetProperty_) {
525 GetFrictionProperty();
526 }
527 StopSnapController();
528 TAG_LOGD(AceLogTag::ACE_SCROLLABLE, "The position of scroll motion is %{public}f, velocity is %{public}f",
529 mainPosition, correctVelocity);
530 auto defaultFriction = isSlow_ ? SLOW_FRICTION : friction_;
531 float friction = sFriction_.value_or(defaultFriction);
532 initVelocity_ = correctVelocity;
533 finalPosition_ = mainPosition + correctVelocity / (friction * -FRICTION_SCALE);
534
535 if (calePredictSnapOffsetCallback_) {
536 std::optional<float> predictSnapOffset =
537 calePredictSnapOffsetCallback_(GetFinalPosition() - mainPosition, GetDragOffset(), correctVelocity);
538 if (predictSnapOffset.has_value() && !NearZero(predictSnapOffset.value())) {
539 currentPos_ = mainPosition;
540 ProcessScrollSnapSpringMotion(predictSnapOffset.value(), correctVelocity);
541 return;
542 }
543 }
544 if (scrollSnapCallback_ && scrollSnapCallback_(GetFinalPosition() - mainPosition, correctVelocity)) {
545 currentVelocity_ = 0.0;
546 return;
547 }
548 if (NearZero(correctVelocity, FRICTION_VELOCITY_THRESHOLD)) {
549 HandleScrollEnd(correctVelocity);
550 currentVelocity_ = 0.0;
551 #ifdef OHOS_PLATFORM
552 if (FrameReport::GetInstance().GetEnable()) {
553 FrameReport::GetInstance().EndListFling();
554 }
555 #endif
556 return;
557 }
558 // change motion param when list item need to be center of screen on watch
559 FixScrollMotion(mainPosition, correctVelocity);
560 // Resets values.
561 currentPos_ = mainPosition;
562 currentVelocity_ = 0.0;
563 lastPosition_ = currentPos_;
564 frictionVelocity_ = initVelocity_;
565 frictionOffsetProperty_->Set(mainPosition);
566 float response = fabs(2 * M_PI / (FRICTION_SCALE * friction));
567 auto curve = AceType::MakeRefPtr<ResponsiveSpringMotion>(response, 1.0f, 0.0f);
568 AnimationOption option;
569 option.SetCurve(curve);
570 option.SetDuration(CUSTOM_SPRING_ANIMATION_DURATION);
571 option.SetFinishCallbackType(FinishCallbackType::LOGICALLY);
572 frictionOffsetProperty_->SetThresholdType(ThresholdType::LAYOUT);
573 frictionOffsetProperty_->SetPropertyUnit(PropertyUnit::PIXEL_POSITION);
574 ACE_SCOPED_TRACE("Scrollable friction animation start, start:%f, end:%f, vel:%f, id:%d, tag:%s", mainPosition,
575 finalPosition_, initVelocity_, nodeId_, nodeTag_.c_str());
576 frictionOffsetProperty_->AnimateWithVelocity(
577 option, finalPosition_, initVelocity_, [weak = AceType::WeakClaim(this), id = Container::CurrentId()]() {
578 ContainerScope scope(id);
579 auto scroll = weak.Upgrade();
580 CHECK_NULL_VOID(scroll);
581 scroll->isFrictionAnimationStop_ = true;
582 ACE_SCOPED_TRACE(
583 "Scrollable friction animation finish, id:%d, tag:%s", scroll->nodeId_, scroll->nodeTag_.c_str());
584 scroll->ProcessScrollMotionStop(true);
585 });
586 isFrictionAnimationStop_ = false;
587 auto context = context_.Upgrade();
588 CHECK_NULL_VOID(context);
589 context->RequestFrame();
590 lastVsyncTime_ = context->GetVsyncTime();
591 }
592
SetDelayedTask()593 void Scrollable::SetDelayedTask()
594 {
595 SetContinuousDragStatus(true);
596 auto context = OHOS::Ace::PipelineContext::GetCurrentContext();
597 CHECK_NULL_VOID(context);
598 auto taskExecutor = SingleTaskExecutor::Make(context->GetTaskExecutor(), TaskExecutor::TaskType::UI);
599 task_.Reset([weak = WeakClaim(this)] {
600 auto drag = weak.Upgrade();
601 if (drag) {
602 drag->ResetContinueDragCount();
603 drag->SetContinuousDragStatus(false);
604 }
605 });
606 taskExecutor.PostDelayedTask(task_, DRAG_INTERVAL_TIME, "ArkUIScrollDragInterval");
607 }
608
MarkNeedFlushAnimationStartTime()609 void Scrollable::MarkNeedFlushAnimationStartTime()
610 {
611 auto context = OHOS::Ace::NG::PipelineContext::GetCurrentContext();
612 CHECK_NULL_VOID(context);
613 context->MarkNeedFlushAnimationStartTime();
614 }
615
ComputeCap(int dragCount)616 double Scrollable::ComputeCap(int dragCount)
617 {
618 if (dragCount < FIRST_THRESHOLD) {
619 return 1.0;
620 }
621 auto cap = ComputeCap(dragCount - 1) + CAP_COEFFICIENT * (dragCount - 1);
622 return cap;
623 }
624
GetGain(double delta)625 double Scrollable::GetGain(double delta)
626 {
627 auto cap = 1.0;
628 auto gain = 1.0;
629 if (std::abs(delta) < MULTI_FLING_DISTANCE) {
630 ResetContinueDragCount();
631 preGain_ = gain;
632 return gain;
633 }
634 if (!continuousSlidingCallback_) {
635 preGain_ = gain;
636 return gain;
637 }
638 auto screenHeight = continuousSlidingCallback_();
639 if (delta == 0 || screenHeight == 0) {
640 preGain_ = gain;
641 return gain;
642 }
643 if (dragCount_ >= FIRST_THRESHOLD && dragCount_ < SECOND_THRESHOLD) {
644 if (Negative(lastPos_ / delta)) {
645 ResetContinueDragCount();
646 preGain_ = gain;
647 return gain;
648 }
649 cap = CAP_COEFFICIENT * (dragCount_ - 1);
650 gain = (LessNotEqual(cap, std::abs(delta) / screenHeight * (dragCount_ - 1))) ? preGain_ + cap :
651 preGain_ + std::abs(delta) / screenHeight * (dragCount_ - 1);
652 } else if (dragCount_ >= SECOND_THRESHOLD) {
653 if (Negative(lastPos_ / delta)) {
654 ResetContinueDragCount();
655 preGain_ = gain;
656 return gain;
657 }
658 cap = CAP_FIXED_VALUE;
659 gain = (LessNotEqual(cap, preGain_ + std::abs(delta) / screenHeight * (dragCount_ - 1))) ? cap :
660 preGain_ + std::abs(delta) / screenHeight * (dragCount_ - 1);
661 }
662 preGain_ = gain;
663 return gain;
664 }
665
ExecuteScrollBegin(double & mainDelta)666 void Scrollable::ExecuteScrollBegin(double& mainDelta)
667 {
668 auto context = context_.Upgrade();
669 if (!scrollBeginCallback_ || !context) {
670 return;
671 }
672
673 ScrollInfo scrollInfo;
674 if (axis_ == Axis::VERTICAL) {
675 scrollInfo = scrollBeginCallback_(0.0_vp, Dimension(mainDelta / context->GetDipScale(), DimensionUnit::VP));
676 mainDelta = context->NormalizeToPx(scrollInfo.dy);
677 } else if (axis_ == Axis::HORIZONTAL) {
678 scrollInfo = scrollBeginCallback_(Dimension(mainDelta / context->GetDipScale(), DimensionUnit::VP), 0.0_vp);
679 mainDelta = context->NormalizeToPx(scrollInfo.dx);
680 }
681 }
682
GetFrictionVelocityByFinalPosition(float final,float position,float friction,float signum,float threshold)683 float Scrollable::GetFrictionVelocityByFinalPosition(float final, float position, float friction,
684 float signum, float threshold)
685 {
686 return DEFAULT_THRESHOLD * threshold * signum - (final - position) * friction;
687 }
688
FixScrollMotion(float position,float initVelocity)689 void Scrollable::FixScrollMotion(float position, float initVelocity)
690 {
691 #ifdef WEARABLE_PRODUCT
692 float signum = 0.0;
693 if (!NearZero(initVelocity)) {
694 signum = GreatNotEqual(initVelocity, 0.0) ? 1.0 : -1.0;
695 }
696 if (frictionOffsetProperty_ && needCenterFix_ && watchFixCallback_) {
697 float finalPosition = watchFixCallback_(GetFinalPosition(), position);
698 if (!NearEqual(finalPosition, GetFinalPosition(), DISTANCE_EPSILON)) {
699 float friction = sFriction_.value_or(friction_);
700 float velocity = GetFrictionVelocityByFinalPosition(finalPosition, position, friction, signum);
701
702 // fix again when velocity is less than velocity threshold
703 if (!NearEqual(finalPosition, GetFinalPosition(), DISTANCE_EPSILON)) {
704 velocity = GetFrictionVelocityByFinalPosition(finalPosition, position, friction, signum, 0.0f);
705 }
706 initVelocity_ = velocity;
707 finalPosition_ = mainPosition + initVelocity_ / (friction * -FRICTION_SCALE);
708 }
709 }
710 #endif
711 }
712
StartScrollSnapMotion(float predictSnapOffset,float scrollSnapVelocity)713 void Scrollable::StartScrollSnapMotion(float predictSnapOffset, float scrollSnapVelocity)
714 {
715 endPos_ = currentPos_ + predictSnapOffset;
716 AnimationOption option;
717 option.SetDuration(CUSTOM_SPRING_ANIMATION_DURATION);
718 auto curve = AceType::MakeRefPtr<ResponsiveSpringMotion>(DEFAULT_SPRING_RESPONSE, DEFAULT_SPRING_DAMP, 0.0f);
719 option.SetCurve(curve);
720 if (!snapOffsetProperty_) {
721 GetSnapProperty();
722 }
723 snapOffsetProperty_->Set(currentPos_);
724 snapOffsetProperty_->SetPropertyUnit(PropertyUnit::PIXEL_POSITION);
725 ACE_SCOPED_TRACE("List snap animation start, start:%f, end:%f, vel:%f, id:%d", currentPos_, endPos_,
726 scrollSnapVelocity, nodeId_);
727 updateSnapAnimationCount_++;
728 snapOffsetProperty_->AnimateWithVelocity(option, endPos_, scrollSnapVelocity,
729 [weak = AceType::WeakClaim(this), id = Container::CurrentId()]() {
730 ContainerScope scope(id);
731 auto scroll = weak.Upgrade();
732 CHECK_NULL_VOID(scroll);
733 ACE_SCOPED_TRACE("List snap animation finish, id:%d", scroll->nodeId_);
734 scroll->updateSnapAnimationCount_--;
735 if (scroll->updateSnapAnimationCount_ == 0) {
736 scroll->isSnapScrollAnimationStop_ = true;
737 scroll->ProcessScrollSnapStop();
738 }
739 });
740 isSnapScrollAnimationStop_ = false;
741 auto context = context_.Upgrade();
742 CHECK_NULL_VOID(context);
743 lastVsyncTime_ = context->GetVsyncTime();
744 MarkNeedFlushAnimationStartTime();
745 }
746
ProcessScrollSnapSpringMotion(float scrollSnapDelta,float scrollSnapVelocity)747 void Scrollable::ProcessScrollSnapSpringMotion(float scrollSnapDelta, float scrollSnapVelocity)
748 {
749 TAG_LOGD(AceLogTag::ACE_SCROLLABLE, "The snap delta of scroll motion is %{public}f, "
750 "The snap velocity of scroll motion is %{public}f",
751 scrollSnapDelta, scrollSnapVelocity);
752 endPos_ = currentPos_ + scrollSnapDelta;
753 ACE_SCOPED_TRACE("Scroll snap animation start, start:%f, end:%f, vel:%f, id:%d", currentPos_, endPos_,
754 scrollSnapVelocity, nodeId_);
755 AnimationOption option;
756 option.SetDuration(CUSTOM_SPRING_ANIMATION_DURATION);
757 auto curve = AceType::MakeRefPtr<ResponsiveSpringMotion>(DEFAULT_SPRING_RESPONSE, DEFAULT_SPRING_DAMP, 0.0f);
758 option.SetCurve(curve);
759 if (!snapOffsetProperty_) {
760 GetSnapProperty();
761 }
762 snapOffsetProperty_->Set(currentPos_);
763 snapOffsetProperty_->SetPropertyUnit(PropertyUnit::PIXEL_POSITION);
764 snapOffsetProperty_->AnimateWithVelocity(option, endPos_, scrollSnapVelocity,
765 [weak = AceType::WeakClaim(this), id = Container::CurrentId()]() {
766 ContainerScope scope(id);
767 auto scroll = weak.Upgrade();
768 CHECK_NULL_VOID(scroll);
769 scroll->isSnapAnimationStop_ = true;
770 ACE_SCOPED_TRACE("Scroll snap animation finish, id:%d", scroll->nodeId_);
771 scroll->ProcessScrollMotionStop(false);
772 });
773 isSnapAnimationStop_ = false;
774 auto context = context_.Upgrade();
775 CHECK_NULL_VOID(context);
776 lastVsyncTime_ = context->GetVsyncTime();
777 }
778
UpdateScrollSnapStartOffset(double offset)779 void Scrollable::UpdateScrollSnapStartOffset(double offset)
780 {
781 UpdateScrollSnapEndWithOffset(offset);
782 }
783
ProcessScrollSnapMotion(double position)784 void Scrollable::ProcessScrollSnapMotion(double position)
785 {
786 TAG_LOGD(AceLogTag::ACE_SCROLLABLE, "Current Pos is %{public}f, position is %{public}f",
787 currentPos_, position);
788 currentVelocity_ = snapVelocity_;
789 if (NearEqual(currentPos_, position)) {
790 UpdateScrollPosition(0.0, SCROLL_FROM_ANIMATION);
791 } else {
792 auto mainDelta = position - currentPos_;
793 HandleScroll(mainDelta, SCROLL_FROM_ANIMATION, NestedState::GESTURE);
794 if (!moved_ && !isSnapScrollAnimationStop_) {
795 StopSnapAnimation();
796 } else if (!touchUp_) {
797 if (scrollTouchUpCallback_) {
798 scrollTouchUpCallback_();
799 }
800 touchUp_ = true;
801 }
802 }
803 currentPos_ = position;
804 if (outBoundaryCallback_ && outBoundaryCallback_() && !isSnapScrollAnimationStop_) {
805 scrollPause_ = true;
806 skipRestartSpring_ = true;
807 MarkNeedFlushAnimationStartTime();
808 StopSnapAnimation();
809 }
810 }
811
ProcessScrollSnapStop()812 void Scrollable::ProcessScrollSnapStop()
813 {
814 if (scrollPause_) {
815 scrollPause_ = false;
816 HandleOverScroll(currentVelocity_);
817 } else {
818 OnAnimateStop();
819 }
820 }
821
OnAnimateStop()822 void Scrollable::OnAnimateStop()
823 {
824 HandleScrollEnd(std::nullopt);
825 currentVelocity_ = 0.0;
826 if (isTouching_ || isDragUpdateStop_) {
827 return;
828 }
829 moved_ = false;
830 #ifdef OHOS_PLATFORM
831 if (FrameReport::GetInstance().GetEnable()) {
832 FrameReport::GetInstance().EndListFling();
833 }
834 #endif
835 if (scrollEnd_) {
836 scrollEnd_();
837 }
838 #if !defined(PREVIEW)
839 LayoutInspector::SupportInspector();
840 #endif
841 }
842
StartSpringMotion(double mainPosition,double mainVelocity,const ExtentPair & extent,const ExtentPair & initExtent)843 void Scrollable::StartSpringMotion(
844 double mainPosition, double mainVelocity, const ExtentPair& extent, const ExtentPair& initExtent)
845 {
846 TAG_LOGD(AceLogTag::ACE_SCROLLABLE, "position is %{public}f, mainVelocity is %{public}f, minExtent is "
847 "%{public}f, maxExtent is %{public}f, initMinExtent is %{public}f, initMaxExtent is %{public}f",
848 mainPosition, mainVelocity, extent.Leading(), extent.Trailing(), initExtent.Leading(), initExtent.Trailing());
849 if (!isSpringAnimationStop_ || (skipRestartSpring_ && NearEqual(mainVelocity, 0.0f, 0.001f))) {
850 return;
851 }
852 currentPos_ = mainPosition;
853 if (mainPosition > initExtent.Trailing() || NearEqual(mainPosition, initExtent.Trailing(), 0.01f)) {
854 finalPosition_ = extent.Trailing();
855 } else if (mainPosition < initExtent.Leading() || NearEqual(mainPosition, initExtent.Leading(), 0.01f)) {
856 finalPosition_ = extent.Leading();
857 } else {
858 return;
859 }
860
861 if (!springOffsetProperty_) {
862 GetSpringProperty();
863 }
864 springAnimationCount_++;
865 springOffsetProperty_->Set(mainPosition);
866 AnimationOption option;
867 auto curve = AceType::MakeRefPtr<ResponsiveSpringMotion>(DEFAULT_SPRING_RESPONSE, DEFAULT_SPRING_DAMP, 0.0f);
868 option.SetCurve(curve);
869 option.SetDuration(CUSTOM_SPRING_ANIMATION_DURATION);
870 springOffsetProperty_->SetPropertyUnit(PropertyUnit::PIXEL_POSITION);
871 ACE_SCOPED_TRACE("Scrollable spring animation start, start:%f, end:%f, vel:%f, id:%d, tag:%s", mainPosition,
872 finalPosition_, mainVelocity, nodeId_, nodeTag_.c_str());
873 lastVsyncTime_ = static_cast<uint64_t>(GetSysTimestamp());
874 springOffsetProperty_->AnimateWithVelocity(
875 option, finalPosition_, mainVelocity, [weak = AceType::WeakClaim(this), id = Container::CurrentId()]() {
876 ContainerScope scope(id);
877 auto scroll = weak.Upgrade();
878 CHECK_NULL_VOID(scroll);
879 scroll->springAnimationCount_--;
880 ACE_SCOPED_TRACE(
881 "Scrollable spring animation finish, id:%d, tag:%s", scroll->nodeId_, scroll->nodeTag_.c_str());
882 // avoid current animation being interrupted by the prev animation's finish callback
883 // and triggering onScrollStop when spring animation turns to friction animation.
884 if (scroll->springAnimationCount_ > 0 || scroll->scrollPause_) {
885 return;
886 }
887 scroll->isSpringAnimationStop_ = true;
888 scroll->currentVelocity_ = 0.0;
889 scroll->OnAnimateStop();
890 });
891 isSpringAnimationStop_ = false;
892 skipRestartSpring_ = false;
893 auto context = context_.Upgrade();
894 CHECK_NULL_VOID(context);
895 context->RequestFrame();
896 }
897
UpdateSpringMotion(double mainPosition,const ExtentPair & extent,const ExtentPair & initExtent)898 void Scrollable::UpdateSpringMotion(
899 double mainPosition, const ExtentPair& extent, const ExtentPair& initExtent)
900 {
901 TAG_LOGD(AceLogTag::ACE_SCROLLABLE, "position is %{public}f, minExtent is "
902 "%{public}f, maxExtent is %{public}f, initMinExtent is %{public}f, initMaxExtent is %{public}f",
903 mainPosition, extent.Leading(), extent.Trailing(), initExtent.Leading(), initExtent.Trailing());
904 if (isSpringAnimationStop_) {
905 return;
906 }
907 float finalPosition = 0.0f;
908 if (mainPosition > initExtent.Trailing() || NearEqual(mainPosition, initExtent.Trailing())) {
909 finalPosition = extent.Trailing();
910 } else if (mainPosition < initExtent.Leading() || NearEqual(mainPosition, initExtent.Leading())) {
911 finalPosition = extent.Leading();
912 } else {
913 return;
914 }
915
916 finalPosition = finalPosition_ + (finalPosition - mainPosition) - (finalPosition_ - currentPos_);
917 if (NearEqual(finalPosition, finalPosition_, SPRING_ACCURACY)) {
918 return;
919 }
920 finalPosition_ = finalPosition;
921 springAnimationCount_++;
922 AnimationOption option;
923 auto curve = AceType::MakeRefPtr<ResponsiveSpringMotion>(DEFAULT_SPRING_RESPONSE, DEFAULT_SPRING_DAMP, 0.0f);
924 option.SetCurve(curve);
925 option.SetDuration(CUSTOM_SPRING_ANIMATION_DURATION);
926 springOffsetProperty_->SetPropertyUnit(PropertyUnit::PIXEL_POSITION);
927 ACE_SCOPED_TRACE("Scrollable spring animation update, start:%f, end:%f, id:%d, tag:%s", mainPosition,
928 finalPosition_, nodeId_, nodeTag_.c_str());
929 AnimationUtils::StartAnimation(
930 option,
931 [weak = AceType::WeakClaim(this)]() {
932 auto scroll = weak.Upgrade();
933 CHECK_NULL_VOID(scroll);
934 scroll->springOffsetProperty_->Set(scroll->finalPosition_);
935 scroll->isSpringAnimationStop_ = false;
936 },
937 [weak = AceType::WeakClaim(this), id = Container::CurrentId()]() {
938 ContainerScope scope(id);
939 auto scroll = weak.Upgrade();
940 CHECK_NULL_VOID(scroll);
941 scroll->springAnimationCount_--;
942 // avoid current animation being interrupted by the prev animation's finish callback
943 if (scroll->springAnimationCount_ > 0) {
944 return;
945 }
946 ACE_SCOPED_TRACE(
947 "Scrollable updated spring animation finish, id:%d, tag:%s", scroll->nodeId_, scroll->nodeTag_.c_str());
948 scroll->isSpringAnimationStop_ = true;
949 scroll->currentVelocity_ = 0.0;
950 scroll->OnAnimateStop();
951 });
952 isSpringAnimationStop_ = false;
953 skipRestartSpring_ = false;
954 }
955
ProcessScrollMotionStop(bool stopFriction)956 void Scrollable::ProcessScrollMotionStop(bool stopFriction)
957 {
958 if (needScrollSnapChange_ && calePredictSnapOffsetCallback_ && frictionOffsetProperty_) {
959 needScrollSnapChange_ = false;
960 auto predictSnapOffset = calePredictSnapOffsetCallback_(GetFinalPosition() - currentPos_, 0.0f, 0.0f);
961 if (predictSnapOffset.has_value() && !NearZero(predictSnapOffset.value())) {
962 ProcessScrollSnapSpringMotion(predictSnapOffset.value(), currentVelocity_);
963 return;
964 }
965 }
966 // spring effect special process
967 if (scrollPause_) {
968 scrollPause_ = false;
969 HandleOverScroll(currentVelocity_);
970 } else {
971 if (isDragUpdateStop_) {
972 return;
973 }
974 moved_ = false;
975 HandleScrollEnd(std::nullopt);
976 #ifdef OHOS_PLATFORM
977 if (FrameReport::GetInstance().GetEnable()) {
978 FrameReport::GetInstance().EndListFling();
979 }
980 #endif
981 if (scrollEnd_) {
982 scrollEnd_();
983 }
984 currentVelocity_ = 0.0;
985 #if !defined(PREVIEW)
986 LayoutInspector::SupportInspector();
987 #endif
988 }
989 }
990
ProcessSpringMotion(double position)991 void Scrollable::ProcessSpringMotion(double position)
992 {
993 TAG_LOGD(AceLogTag::ACE_SCROLLABLE, "Current Pos is %{public}f, position is %{public}f",
994 currentPos_, position);
995 auto context = OHOS::Ace::PipelineContext::GetCurrentContext();
996 CHECK_NULL_VOID(context);
997 uint64_t currentVsync = context->GetVsyncTime();
998 uint64_t diff = currentVsync - lastVsyncTime_;
999 if (diff < MAX_VSYNC_DIFF_TIME && diff > MIN_DIFF_VSYNC) {
1000 currentVelocity_ = (position - currentPos_) / diff * MILLOS_PER_NANO_SECONDS;
1001 }
1002 lastVsyncTime_ = currentVsync;
1003 if (LessOrEqual(std::abs(currentPos_ - position), 1)) {
1004 // trace stop at OnScrollStop
1005 if (!isFadingAway_) {
1006 AceAsyncTraceBegin(0, (TRAILING_ANIMATION + std::to_string(nodeId_) + std::string(" ") + nodeTag_).c_str());
1007 }
1008 }
1009 auto distance = currentPos_ - finalPosition_;
1010 auto nextDistance = position - finalPosition_;
1011 isFadingAway_ = GreatNotEqual(std::abs(nextDistance), std::abs(distance));
1012 auto delta = position - currentPos_;
1013 if (distance * nextDistance < 0) {
1014 double currentVelocity = currentVelocity_;
1015 scrollPause_ = true;
1016 MarkNeedFlushAnimationStartTime();
1017 StopSpringAnimation();
1018 ACE_SCOPED_TRACE("change direction in spring animation and start fling animation, distance:%f, "
1019 "nextDistance:%f, nodeId:%d, tag:%s",
1020 distance, nextDistance, nodeId_, nodeTag_.c_str());
1021 // only handle offsets that are out of bounds
1022 delta = finalPosition_ - currentPos_;
1023 // remainVelocityCallback_ will pass the velocity to the child component
1024 if (!remainVelocityCallback_ || !remainVelocityCallback_(currentVelocity)) {
1025 StartScrollAnimation(position, currentVelocity);
1026 }
1027 }
1028 moved_ = UpdateScrollPosition(delta, SCROLL_FROM_ANIMATION_SPRING);
1029 if (!moved_) {
1030 StopSpringAnimation();
1031 } else if (!touchUp_) {
1032 if (scrollTouchUpCallback_) {
1033 scrollTouchUpCallback_();
1034 }
1035 touchUp_ = true;
1036 }
1037 currentPos_ = position;
1038 }
1039
ProcessScrollMotion(double position)1040 void Scrollable::ProcessScrollMotion(double position)
1041 {
1042 currentVelocity_ = frictionVelocity_;
1043 if (needScrollSnapToSideCallback_) {
1044 needScrollSnapChange_ = needScrollSnapToSideCallback_(position - currentPos_);
1045 }
1046 TAG_LOGD(AceLogTag::ACE_SCROLLABLE, "position is %{public}f, currentVelocity_ is %{public}f, "
1047 "needScrollSnapChange_ is %{public}u",
1048 position, currentVelocity_, needScrollSnapChange_);
1049 if (LessOrEqual(std::abs(currentPos_ - position), 1)) {
1050 // trace stop at OnScrollStop
1051 AceAsyncTraceBegin(0, (TRAILING_ANIMATION + std::to_string(nodeId_) + std::string(" ") + nodeTag_).c_str());
1052 }
1053 // UpdateScrollPosition return false, means reach to scroll limit.
1054 auto mainDelta = position - currentPos_;
1055 HandleScroll(mainDelta, SCROLL_FROM_ANIMATION, NestedState::GESTURE);
1056 if (!moved_) {
1057 ResetContinueDragCount();
1058 StopFrictionAnimation();
1059 } else if (!touchUp_) {
1060 if (scrollTouchUpCallback_) {
1061 scrollTouchUpCallback_();
1062 }
1063 touchUp_ = true;
1064 }
1065 currentPos_ = position;
1066
1067 // spring effect special process
1068 if ((IsSnapStopped() && canOverScroll_) || needScrollSnapChange_ ||
1069 (!Container::IsCurrentUseNewPipeline() && outBoundaryCallback_ && outBoundaryCallback_())) {
1070 ACE_SCOPED_TRACE("scrollPause set true to stop ProcessScrollMotion, canOverScroll:%u, needScrollSnapChange:%u, "
1071 "nodeId:%d, tag:%s", canOverScroll_, needScrollSnapChange_, nodeId_, nodeTag_.c_str());
1072 scrollPause_ = true;
1073 skipRestartSpring_ = true;
1074 ResetContinueDragCount();
1075 MarkNeedFlushAnimationStartTime();
1076 StopFrictionAnimation();
1077 }
1078 }
1079
UpdateScrollPosition(const double offset,int32_t source) const1080 bool Scrollable::UpdateScrollPosition(const double offset, int32_t source) const
1081 {
1082 bool ret = true;
1083 if (callback_) {
1084 ret = callback_(offset, source);
1085 }
1086 return ret;
1087 }
1088
ProcessScrollOverCallback(double velocity)1089 void Scrollable::ProcessScrollOverCallback(double velocity)
1090 {
1091 if (outBoundaryCallback_ && !outBoundaryCallback_() && !canOverScroll_) {
1092 return;
1093 }
1094 // In the case of chain animation enabled, you need to switch the control point first,
1095 // and then correct the offset value in notification process
1096 if (notifyScrollOverCallback_) {
1097 notifyScrollOverCallback_(velocity);
1098 }
1099 // then use corrected offset to make scroll motion.
1100 if (scrollOverCallback_) {
1101 scrollOverCallback_(velocity);
1102 }
1103 }
1104
HandleOverScroll(double velocity)1105 bool Scrollable::HandleOverScroll(double velocity)
1106 {
1107 if (!overScrollCallback_) {
1108 if (edgeEffect_ == EdgeEffect::SPRING) {
1109 ProcessScrollOverCallback(velocity);
1110 return true;
1111 }
1112 if (scrollEndCallback_) {
1113 scrollEndCallback_();
1114 }
1115 return false;
1116 }
1117 // call NestableScrollContainer::HandleOverScroll
1118 return overScrollCallback_(velocity);
1119 }
1120
SetSlipFactor(double SlipFactor)1121 void Scrollable::SetSlipFactor(double SlipFactor)
1122 {
1123 slipFactor_ = std::clamp(SlipFactor, -ADJUSTABLE_VELOCITY, ADJUSTABLE_VELOCITY);
1124 }
1125
UpdateScrollSnapEndWithOffset(double offset)1126 void Scrollable::UpdateScrollSnapEndWithOffset(double offset)
1127 {
1128 if (!isSnapScrollAnimationStop_) {
1129 MarkNeedFlushAnimationStartTime();
1130 AnimationOption option;
1131 option.SetDuration(CUSTOM_SPRING_ANIMATION_DURATION);
1132 auto curve = AceType::MakeRefPtr<ResponsiveSpringMotion>(DEFAULT_SPRING_RESPONSE, DEFAULT_SPRING_DAMP, 0.0f);
1133 option.SetCurve(curve);
1134 if (!snapOffsetProperty_) {
1135 GetSnapProperty();
1136 }
1137 updateSnapAnimationCount_++;
1138 endPos_ -= offset;
1139 snapOffsetProperty_->SetPropertyUnit(PropertyUnit::PIXEL_POSITION);
1140 AnimationUtils::StartAnimation(
1141 option,
1142 [weak = AceType::WeakClaim(this)]() {
1143 auto scroll = weak.Upgrade();
1144 CHECK_NULL_VOID(scroll);
1145 scroll->snapOffsetProperty_->Set(scroll->endPos_);
1146 scroll->isSnapScrollAnimationStop_ = false;
1147 },
1148 [weak = AceType::WeakClaim(this), id = Container::CurrentId()]() {
1149 ContainerScope scope(id);
1150 auto scroll = weak.Upgrade();
1151 CHECK_NULL_VOID(scroll);
1152 scroll->updateSnapAnimationCount_--;
1153 // avoid current animation being interrupted by the prev animation's finish callback
1154 if (scroll->updateSnapAnimationCount_ == 0) {
1155 scroll->isSnapScrollAnimationStop_ = true;
1156 scroll->ProcessScrollSnapStop();
1157 }
1158 });
1159 isSnapScrollAnimationStop_ = false;
1160 }
1161 }
1162
GetFrictionProperty()1163 RefPtr<NodeAnimatablePropertyFloat> Scrollable::GetFrictionProperty()
1164 {
1165 auto propertyCallback = [weak = AceType::WeakClaim(this)](float position) {
1166 auto scroll = weak.Upgrade();
1167 CHECK_NULL_VOID(scroll);
1168 if (scroll->isFrictionAnimationStop_ || scroll->isTouching_) {
1169 return;
1170 }
1171 scroll->isSnapAnimation_ = false;
1172 scroll->ProcessScrollMotion(position);
1173 if (NearEqual(scroll->finalPosition_, position, 1.0)) {
1174 scroll->StopFrictionAnimation();
1175 }
1176 auto context = OHOS::Ace::PipelineContext::GetCurrentContext();
1177 CHECK_NULL_VOID(context);
1178 uint64_t currentVsync = context->GetVsyncTime();
1179 uint64_t diff = currentVsync - scroll->lastVsyncTime_;
1180 if (diff < MAX_VSYNC_DIFF_TIME && diff > MIN_DIFF_VSYNC) {
1181 scroll->frictionVelocity_ = (position - scroll->lastPosition_) / diff * MILLOS_PER_NANO_SECONDS;
1182 if (NearZero(scroll->frictionVelocity_, FRICTION_VELOCITY_THRESHOLD)) {
1183 scroll->StopFrictionAnimation();
1184 ResSchedReport::GetInstance().ResSchedDataReport("slide_off");
1185 }
1186 }
1187 scroll->lastVsyncTime_ = currentVsync;
1188 scroll->lastPosition_ = position;
1189 };
1190 frictionOffsetProperty_ = AceType::MakeRefPtr<NodeAnimatablePropertyFloat>(0.0, std::move(propertyCallback));
1191 return frictionOffsetProperty_;
1192 }
1193
GetSpringProperty()1194 RefPtr<NodeAnimatablePropertyFloat> Scrollable::GetSpringProperty()
1195 {
1196 auto propertyCallback = [weak = AceType::WeakClaim(this)](float position) {
1197 auto scroll = weak.Upgrade();
1198 CHECK_NULL_VOID(scroll);
1199 if (scroll->isSpringAnimationStop_) {
1200 return;
1201 }
1202 if (!NearEqual(scroll->finalPosition_, position, SPRING_ACCURACY)) {
1203 scroll->ProcessSpringMotion(position);
1204 return;
1205 }
1206 /*
1207 * In order to prevent accumulation errors, the current position is re obtained to ensure that
1208 * the last frame can accurately stop at the top and bottom positions.
1209 */
1210 if (scroll->currentPositionCallback_) {
1211 double currPos = scroll->currentPositionCallback_();
1212 if (NearEqual(currPos, scroll->currentPos_, 0.5)) {
1213 scroll->currentPos_ = currPos;
1214 }
1215 }
1216 scroll->ProcessSpringMotion(scroll->finalPosition_);
1217 scroll->StopSpringAnimation();
1218 };
1219 springOffsetProperty_ = AceType::MakeRefPtr<NodeAnimatablePropertyFloat>(0.0, std::move(propertyCallback));
1220 return springOffsetProperty_;
1221 }
1222
GetSnapProperty()1223 RefPtr<NodeAnimatablePropertyFloat> Scrollable::GetSnapProperty()
1224 {
1225 auto propertyCallback = [weak = AceType::WeakClaim(this)](float position) {
1226 auto scroll = weak.Upgrade();
1227 CHECK_NULL_VOID(scroll);
1228 if (scroll->isSnapScrollAnimationStop_ && scroll->isSnapAnimationStop_) {
1229 return;
1230 }
1231 auto context = OHOS::Ace::PipelineContext::GetCurrentContext();
1232 CHECK_NULL_VOID(context);
1233 uint64_t currentVsync = context->GetVsyncTime();
1234 uint64_t diff = currentVsync - scroll->lastVsyncTime_;
1235 if (diff < MAX_VSYNC_DIFF_TIME && diff > MIN_DIFF_VSYNC) {
1236 scroll->snapVelocity_ = (position - scroll->currentPos_) / diff * MILLOS_PER_NANO_SECONDS;
1237 }
1238 scroll->lastVsyncTime_ = currentVsync;
1239 if (NearEqual(scroll->endPos_, position, SPRING_ACCURACY)) {
1240 if (!scroll->isSnapScrollAnimationStop_) {
1241 scroll->ProcessScrollSnapMotion(scroll->endPos_);
1242 } else if (!scroll->isSnapAnimationStop_) {
1243 scroll->isSnapAnimation_ = true;
1244 scroll->ProcessScrollMotion(scroll->endPos_);
1245 }
1246 scroll->StopSnapAnimation();
1247 } else {
1248 if (!scroll->isSnapScrollAnimationStop_) {
1249 scroll->ProcessScrollSnapMotion(position);
1250 } else if (!scroll->isSnapAnimationStop_) {
1251 scroll->isSnapAnimation_ = true;
1252 scroll->ProcessScrollMotion(position);
1253 }
1254 }
1255 };
1256 snapOffsetProperty_ = AceType::MakeRefPtr<NodeAnimatablePropertyFloat>(0.0, std::move(propertyCallback));
1257 return snapOffsetProperty_;
1258 }
1259
StopFrictionAnimation()1260 void Scrollable::StopFrictionAnimation()
1261 {
1262 if (!isFrictionAnimationStop_) {
1263 ACE_SCOPED_TRACE("StopFrictionAnimation, id:%d, tag:%s", nodeId_, nodeTag_.c_str());
1264 isFrictionAnimationStop_ = true;
1265 AnimationOption option;
1266 option.SetCurve(Curves::EASE);
1267 option.SetDuration(0);
1268 AnimationUtils::StartAnimation(
1269 option,
1270 [weak = AceType::WeakClaim(this)]() {
1271 auto scroll = weak.Upgrade();
1272 CHECK_NULL_VOID(scroll);
1273 scroll->frictionOffsetProperty_->Set(scroll->currentPos_);
1274 },
1275 nullptr);
1276 }
1277 }
1278
StopSpringAnimation(bool reachFinalPosition)1279 void Scrollable::StopSpringAnimation(bool reachFinalPosition)
1280 {
1281 if (!isSpringAnimationStop_) {
1282 ACE_SCOPED_TRACE(
1283 "StopSpringAnimation, reachFinalPosition:%u, id:%d, tag:%s", reachFinalPosition, nodeId_, nodeTag_.c_str());
1284 isSpringAnimationStop_ = true;
1285 isFadingAway_ = false;
1286 AnimationOption option;
1287 option.SetCurve(Curves::EASE);
1288 option.SetDuration(0);
1289 AnimationUtils::StartAnimation(
1290 option,
1291 [weak = AceType::WeakClaim(this), reachFinalPosition]() {
1292 auto scroll = weak.Upgrade();
1293 CHECK_NULL_VOID(scroll);
1294 if (reachFinalPosition) {
1295 // ensure that the spring animation is restored to its final position.
1296 scroll->ProcessSpringMotion(scroll->finalPosition_);
1297 // use non final position to stop animation, otherwise the animation cannot be stoped.
1298 scroll->springOffsetProperty_->Set(scroll->finalPosition_ - 1.f);
1299 } else {
1300 // avoid top edge spring can not stop
1301 scroll->springOffsetProperty_->Set(scroll->currentPos_);
1302 }
1303 },
1304 nullptr);
1305 }
1306 currentVelocity_ = 0.0;
1307 }
1308
StopSnapAnimation()1309 void Scrollable::StopSnapAnimation()
1310 {
1311 if (!isSnapAnimationStop_ || !isSnapScrollAnimationStop_) {
1312 ACE_SCOPED_TRACE("StopSnapAnimation, isSnapAnimationStop_:%u, isSnapScrollAnimationStop_:%u, id:%d, tag:%s",
1313 isSnapAnimationStop_, isSnapScrollAnimationStop_, nodeId_, nodeTag_.c_str());
1314 isSnapAnimationStop_ = true;
1315 isSnapScrollAnimationStop_ = true;
1316 AnimationOption option;
1317 option.SetCurve(Curves::EASE);
1318 option.SetDuration(0);
1319 AnimationUtils::StartAnimation(
1320 option,
1321 [weak = AceType::WeakClaim(this)]() {
1322 auto scroll = weak.Upgrade();
1323 CHECK_NULL_VOID(scroll);
1324 scroll->snapOffsetProperty_->Set(scroll->currentPos_);
1325 },
1326 nullptr);
1327 }
1328 }
1329
IsMouseWheelScroll(const GestureEvent & info)1330 inline bool Scrollable::IsMouseWheelScroll(const GestureEvent& info)
1331 {
1332 return info.GetInputEventType() == InputEventType::AXIS && info.GetSourceTool() != SourceTool::TOUCHPAD;
1333 }
1334
OnCollectTouchTarget(TouchTestResult & result,const RefPtr<FrameNode> & frameNode,const RefPtr<TargetComponent> & targetComponent,ResponseLinkResult & responseLinkResult)1335 void Scrollable::OnCollectTouchTarget(TouchTestResult& result, const RefPtr<FrameNode>& frameNode,
1336 const RefPtr<TargetComponent>& targetComponent, ResponseLinkResult& responseLinkResult)
1337 {
1338 if (panRecognizerNG_) {
1339 panRecognizerNG_->SetNodeId(frameNode->GetId());
1340 panRecognizerNG_->AttachFrameNode(frameNode);
1341 panRecognizerNG_->SetTargetComponent(targetComponent);
1342 panRecognizerNG_->SetIsSystemGesture(true);
1343 panRecognizerNG_->SetRecognizerType(GestureTypeName::PAN_GESTURE);
1344 result.emplace_back(panRecognizerNG_);
1345 responseLinkResult.emplace_back(panRecognizerNG_);
1346 }
1347 }
1348 } // namespace OHOS::Ace::NG
1349