1 /*
2 * Copyright (c) 2022-2024 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/refresh/refresh_pattern.h"
17
18 #include "base/geometry/dimension.h"
19 #include "base/geometry/ng/offset_t.h"
20 #include "base/memory/ace_type.h"
21 #include "base/utils/utils.h"
22 #include "core/animation/spring_curve.h"
23 #include "core/common/container.h"
24 #include "core/components/common/properties/animation_option.h"
25 #include "core/components/refresh/refresh_theme.h"
26 #include "core/components_ng/base/frame_node.h"
27 #include "core/components_ng/event/event_hub.h"
28 #include "core/components_ng/pattern/loading_progress/loading_progress_layout_property.h"
29 #include "core/components_ng/pattern/loading_progress/loading_progress_paint_property.h"
30 #include "core/components_ng/pattern/refresh/refresh_animation_state.h"
31 #include "core/components_ng/pattern/refresh/refresh_layout_property.h"
32 #include "core/components_ng/pattern/scrollable/scrollable_pattern.h"
33 #include "core/components_ng/property/property.h"
34 #include "core/components_ng/render/animation_utils.h"
35 #include "core/pipeline/base/element_register.h"
36 #include "core/pipeline_ng/pipeline_context.h"
37 #include "frameworks/base/i18n/localization.h"
38 #include "frameworks/base/utils/time_util.h"
39 #include "frameworks/base/utils/utils.h"
40 #include "frameworks/core/components/common/layout/constants.h"
41 #include "frameworks/core/components_ng/pattern/loading_progress/loading_progress_pattern.h"
42 #include "frameworks/core/components_ng/pattern/text/text_pattern.h"
43
44 namespace OHOS::Ace::NG {
45
46 namespace {
47 constexpr float PERCENT = 0.01f; // Percent
48 constexpr float FOLLOW_TO_RECYCLE_DURATION = 600.0f;
49 constexpr float CUSTOM_BUILDER_ANIMATION_DURATION = 100.0f;
50 constexpr float LOADING_ANIMATION_DURATION = 350.0f;
51 constexpr float MAX_OFFSET = 100000.0f;
52 constexpr float HALF = 0.5f;
53 constexpr float BASE_SCALE = 0.707f; // std::sqrt(2)/2
54 constexpr Dimension TRIGGER_LOADING_DISTANCE = 16.0_vp;
55 constexpr Dimension TRIGGER_REFRESH_DISTANCE = 64.0_vp;
56 constexpr Dimension MAX_SCROLL_DISTANCE = 128.0_vp;
57 constexpr Dimension LOADING_PROGRESS_SIZE = 32.0_vp;
58 constexpr float DEFAULT_FRICTION = 62.0f;
59 const RefPtr<Curve> DEFAULT_CURVE = AceType::MakeRefPtr<CubicCurve>(0.2f, 0.0f, 0.1f, 1.0f);
60 const std::string REFRESH_DRAG_SCENE = "refresh_drag_scene";
61 } // namespace
62
OnAttachToFrameNode()63 void RefreshPattern::OnAttachToFrameNode()
64 {
65 auto host = GetHost();
66 CHECK_NULL_VOID(host);
67 host->GetRenderContext()->SetClipToBounds(true);
68 host->GetRenderContext()->UpdateClipEdge(true);
69 }
70
OnModifyDone()71 void RefreshPattern::OnModifyDone()
72 {
73 Pattern::OnModifyDone();
74 auto host = GetHost();
75 CHECK_NULL_VOID(host);
76 auto hub = host->GetEventHub<EventHub>();
77 CHECK_NULL_VOID(hub);
78 auto gestureHub = hub->GetOrCreateGestureEventHub();
79 CHECK_NULL_VOID(gestureHub);
80 auto layoutProperty = GetLayoutProperty<RefreshLayoutProperty>();
81 CHECK_NULL_VOID(layoutProperty);
82 InitPanEvent(gestureHub);
83 InitOnKeyEvent();
84 InitChildNode();
85 if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
86 InitOffsetProperty();
87 } else {
88 triggerLoadingDistance_ = static_cast<float>(
89 std::clamp(layoutProperty->GetIndicatorOffset().value_or(TRIGGER_LOADING_DISTANCE).ConvertToPx(),
90 -1.0f * TRIGGER_LOADING_DISTANCE.ConvertToPx(), TRIGGER_REFRESH_DISTANCE.ConvertToPx()));
91 InitLowVersionOffset();
92 }
93 RefreshStatusChangeEffect();
94 SetAccessibilityAction();
95 }
96
CreateLayoutAlgorithm()97 RefPtr<LayoutAlgorithm> RefreshPattern::CreateLayoutAlgorithm()
98 {
99 auto refreshLayoutAlgorithm = MakeRefPtr<RefreshLayoutAlgorithm>();
100 if (isCustomBuilderExist_) {
101 refreshLayoutAlgorithm->SetCustomBuilderIndex(0);
102 if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
103 refreshLayoutAlgorithm->SetBuilderMeasureBaseHeight(builderMeasureBaseHeight_);
104 } else {
105 refreshLayoutAlgorithm->SetCustomBuilderOffset(customBuilderOffset_);
106 refreshLayoutAlgorithm->SetScrollOffset(scrollOffset_);
107 }
108 }
109 return refreshLayoutAlgorithm;
110 }
111
InitPanEvent(const RefPtr<GestureEventHub> & gestureHub)112 void RefreshPattern::InitPanEvent(const RefPtr<GestureEventHub>& gestureHub)
113 {
114 if (panEvent_) {
115 return;
116 }
117 auto actionStartTask = [weak = WeakClaim(this)](const GestureEvent& info) {
118 auto pattern = weak.Upgrade();
119 CHECK_NULL_VOID(pattern);
120 pattern->HandleDragStart(true, static_cast<float>(info.GetMainVelocity()));
121 };
122 auto actionUpdateTask = [weak = WeakClaim(this)](const GestureEvent& info) {
123 auto pattern = weak.Upgrade();
124 CHECK_NULL_VOID(pattern);
125 pattern->HandleDragUpdate(static_cast<float>(info.GetMainDelta()), static_cast<float>(info.GetMainVelocity()));
126 };
127 auto actionEndTask = [weak = WeakClaim(this)](const GestureEvent& info) {
128 auto pattern = weak.Upgrade();
129 CHECK_NULL_VOID(pattern);
130 pattern->HandleDragEnd(info.GetMainVelocity());
131 };
132 auto actionCancelTask = [weak = WeakClaim(this)]() {
133 auto pattern = weak.Upgrade();
134 CHECK_NULL_VOID(pattern);
135 pattern->HandleDragCancel();
136 };
137 PanDirection panDirection;
138 panDirection.type = PanDirection::VERTICAL;
139 if (panEvent_) {
140 gestureHub->RemovePanEvent(panEvent_);
141 }
142
143 panEvent_ = MakeRefPtr<PanEvent>(
144 std::move(actionStartTask), std::move(actionUpdateTask), std::move(actionEndTask), std::move(actionCancelTask));
145 gestureHub->AddPanEvent(panEvent_, panDirection, 1, DEFAULT_PAN_DISTANCE);
146 }
147
InitOnKeyEvent()148 void RefreshPattern::InitOnKeyEvent()
149 {
150 if (isKeyEventRegisted_) {
151 return;
152 }
153 auto host = GetHost();
154 CHECK_NULL_VOID(host);
155 auto focusHub = host->GetFocusHub();
156 CHECK_NULL_VOID(focusHub);
157 auto onKeyEvent = [wp = WeakClaim(this)](const KeyEvent& event) -> bool {
158 auto pattern = wp.Upgrade();
159 CHECK_NULL_RETURN(pattern, false);
160 return pattern->OnKeyEvent(event);
161 };
162 isKeyEventRegisted_ = true;
163 focusHub->SetOnKeyEventInternal(std::move(onKeyEvent));
164 }
165
InitProgressNode()166 void RefreshPattern::InitProgressNode()
167 {
168 auto host = GetHost();
169 CHECK_NULL_VOID(host);
170 progressChild_ = FrameNode::CreateFrameNode(V2::LOADING_PROGRESS_ETS_TAG,
171 ElementRegister::GetInstance()->MakeUniqueId(), AceType::MakeRefPtr<LoadingProgressPattern>());
172 CHECK_NULL_VOID(progressChild_);
173 host->AddChild(progressChild_, -1);
174 auto gestureHub = progressChild_->GetEventHub<EventHub>();
175 if (gestureHub) {
176 gestureHub->SetEnabled(false);
177 }
178 auto progressLayoutProperty = progressChild_->GetLayoutProperty<LoadingProgressLayoutProperty>();
179 CHECK_NULL_VOID(progressLayoutProperty);
180 progressLayoutProperty->UpdateUserDefinedIdealSize(
181 CalcSize(CalcLength(LOADING_PROGRESS_SIZE.ConvertToPx()), CalcLength(LOADING_PROGRESS_SIZE.ConvertToPx())));
182 auto layoutProperty = GetLayoutProperty<RefreshLayoutProperty>();
183 CHECK_NULL_VOID(layoutProperty);
184 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
185 CHECK_NULL_VOID(progressPaintProperty);
186 progressPaintProperty->UpdateLoadingProgressOwner(LoadingProgressOwner::REFRESH);
187 if (layoutProperty->HasProgressColor()) {
188 progressPaintProperty->UpdateColor(layoutProperty->GetProgressColorValue());
189 }
190 progressChild_->MarkDirtyNode();
191 }
192 // the child need to add to be added to the first position in customBuilder mode,
193 // the child need to add to be added to the last position in loadingProgress mode.
InitChildNode()194 void RefreshPattern::InitChildNode()
195 {
196 auto host = GetHost();
197 CHECK_NULL_VOID(host);
198 if (isCustomBuilderExist_) {
199 if (progressChild_) {
200 host->RemoveChild(progressChild_);
201 progressChild_ = nullptr;
202 }
203 } else if (!progressChild_) {
204 InitProgressNode();
205 if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
206 auto progressContext = progressChild_->GetRenderContext();
207 CHECK_NULL_VOID(progressContext);
208 progressContext->UpdateOpacity(0.0);
209 } else {
210 UpdateLoadingProgress();
211 }
212 }
213 }
214
RefreshStatusChangeEffect()215 void RefreshPattern::RefreshStatusChangeEffect()
216 {
217 auto layoutProperty = GetLayoutProperty<RefreshLayoutProperty>();
218 CHECK_NULL_VOID(layoutProperty);
219 auto refreshingProp = layoutProperty->GetIsRefreshing().value_or(false);
220 if (isRefreshing_ != refreshingProp) {
221 if (refreshingProp) {
222 QuickStartFresh();
223 } else {
224 QuickEndFresh();
225 }
226 }
227 }
228
QuickStartFresh()229 void RefreshPattern::QuickStartFresh()
230 {
231 UpdateRefreshStatus(RefreshStatus::REFRESH);
232 if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
233 QuickFirstChildAppear();
234 return;
235 }
236
237 if (isCustomBuilderExist_) {
238 CustomBuilderRefreshingAnimation(false);
239 } else {
240 LoadingProgressRefreshingAnimation(false);
241 }
242 }
243
QuickEndFresh()244 void RefreshPattern::QuickEndFresh()
245 {
246 SwitchToFinish();
247 if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
248 QuickFirstChildDisappear();
249 return;
250 }
251
252 if (isCustomBuilderExist_) {
253 CustomBuilderExit();
254 } else {
255 LoadingProgressExit();
256 }
257 }
258
OnKeyEvent(const KeyEvent & event)259 bool RefreshPattern::OnKeyEvent(const KeyEvent& event)
260 {
261 if (event.code == KeyCode::KEY_F5 || (event.IsCombinationKey() && event.IsCtrlWith(KeyCode::KEY_R))) {
262 if (!isRefreshing_) {
263 QuickStartFresh();
264 }
265 return true;
266 }
267 return false;
268 }
269
HandleDragStart(bool isDrag,float mainSpeed)270 void RefreshPattern::HandleDragStart(bool isDrag, float mainSpeed)
271 {
272 UpdateDragFRCSceneInfo(REFRESH_DRAG_SCENE, mainSpeed, SceneStatus::START);
273 if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
274 isSourceFromAnimation_ = !isDrag;
275 ResetAnimation();
276 } else {
277 HandleDragStartLowVersion();
278 }
279 // AccessibilityEventType::SCROLL_START
280 }
281
HandleDragUpdate(float delta,float mainSpeed)282 void RefreshPattern::HandleDragUpdate(float delta, float mainSpeed)
283 {
284 UpdateDragFRCSceneInfo(REFRESH_DRAG_SCENE, mainSpeed, SceneStatus::RUNNING);
285 if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
286 // If dragging does not expand the refresh, there is no need to continue executing the code
287 if (NearZero(scrollOffset_) && NonPositive(delta)) {
288 return;
289 }
290 scrollOffset_ = std::clamp(scrollOffset_ + delta * CalculateFriction(), 0.0f, MAX_OFFSET);
291 if (!isSourceFromAnimation_) {
292 if (isRefreshing_) {
293 UpdateLoadingProgressStatus(RefreshAnimationState::RECYCLE, GetFollowRatio());
294 } else {
295 UpdateLoadingProgressStatus(RefreshAnimationState::FOLLOW_HAND, GetFollowRatio());
296 if (LessNotEqual(scrollOffset_, static_cast<float>(TRIGGER_REFRESH_DISTANCE.ConvertToPx()))) {
297 UpdateRefreshStatus(RefreshStatus::DRAG);
298 } else {
299 UpdateRefreshStatus(RefreshStatus::OVER_DRAG);
300 }
301 }
302 }
303 UpdateFirstChildPlacement();
304 } else {
305 HandleDragUpdateLowVersion(delta);
306 }
307 }
308
HandleDragEnd(float speed)309 void RefreshPattern::HandleDragEnd(float speed)
310 {
311 UpdateDragFRCSceneInfo(REFRESH_DRAG_SCENE, speed, SceneStatus::END);
312 if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
313 SpeedTriggerAnimation(speed);
314 } else {
315 HandleDragEndLowVersion();
316 }
317 }
318
HandleDragCancel()319 void RefreshPattern::HandleDragCancel()
320 {
321 HandleDragEnd(0.0f);
322 }
323
CalculateFriction()324 float RefreshPattern::CalculateFriction()
325 {
326 auto host = GetHost();
327 CHECK_NULL_RETURN(host, 1.0f);
328 auto geometryNode = host->GetGeometryNode();
329 CHECK_NULL_RETURN(geometryNode, 1.0f);
330 auto contentHeight = geometryNode->GetPaddingSize().Height();
331 return NearZero(contentHeight) ? 1.0f : ScrollablePattern::CalculateFriction(scrollOffset_ / contentHeight);
332 }
333
GetFollowRatio()334 float RefreshPattern::GetFollowRatio()
335 {
336 auto loadingVisibleHeight = GetLoadingVisibleHeight();
337 auto ratio = 0.0f;
338 if (!NearEqual(static_cast<float>(TRIGGER_REFRESH_DISTANCE.ConvertToPx()), loadingVisibleHeight)) {
339 ratio = static_cast<float>(
340 (scrollOffset_ - loadingVisibleHeight) / (TRIGGER_REFRESH_DISTANCE.ConvertToPx() - loadingVisibleHeight));
341 }
342 return std::clamp(ratio, 0.0f, 1.0f);
343 }
344
FireStateChange(int32_t value)345 void RefreshPattern::FireStateChange(int32_t value)
346 {
347 auto refreshEventHub = GetEventHub<RefreshEventHub>();
348 CHECK_NULL_VOID(refreshEventHub);
349 refreshEventHub->FireOnStateChange(value);
350 if (refreshStatus_ == RefreshStatus::REFRESH && Recorder::EventRecorder::Get().IsComponentRecordEnable()) {
351 auto host = GetHost();
352 CHECK_NULL_VOID(host);
353 auto inspectorId = host->GetInspectorId().value_or("");
354 Recorder::EventParamsBuilder builder;
355 builder.SetId(inspectorId)
356 .SetType(host->GetTag())
357 .SetEventType(Recorder::EventType::REFRESH)
358 .SetDescription(host->GetAutoEventParamValue(""));
359 Recorder::EventRecorder::Get().OnEvent(std::move(builder));
360 }
361 }
362
FireRefreshing()363 void RefreshPattern::FireRefreshing()
364 {
365 auto refreshEventHub = GetEventHub<RefreshEventHub>();
366 CHECK_NULL_VOID(refreshEventHub);
367 refreshEventHub->FireOnRefreshing();
368 }
369
FireChangeEvent(const std::string & value)370 void RefreshPattern::FireChangeEvent(const std::string& value)
371 {
372 auto refreshEventHub = GetEventHub<RefreshEventHub>();
373 CHECK_NULL_VOID(refreshEventHub);
374 refreshEventHub->FireChangeEvent(value);
375 }
376
AddCustomBuilderNode(const RefPtr<NG::UINode> & builder)377 void RefreshPattern::AddCustomBuilderNode(const RefPtr<NG::UINode>& builder)
378 {
379 if (!builder) {
380 isCustomBuilderExist_ = false;
381 customBuilder_ = nullptr;
382 return;
383 }
384 auto host = GetHost();
385 CHECK_NULL_VOID(host);
386
387 if (!isCustomBuilderExist_) {
388 host->AddChild(builder, 0);
389 } else {
390 auto customNodeChild = host->GetFirstChild();
391 CHECK_NULL_VOID(customNodeChild);
392 host->ReplaceChild(customNodeChild, builder);
393 }
394 customBuilder_ = AceType::DynamicCast<FrameNode>(builder);
395 isCustomBuilderExist_ = true;
396 }
397
SetAccessibilityAction()398 void RefreshPattern::SetAccessibilityAction()
399 {
400 auto host = GetHost();
401 CHECK_NULL_VOID(host);
402 auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
403 CHECK_NULL_VOID(accessibilityProperty);
404 accessibilityProperty->SetActionScrollForward([weakPtr = WeakClaim(this)]() {
405 const auto& pattern = weakPtr.Upgrade();
406 CHECK_NULL_VOID(pattern);
407 if (pattern->IsRefreshing()) {
408 return;
409 }
410 pattern->HandleDragStart(true, 0.0f);
411 for (float delta = 0.0f; LessNotEqual(delta, static_cast<float>(MAX_SCROLL_DISTANCE.ConvertToPx()));
412 delta += TRIGGER_LOADING_DISTANCE.ConvertToPx()) {
413 pattern->HandleDragUpdate(delta, 0.0f);
414 }
415 pattern->HandleDragEnd(0.0f);
416 });
417 }
418
InitCoordinationEvent(RefPtr<ScrollableCoordinationEvent> & coordinationEvent)419 void RefreshPattern::InitCoordinationEvent(RefPtr<ScrollableCoordinationEvent>& coordinationEvent)
420 {
421 auto onScrollEvent = [weak = WeakClaim(this)](float offset, float mainSpeed) -> bool {
422 auto pattern = weak.Upgrade();
423 CHECK_NULL_RETURN(pattern, false);
424 pattern->HandleDragUpdate(offset, mainSpeed);
425 return Positive(pattern->scrollOffset_) || NonNegative(offset);
426 };
427 coordinationEvent->SetOnScrollEvent(onScrollEvent);
428 auto onScrollStartEvent = [weak = WeakClaim(this)](bool isDrag, float mainSpeed) {
429 auto pattern = weak.Upgrade();
430 CHECK_NULL_VOID(pattern);
431 pattern->HandleDragStart(isDrag, mainSpeed);
432 };
433 coordinationEvent->SetOnScrollStartEvent(onScrollStartEvent);
434 auto onScrollEndEvent = [weak = WeakClaim(this)](float speed) {
435 auto pattern = weak.Upgrade();
436 CHECK_NULL_VOID(pattern);
437 pattern->HandleDragEnd(speed);
438 };
439 coordinationEvent->SetOnScrollEndEvent(onScrollEndEvent);
440 }
441
UpdateRefreshStatus(RefreshStatus newStatus)442 void RefreshPattern::UpdateRefreshStatus(RefreshStatus newStatus)
443 {
444 if (refreshStatus_ == newStatus) {
445 return;
446 }
447 refreshStatus_ = newStatus;
448 if (refreshStatus_ == RefreshStatus::REFRESH) {
449 isRefreshing_ = true;
450 // the two-way binding of 'refreshing' variable need to be changed before 'onRefreshing' function is triggered
451 FireChangeEvent("true");
452 FireRefreshing();
453 } else {
454 isRefreshing_ = false;
455 FireChangeEvent("false");
456 }
457 FireStateChange(static_cast<int>(refreshStatus_));
458 TAG_LOGD(AceLogTag::ACE_REFRESH, "refresh status changed %{public}d", static_cast<int32_t>(refreshStatus_));
459 }
460
SwitchToFinish()461 void RefreshPattern::SwitchToFinish()
462 {
463 if (refreshStatus_ != RefreshStatus::REFRESH && refreshStatus_ != RefreshStatus::DONE) {
464 UpdateRefreshStatus(RefreshStatus::INACTIVE);
465 } else {
466 UpdateRefreshStatus(RefreshStatus::DONE);
467 }
468 }
469
UpdateLoadingProgressStatus(RefreshAnimationState state,float ratio)470 void RefreshPattern::UpdateLoadingProgressStatus(RefreshAnimationState state, float ratio)
471 {
472 // Need to check loadingProgress mode
473 CHECK_NULL_VOID(progressChild_);
474 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
475 CHECK_NULL_VOID(progressPaintProperty);
476 progressPaintProperty->UpdateRefreshAnimationState(state);
477 switch (state) {
478 case RefreshAnimationState::FOLLOW_HAND:
479 case RefreshAnimationState::RECYCLE:
480 progressPaintProperty->UpdateRefreshSizeScaleRatio(ratio);
481 break;
482 default:
483 break;
484 }
485 if (CheckNeedRender(progressPaintProperty->GetPropertyChangeFlag())) {
486 progressChild_->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
487 }
488 }
489
InitOffsetProperty()490 void RefreshPattern::InitOffsetProperty()
491 {
492 if (!offsetProperty_) {
493 auto propertyCallback = [weak = AceType::WeakClaim(this)](float scrollOffset) {
494 auto pattern = weak.Upgrade();
495 CHECK_NULL_VOID(pattern);
496 pattern->scrollOffset_ = scrollOffset;
497 pattern->UpdateFirstChildPlacement();
498 };
499 offsetProperty_ = AceType::MakeRefPtr<NodeAnimatablePropertyFloat>(0.0, std::move(propertyCallback));
500 auto host = GetHost();
501 CHECK_NULL_VOID(host);
502 auto renderContext = host->GetRenderContext();
503 CHECK_NULL_VOID(renderContext);
504 renderContext->AttachNodeAnimatableProperty(offsetProperty_);
505 offsetProperty_->SetPropertyUnit(PropertyUnit::PIXEL_POSITION);
506 }
507 }
508
UpdateFirstChildPlacement()509 void RefreshPattern::UpdateFirstChildPlacement()
510 {
511 auto host = GetHost();
512 CHECK_NULL_VOID(host);
513 auto geometryNode = host->GetGeometryNode();
514 CHECK_NULL_VOID(geometryNode);
515 auto refreshHeight = geometryNode->GetFrameSize().Height();
516 auto scrollOffset = std::clamp(scrollOffset_, 0.0f, refreshHeight);
517 if (progressChild_) {
518 if (isSourceFromAnimation_) {
519 UpdateLoadingProgressTranslate(0.0f);
520 UpdateScrollTransition(scrollOffset);
521 } else {
522 UpdateLoadingProgressTranslate(scrollOffset);
523 UpdateScrollTransition(scrollOffset);
524 UpdateLoadingProgressStatus(GetLoadingProgressStatus(), GetFollowRatio());
525 }
526 } else {
527 UpdateBuilderHeight(scrollOffset);
528 }
529 }
530
UpdateScrollTransition(float scrollOffset)531 void RefreshPattern::UpdateScrollTransition(float scrollOffset)
532 {
533 auto host = GetHost();
534 CHECK_NULL_VOID(host);
535 // If the refresh has no children without loadingProgress, it does not need to be offset.
536 if (host->TotalChildCount() <= 1) {
537 return;
538 }
539 // Need to search for frameNode and skip ComponentNode
540 auto childNode = host->GetFirstChild();
541 while (!AceType::InstanceOf<FrameNode>(childNode) && !childNode->GetChildren().empty()) {
542 childNode = childNode->GetFirstChild();
543 }
544 auto scrollableNode = AceType::DynamicCast<FrameNode>(childNode);
545 CHECK_NULL_VOID(scrollableNode);
546 auto scrollableRenderContext = scrollableNode->GetRenderContext();
547 CHECK_NULL_VOID(scrollableRenderContext);
548 scrollableRenderContext->UpdateTransformTranslate({ 0.0f, scrollOffset, 0.0f });
549 }
550
UpdateBuilderHeight(float builderHeight)551 void RefreshPattern::UpdateBuilderHeight(float builderHeight)
552 {
553 auto host = GetHost();
554 CHECK_NULL_VOID(host);
555 auto layoutProperty = GetLayoutProperty<RefreshLayoutProperty>();
556 CHECK_NULL_VOID(layoutProperty);
557 builderMeasureBaseHeight_ = builderHeight;
558 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
559 }
560
UpdateLoadingProgressTranslate(float scrollOffset)561 void RefreshPattern::UpdateLoadingProgressTranslate(float scrollOffset)
562 {
563 CHECK_NULL_VOID(progressChild_);
564 auto renderContext = progressChild_->GetRenderContext();
565 CHECK_NULL_VOID(renderContext);
566 auto loadingVisibleHeight = GetLoadingVisibleHeight();
567 if (GreatOrEqual(scrollOffset, loadingVisibleHeight) &&
568 !NearEqual(loadingVisibleHeight, static_cast<float>(TRIGGER_REFRESH_DISTANCE.ConvertToPx()))) {
569 auto ratio = static_cast<float>(
570 (scrollOffset - loadingVisibleHeight) / (TRIGGER_REFRESH_DISTANCE.ConvertToPx() - loadingVisibleHeight));
571 renderContext->UpdateOpacity(std::clamp(ratio, 0.0f, 1.0f));
572 renderContext->UpdateTransformTranslate({ 0.0f, (scrollOffset - loadingVisibleHeight) * HALF, 0.0f });
573 } else {
574 renderContext->UpdateOpacity(0.0f);
575 }
576 }
577
GetLoadingVisibleHeight()578 float RefreshPattern::GetLoadingVisibleHeight()
579 {
580 CHECK_NULL_RETURN(progressChild_, 0.0f);
581 auto renderContext = progressChild_->GetRenderContext();
582 CHECK_NULL_RETURN(renderContext, 0.0f);
583 auto geometryNode = progressChild_->GetGeometryNode();
584 CHECK_NULL_RETURN(geometryNode, 0.0f);
585 auto loadingHeight = geometryNode->GetFrameSize().Height();
586 return (HALF + BASE_SCALE * HALF) * loadingHeight;
587 }
588
SpeedTriggerAnimation(float speed)589 void RefreshPattern::SpeedTriggerAnimation(float speed)
590 {
591 auto targetOffset = (isSourceFromAnimation_ || LessNotEqual(scrollOffset_, TRIGGER_REFRESH_DISTANCE.ConvertToPx()))
592 ? 0.0f
593 : TRIGGER_REFRESH_DISTANCE.ConvertToPx();
594 auto dealSpeed = 0.0f;
595 if (!NearEqual(scrollOffset_, targetOffset)) {
596 dealSpeed = speed / (targetOffset - scrollOffset_);
597 }
598 if (!isSourceFromAnimation_ && refreshStatus_ == RefreshStatus::OVER_DRAG) {
599 UpdateRefreshStatus(RefreshStatus::REFRESH);
600 UpdateLoadingProgressStatus(RefreshAnimationState::FOLLOW_TO_RECYCLE, GetFollowRatio());
601 } else if (NearZero(targetOffset)) {
602 SwitchToFinish();
603 }
604 ResetAnimation();
605 AnimationOption option;
606 auto curve = AceType::MakeRefPtr<InterpolatingSpring>(dealSpeed, 1.0f, 228.0f, 30.0f);
607 option.SetCurve(curve);
608 animation_ = AnimationUtils::StartAnimation(
609 option,
610 [&, weak = AceType::WeakClaim(this)]() {
611 auto pattern = weak.Upgrade();
612 CHECK_NULL_VOID(pattern);
613 pattern->offsetProperty_->Set(targetOffset);
614 },
615 [weak = AceType::WeakClaim(this)]() {
616 auto pattern = weak.Upgrade();
617 CHECK_NULL_VOID(pattern);
618 pattern->SpeedAnimationFinish();
619 });
620 }
621
GetTargetOffset()622 float RefreshPattern::GetTargetOffset()
623 {
624 if (isSourceFromAnimation_) {
625 return 0.0f;
626 }
627 auto targetOffset = 0.0f;
628 switch (refreshStatus_) {
629 case RefreshStatus::OVER_DRAG:
630 case RefreshStatus::REFRESH:
631 targetOffset = TRIGGER_REFRESH_DISTANCE.ConvertToPx();
632 break;
633 default:
634 targetOffset = 0.0f;
635 break;
636 }
637 return targetOffset;
638 }
639
SpeedAnimationFinish()640 void RefreshPattern::SpeedAnimationFinish()
641 {
642 if (isRefreshing_) {
643 UpdateLoadingProgressStatus(RefreshAnimationState::RECYCLE, GetFollowRatio());
644 } else {
645 UpdateLoadingProgressStatus(RefreshAnimationState::FOLLOW_HAND, GetFollowRatio());
646 }
647 }
648
QuickFirstChildAppear()649 void RefreshPattern::QuickFirstChildAppear()
650 {
651 UpdateLoadingProgressStatus(RefreshAnimationState::RECYCLE, GetFollowRatio());
652 ResetAnimation();
653 AnimationOption option;
654 option.SetCurve(DEFAULT_CURVE);
655 option.SetDuration(LOADING_ANIMATION_DURATION);
656 animation_ = AnimationUtils::StartAnimation(
657 option, [&]() { offsetProperty_->Set(static_cast<float>(TRIGGER_REFRESH_DISTANCE.ConvertToPx())); });
658 }
659
QuickFirstChildDisappear()660 void RefreshPattern::QuickFirstChildDisappear()
661 {
662 ResetAnimation();
663 AnimationOption option;
664 option.SetCurve(DEFAULT_CURVE);
665 option.SetDuration(LOADING_ANIMATION_DURATION);
666 animation_ = AnimationUtils::StartAnimation(
667 option, [&]() { offsetProperty_->Set(0.0f); },
668 [weak = AceType::WeakClaim(this)]() {
669 auto pattern = weak.Upgrade();
670 CHECK_NULL_VOID(pattern);
671 pattern->SpeedAnimationFinish();
672 });
673 }
674
GetLoadingProgressStatus()675 RefreshAnimationState RefreshPattern::GetLoadingProgressStatus()
676 {
677 auto defaultValue = RefreshAnimationState::FOLLOW_HAND;
678 CHECK_NULL_RETURN(progressChild_, defaultValue);
679 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
680 CHECK_NULL_RETURN(progressPaintProperty, defaultValue);
681 return progressPaintProperty->GetRefreshAnimationState().value_or(defaultValue);
682 }
683
ResetAnimation()684 void RefreshPattern::ResetAnimation()
685 {
686 float currentOffset = scrollOffset_;
687 AnimationUtils::StopAnimation(animation_);
688 if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
689 offsetProperty_->Set(currentOffset);
690 } else {
691 lowVersionOffset_->Set(currentOffset);
692 }
693 }
694
UpdateDragFRCSceneInfo(const std::string & scene,float speed,SceneStatus sceneStatus)695 void RefreshPattern::UpdateDragFRCSceneInfo(const std::string& scene, float speed, SceneStatus sceneStatus)
696 {
697 auto host = GetHost();
698 CHECK_NULL_VOID(host);
699 host->AddFRCSceneInfo(scene, std::abs(speed), sceneStatus);
700 }
701
InitLowVersionOffset()702 void RefreshPattern::InitLowVersionOffset()
703 {
704 if (!lowVersionOffset_) {
705 auto propertyCallback = [weak = AceType::WeakClaim(this)](float scrollOffset) {
706 auto pattern = weak.Upgrade();
707 CHECK_NULL_VOID(pattern);
708 pattern->scrollOffset_ = scrollOffset;
709 pattern->UpdateChild();
710 };
711 lowVersionOffset_ = AceType::MakeRefPtr<NodeAnimatablePropertyFloat>(0.0, std::move(propertyCallback));
712 auto host = GetHost();
713 CHECK_NULL_VOID(host);
714 auto renderContext = host->GetRenderContext();
715 CHECK_NULL_VOID(renderContext);
716 renderContext->AttachNodeAnimatableProperty(lowVersionOffset_);
717 }
718 }
719
UpdateChild()720 void RefreshPattern::UpdateChild()
721 {
722 if (customBuilder_) {
723 UpdateCustomBuilderProperty();
724 } else {
725 UpdateLoadingProgress();
726 }
727 }
728
HandleDragStartLowVersion()729 void RefreshPattern::HandleDragStartLowVersion()
730 {
731 if (isRefreshing_) {
732 return;
733 }
734 scrollOffset_ = 0.0f;
735 UpdateLoadingProgressStatus(RefreshAnimationState::FOLLOW_HAND, 0.0f);
736 }
737
HandleDragUpdateLowVersion(float delta)738 void RefreshPattern::HandleDragUpdateLowVersion(float delta)
739 {
740 if (isRefreshing_) {
741 return;
742 }
743 scrollOffset_ = GetScrollOffset(delta);
744 if (LessNotEqual(scrollOffset_, static_cast<float>(TRIGGER_REFRESH_DISTANCE.ConvertToPx()))) {
745 UpdateRefreshStatus(RefreshStatus::DRAG);
746 } else {
747 UpdateRefreshStatus(RefreshStatus::OVER_DRAG);
748 }
749 if (customBuilder_) {
750 HandleCustomBuilderDragUpdateStage();
751 return;
752 }
753 UpdateLoadingProgress();
754 if (GreatNotEqual(scrollOffset_, triggerLoadingDistance_)) {
755 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
756 CHECK_NULL_VOID(progressPaintProperty);
757 float triggerRefreshDistance = TRIGGER_REFRESH_DISTANCE.ConvertToPx();
758 float ratio =
759 NearEqual(triggerRefreshDistance, triggerLoadingDistance_)
760 ? 1.0f
761 : (scrollOffset_ - triggerLoadingDistance_) / (triggerRefreshDistance - triggerLoadingDistance_);
762 progressPaintProperty->UpdateRefreshSizeScaleRatio(std::clamp(ratio, 0.0f, 1.0f));
763 }
764 }
765
HandleDragEndLowVersion()766 void RefreshPattern::HandleDragEndLowVersion()
767 {
768 if (isRefreshing_) {
769 return;
770 }
771 if (customBuilder_) {
772 HandleCustomBuilderDragEndStage();
773 return;
774 }
775 if (refreshStatus_ == RefreshStatus::OVER_DRAG) {
776 UpdateRefreshStatus(RefreshStatus::REFRESH);
777 LoadingProgressRefreshingAnimation(true);
778 } else {
779 SwitchToFinish();
780 LoadingProgressExit();
781 }
782 // AccessibilityEventType::SCROLL_END
783 }
784
LoadingProgressRefreshingAnimation(bool isDrag)785 void RefreshPattern::LoadingProgressRefreshingAnimation(bool isDrag)
786 {
787 UpdateLoadingProgressStatus(RefreshAnimationState::RECYCLE, 1.0f);
788 ResetAnimation();
789 AnimationOption option;
790 if (isDrag) {
791 option.SetCurve(AceType::MakeRefPtr<SpringCurve>(0.0f, 1.0f, 228.0f, 30.0f));
792 option.SetDuration(FOLLOW_TO_RECYCLE_DURATION);
793 } else {
794 option.SetCurve(DEFAULT_CURVE);
795 option.SetDuration(LOADING_ANIMATION_DURATION);
796 }
797 animation_ = AnimationUtils::StartAnimation(
798 option, [&]() { lowVersionOffset_->Set(TRIGGER_REFRESH_DISTANCE.ConvertToPx()); });
799 }
800
LoadingProgressExit()801 void RefreshPattern::LoadingProgressExit()
802 {
803 ResetAnimation();
804 AnimationOption option;
805 option.SetCurve(DEFAULT_CURVE);
806 option.SetDuration(LOADING_ANIMATION_DURATION);
807 animation_ = AnimationUtils::StartAnimation(
808 option, [&]() { lowVersionOffset_->Set(0.0f); },
809 [weak = AceType::WeakClaim(this)]() {
810 auto pattern = weak.Upgrade();
811 CHECK_NULL_VOID(pattern);
812 pattern->UpdateLoadingProgressStatus(RefreshAnimationState::FOLLOW_HAND, 0.0f);
813 });
814 }
815
UpdateLoadingProgress()816 void RefreshPattern::UpdateLoadingProgress()
817 {
818 float loadingProgressOffset =
819 std::clamp(scrollOffset_, triggerLoadingDistance_, static_cast<float>(MAX_SCROLL_DISTANCE.ConvertToPx()));
820 UpdateLoadingMarginTop(loadingProgressOffset);
821 float triggerRefreshDistance = TRIGGER_REFRESH_DISTANCE.ConvertToPx();
822 float ratio = NearEqual(triggerRefreshDistance, triggerLoadingDistance_)
823 ? 1.0f
824 : (loadingProgressOffset - triggerLoadingDistance_) /
825 (TRIGGER_REFRESH_DISTANCE.ConvertToPx() - triggerLoadingDistance_);
826 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
827 CHECK_NULL_VOID(progressPaintProperty);
828 progressPaintProperty->UpdateRefreshSizeScaleRatio(ratio);
829 auto progressContext = progressChild_->GetRenderContext();
830 CHECK_NULL_VOID(progressContext);
831 progressContext->UpdateOpacity(std::clamp(ratio, 0.0f, 1.0f));
832 progressChild_->MarkDirtyNode(PROPERTY_UPDATE_LAYOUT);
833 }
834
CustomBuilderRefreshingAnimation(bool isDrag)835 void RefreshPattern::CustomBuilderRefreshingAnimation(bool isDrag)
836 {
837 ResetAnimation();
838 AnimationOption option;
839 if (isDrag) {
840 option.SetCurve(AceType::MakeRefPtr<SpringCurve>(0.0f, 1.0f, 228.0f, 30.0f));
841 option.SetDuration(FOLLOW_TO_RECYCLE_DURATION);
842 } else {
843 option.SetCurve(DEFAULT_CURVE);
844 option.SetDuration(CUSTOM_BUILDER_ANIMATION_DURATION);
845 }
846 animation_ = AnimationUtils::StartAnimation(
847 option, [&]() { lowVersionOffset_->Set(TRIGGER_REFRESH_DISTANCE.ConvertToPx()); });
848 }
849
CustomBuilderExit()850 void RefreshPattern::CustomBuilderExit()
851 {
852 ResetAnimation();
853 AnimationOption option;
854 option.SetDuration(CUSTOM_BUILDER_ANIMATION_DURATION);
855 option.SetCurve(DEFAULT_CURVE);
856 animation_ = AnimationUtils::StartAnimation(option, [&]() { lowVersionOffset_->Set(0.0f); });
857 }
858
UpdateCustomBuilderProperty()859 void RefreshPattern::UpdateCustomBuilderProperty()
860 {
861 auto customBuilderSize = customBuilder_->GetGeometryNode()->GetFrameSize();
862 auto maxScroll = static_cast<float>(MAX_SCROLL_DISTANCE.ConvertToPx());
863 customBuilderOffset_ = std::clamp(scrollOffset_, triggerLoadingDistance_, maxScroll - customBuilderSize.Height());
864 float triggerRefreshDistance = TRIGGER_REFRESH_DISTANCE.ConvertToPx();
865 float ratio = NearEqual(triggerRefreshDistance, triggerLoadingDistance_)
866 ? 1.0f
867 : (customBuilderOffset_ - triggerLoadingDistance_) /
868 (TRIGGER_REFRESH_DISTANCE.ConvertToPx() - triggerLoadingDistance_);
869 auto customBuilderContext = customBuilder_->GetRenderContext();
870 CHECK_NULL_VOID(customBuilderContext);
871 customBuilderContext->UpdateOpacity(std::clamp(ratio, 0.0f, 1.0f));
872 auto host = GetHost();
873 CHECK_NULL_VOID(host);
874 host->MarkDirtyNode(PROPERTY_UPDATE_LAYOUT);
875 }
876
HandleCustomBuilderDragUpdateStage()877 void RefreshPattern::HandleCustomBuilderDragUpdateStage()
878 {
879 auto customBuilderSize = customBuilder_->GetGeometryNode()->GetMarginFrameSize();
880 auto maxScroll = MAX_SCROLL_DISTANCE.ConvertToPx();
881 if (NearZero(static_cast<double>(customBuilder_->GetGeometryNode()->GetMarginFrameSize().Height()))) {
882 return;
883 }
884 if (LessNotEqual(static_cast<double>(maxScroll - customBuilderSize.Height()),
885 static_cast<double>(triggerLoadingDistance_))) {
886 return;
887 }
888 UpdateCustomBuilderProperty();
889 }
890
HandleCustomBuilderDragEndStage()891 void RefreshPattern::HandleCustomBuilderDragEndStage()
892 {
893 if (refreshStatus_ == RefreshStatus::OVER_DRAG) {
894 UpdateRefreshStatus(RefreshStatus::REFRESH);
895 CustomBuilderRefreshingAnimation(true);
896 } else {
897 SwitchToFinish();
898 CustomBuilderExit();
899 }
900 }
901
UpdateLoadingMarginTop(float top)902 void RefreshPattern::UpdateLoadingMarginTop(float top)
903 {
904 auto progressLayoutProperty = progressChild_->GetLayoutProperty<LoadingProgressLayoutProperty>();
905 CHECK_NULL_VOID(progressLayoutProperty);
906 MarginProperty marginProperty;
907 marginProperty.left = CalcLength(0.0f);
908 marginProperty.right = CalcLength(0.0f);
909 marginProperty.bottom = CalcLength(0.0f);
910 marginProperty.top = CalcLength(top);
911 progressLayoutProperty->UpdateMargin(marginProperty);
912 }
913
GetScrollOffset(float delta)914 float RefreshPattern::GetScrollOffset(float delta)
915 {
916 auto layoutProperty = GetLayoutProperty<RefreshLayoutProperty>();
917 CHECK_NULL_RETURN(layoutProperty, 0.0f);
918 auto frictionRatio = static_cast<float>(layoutProperty->GetFriction().value_or(DEFAULT_FRICTION)) * PERCENT;
919 auto scrollY = delta * frictionRatio;
920 return std::clamp(scrollOffset_ + scrollY, 0.0f, static_cast<float>(MAX_SCROLL_DISTANCE.ConvertToPx()));
921 }
922 } // namespace OHOS::Ace::NG
923