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/log/dump_log.h"
21 #include "base/memory/ace_type.h"
22 #include "base/utils/utils.h"
23 #include "core/animation/spring_curve.h"
24 #include "core/common/container.h"
25 #include "core/components/common/properties/animation_option.h"
26 #include "core/components/refresh/refresh_theme.h"
27 #include "core/components_ng/base/frame_node.h"
28 #include "core/components_ng/event/event_hub.h"
29 #include "core/components_ng/pattern/loading_progress/loading_progress_layout_property.h"
30 #include "core/components_ng/pattern/loading_progress/loading_progress_paint_property.h"
31 #include "core/components_ng/pattern/refresh/refresh_animation_state.h"
32 #include "core/components_ng/pattern/refresh/refresh_layout_property.h"
33 #include "core/components_ng/pattern/scrollable/scrollable_pattern.h"
34 #include "core/components_ng/property/property.h"
35 #include "core/components_ng/render/animation_utils.h"
36 #include "core/pipeline/base/element_register.h"
37 #include "core/pipeline_ng/pipeline_context.h"
38 #include "frameworks/base/i18n/localization.h"
39 #include "frameworks/base/utils/time_util.h"
40 #include "frameworks/base/utils/utils.h"
41 #include "frameworks/core/components/common/layout/constants.h"
42 #include "frameworks/core/components_ng/pattern/loading_progress/loading_progress_pattern.h"
43 #include "frameworks/core/components_ng/pattern/text/text_pattern.h"
44
45 namespace OHOS::Ace::NG {
46
47 namespace {
48 constexpr float PERCENT = 0.01f; // Percent
49 constexpr float FOLLOW_TO_RECYCLE_DURATION = 600.0f;
50 constexpr float CUSTOM_BUILDER_ANIMATION_DURATION = 100.0f;
51 constexpr float LOADING_ANIMATION_DURATION = 350.0f;
52 constexpr float MAX_OFFSET = 100000.0f;
53 constexpr float HALF = 0.5f;
54 constexpr float BASE_SCALE = 0.707f; // std::sqrt(2)/2
55 constexpr Dimension TRIGGER_REFRESH_WITH_TEXT_DISTANCE = 96.0_vp;
56 constexpr Dimension TRIGGER_REFRESH_DISTANCE = 64.0_vp;
57 constexpr Dimension MAX_SCROLL_DISTANCE = 128.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 constexpr Dimension LOADING_TEXT_TOP_MARGIN = 16.0_vp;
62 constexpr Dimension LOADING_TEXT_DISPLAY_DISTANCE = 80.0_vp;
63 } // namespace
64
65
GetTriggerRefreshDisTance()66 Dimension RefreshPattern::GetTriggerRefreshDisTance()
67 {
68 if (hasLoadingText_) {
69 return TRIGGER_REFRESH_WITH_TEXT_DISTANCE;
70 } else {
71 return TRIGGER_REFRESH_DISTANCE;
72 }
73 }
74
OnAttachToFrameNode()75 void RefreshPattern::OnAttachToFrameNode()
76 {
77 auto host = GetHost();
78 CHECK_NULL_VOID(host);
79 host->GetRenderContext()->SetClipToBounds(true);
80 host->GetRenderContext()->UpdateClipEdge(true);
81 }
82
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig & config)83 bool RefreshPattern::OnDirtyLayoutWrapperSwap(
84 const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& config)
85 {
86 if (isRemoveCustomBuilder_ || isTextNodeChanged_) {
87 UpdateFirstChildPlacement();
88 if (isRefreshing_) {
89 UpdateLoadingProgressStatus(RefreshAnimationState::RECYCLE, GetFollowRatio());
90 }
91 isRemoveCustomBuilder_ = false;
92 isTextNodeChanged_ = false;
93 } else if (progressChild_) {
94 auto host = GetHost();
95 CHECK_NULL_RETURN(host, false);
96 auto geometryNode = host->GetGeometryNode();
97 CHECK_NULL_RETURN(geometryNode, false);
98 auto refreshHeight = geometryNode->GetFrameSize().Height();
99 auto scrollOffset = std::clamp(scrollOffset_, 0.0f, refreshHeight);
100 UpdateScrollTransition(scrollOffset);
101 }
102 return false;
103 }
104
OnModifyDone()105 void RefreshPattern::OnModifyDone()
106 {
107 Pattern::OnModifyDone();
108 auto host = GetHost();
109 CHECK_NULL_VOID(host);
110 auto hub = host->GetEventHub<EventHub>();
111 CHECK_NULL_VOID(hub);
112 auto gestureHub = hub->GetOrCreateGestureEventHub();
113 CHECK_NULL_VOID(gestureHub);
114 auto layoutProperty = GetLayoutProperty<RefreshLayoutProperty>();
115 CHECK_NULL_VOID(layoutProperty);
116 hasLoadingText_ = layoutProperty->HasLoadingText();
117 refreshOffset_ = layoutProperty->GetRefreshOffset().value_or(GetTriggerRefreshDisTance());
118 if (LessOrEqual(refreshOffset_.Value(), 0)) {
119 refreshOffset_ = GetTriggerRefreshDisTance();
120 }
121 pullToRefresh_ = layoutProperty->GetPullToRefresh().value_or(true);
122 InitPanEvent(gestureHub);
123 InitOnKeyEvent();
124 InitChildNode();
125 if (Container::GreatOrEqualAPIVersionWithCheck(PlatformVersion::VERSION_ELEVEN)) {
126 InitOffsetProperty();
127 } else {
128 triggerLoadingDistance_ = static_cast<float>(
129 std::clamp(layoutProperty->GetIndicatorOffset().value_or(triggerLoadingDistanceTheme_).ConvertToPx(),
130 -1.0f * triggerLoadingDistanceTheme_.ConvertToPx(), GetTriggerRefreshDisTance().ConvertToPx()));
131 InitLowVersionOffset();
132 }
133 RefreshStatusChangeEffect();
134 SetAccessibilityAction();
135 }
136
CreateLayoutAlgorithm()137 RefPtr<LayoutAlgorithm> RefreshPattern::CreateLayoutAlgorithm()
138 {
139 auto refreshLayoutAlgorithm = MakeRefPtr<RefreshLayoutAlgorithm>();
140 if (isCustomBuilderExist_) {
141 refreshLayoutAlgorithm->SetCustomBuilderIndex(0);
142 if (Container::GreatOrEqualAPIVersionWithCheck(PlatformVersion::VERSION_ELEVEN)) {
143 refreshLayoutAlgorithm->SetBuilderMeasureBaseHeight(builderMeasureBaseHeight_);
144 } else {
145 refreshLayoutAlgorithm->SetCustomBuilderOffset(customBuilderOffset_);
146 refreshLayoutAlgorithm->SetScrollOffset(scrollOffset_);
147 }
148 }
149 return refreshLayoutAlgorithm;
150 }
151
InitPanEvent(const RefPtr<GestureEventHub> & gestureHub)152 void RefreshPattern::InitPanEvent(const RefPtr<GestureEventHub>& gestureHub)
153 {
154 if (panEvent_) {
155 return;
156 }
157 auto actionStartTask = [weak = WeakClaim(this)](const GestureEvent& info) {
158 TAG_LOGI(AceLogTag::ACE_REFRESH, "Drag start and drag motion triggered by self");
159 auto pattern = weak.Upgrade();
160 CHECK_NULL_VOID(pattern);
161 auto speed = static_cast<float>(info.GetMainVelocity());
162 pattern->UpdateDragFRCSceneInfo(REFRESH_DRAG_SCENE, speed, SceneStatus::START);
163 pattern->HandleDragStart(true, speed);
164 };
165 auto actionUpdateTask = [weak = WeakClaim(this)](const GestureEvent& info) {
166 auto pattern = weak.Upgrade();
167 CHECK_NULL_VOID(pattern);
168 pattern->HandleDragUpdate(static_cast<float>(info.GetMainDelta()), static_cast<float>(info.GetMainVelocity()));
169 };
170 auto actionEndTask = [weak = WeakClaim(this)](const GestureEvent& info) {
171 TAG_LOGI(AceLogTag::ACE_REFRESH, "Drag end and drag motion triggered by self");
172 auto pattern = weak.Upgrade();
173 CHECK_NULL_VOID(pattern);
174 auto speed = static_cast<float>(info.GetMainVelocity());
175 pattern->UpdateDragFRCSceneInfo(REFRESH_DRAG_SCENE, speed, SceneStatus::END);
176 pattern->HandleDragEnd(speed);
177 };
178 auto actionCancelTask = [weak = WeakClaim(this)]() {
179 TAG_LOGI(AceLogTag::ACE_REFRESH, "Drag cancel and drag motion triggered by self");
180 auto pattern = weak.Upgrade();
181 CHECK_NULL_VOID(pattern);
182 pattern->HandleDragCancel();
183 };
184 PanDirection panDirection;
185 panDirection.type = PanDirection::VERTICAL;
186 if (panEvent_) {
187 gestureHub->RemovePanEvent(panEvent_);
188 }
189
190 panEvent_ = MakeRefPtr<PanEvent>(
191 std::move(actionStartTask), std::move(actionUpdateTask), std::move(actionEndTask), std::move(actionCancelTask));
192 gestureHub->AddPanEvent(panEvent_, panDirection, 1, DEFAULT_PAN_DISTANCE);
193 if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_THIRTEEN)) {
194 gestureHub->SetIsAllowMouse(false);
195 }
196 }
197
InitOnKeyEvent()198 void RefreshPattern::InitOnKeyEvent()
199 {
200 if (isKeyEventRegisted_) {
201 return;
202 }
203 auto host = GetHost();
204 CHECK_NULL_VOID(host);
205 auto focusHub = host->GetFocusHub();
206 CHECK_NULL_VOID(focusHub);
207 auto onKeyEvent = [wp = WeakClaim(this)](const KeyEvent& event) -> bool {
208 auto pattern = wp.Upgrade();
209 CHECK_NULL_RETURN(pattern, false);
210 return pattern->OnKeyEvent(event);
211 };
212 isKeyEventRegisted_ = true;
213 focusHub->SetOnKeyEventInternal(std::move(onKeyEvent));
214 }
215
InitProgressNode()216 void RefreshPattern::InitProgressNode()
217 {
218 auto host = GetHost();
219 CHECK_NULL_VOID(host);
220 progressChild_ = FrameNode::CreateFrameNode(V2::LOADING_PROGRESS_ETS_TAG,
221 ElementRegister::GetInstance()->MakeUniqueId(), AceType::MakeRefPtr<LoadingProgressPattern>());
222 CHECK_NULL_VOID(progressChild_);
223 host->AddChild(progressChild_, 0);
224 auto gestureHub = progressChild_->GetEventHub<EventHub>();
225 if (gestureHub) {
226 gestureHub->SetEnabled(false);
227 }
228 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
229 CHECK_NULL_VOID(progressPaintProperty);
230 progressPaintProperty->UpdateLoadingProgressOwner(LoadingProgressOwner::REFRESH);
231 auto context = host->GetContext();
232 if (context) {
233 auto theme = context->GetTheme<RefreshTheme>();
234 if (theme) {
235 loadingProgressSizeTheme_ = theme->GetProgressDiameter();
236 triggerLoadingDistanceTheme_ = theme->GetLoadingDistance();
237 progressPaintProperty->UpdateColor(theme->GetProgressColor());
238 }
239 }
240 auto progressLayoutProperty = progressChild_->GetLayoutProperty<LoadingProgressLayoutProperty>();
241 CHECK_NULL_VOID(progressLayoutProperty);
242 progressLayoutProperty->UpdateUserDefinedIdealSize(
243 CalcSize(CalcLength(loadingProgressSizeTheme_.ConvertToPx()),
244 CalcLength(loadingProgressSizeTheme_.ConvertToPx())));
245 progressChild_->MarkDirtyNode();
246 }
247
UpdateLoadingTextOpacity(float opacity)248 void RefreshPattern::UpdateLoadingTextOpacity(float opacity)
249 {
250 CHECK_NULL_VOID(loadingTextNode_);
251 auto loadingTextRenderContext = loadingTextNode_->GetRenderContext();
252 CHECK_NULL_VOID(loadingTextRenderContext);
253 if (opacity > 0.0f) {
254 opacity = std::clamp(scrollOffset_ - static_cast<float>(LOADING_TEXT_DISPLAY_DISTANCE.ConvertToPx()), 0.0f,
255 static_cast<float>(TRIGGER_REFRESH_WITH_TEXT_DISTANCE.ConvertToPx() -
256 LOADING_TEXT_DISPLAY_DISTANCE.ConvertToPx())) /
257 static_cast<float>(
258 TRIGGER_REFRESH_WITH_TEXT_DISTANCE.ConvertToPx() - LOADING_TEXT_DISPLAY_DISTANCE.ConvertToPx());
259 }
260 loadingTextRenderContext->UpdateOpacity(opacity);
261 }
262
InitProgressColumn()263 void RefreshPattern::InitProgressColumn()
264 {
265 auto host = GetHost();
266 CHECK_NULL_VOID(host);
267 columnNode_ = FrameNode::CreateFrameNode(V2::COLUMN_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(),
268 AceType::MakeRefPtr<LinearLayoutPattern>(true));
269 loadingTextNode_ = FrameNode::CreateFrameNode(
270 V2::TEXT_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(), AceType::MakeRefPtr<TextPattern>());
271 auto loadingTextLayoutProperty = loadingTextNode_->GetLayoutProperty<TextLayoutProperty>();
272 CHECK_NULL_VOID(loadingTextLayoutProperty);
273 auto layoutProperty = host->GetLayoutProperty<RefreshLayoutProperty>();
274 CHECK_NULL_VOID(layoutProperty);
275 loadingTextLayoutProperty->UpdateContent(layoutProperty->GetLoadingTextValue());
276 loadingTextLayoutProperty->UpdateMaxLines(1);
277 loadingTextLayoutProperty->UpdateMaxFontScale(2.0f);
278 loadingTextLayoutProperty->UpdateTextOverflow(TextOverflow::ELLIPSIS);
279 auto context = host->GetContext();
280 CHECK_NULL_VOID(context);
281 auto theme = context->GetTheme<RefreshTheme>();
282 CHECK_NULL_VOID(theme);
283 loadingTextLayoutProperty->UpdateTextColor(theme->GetTextStyle().GetTextColor());
284 loadingTextLayoutProperty->UpdateFontSize(theme->GetTextStyle().GetFontSize());
285
286 PaddingProperty textpadding;
287 textpadding.top = CalcLength(loadingProgressSizeTheme_.ConvertToPx());
288 auto prop = columnNode_->GetLayoutProperty<LinearLayoutProperty>();
289 prop->UpdatePadding(textpadding);
290 UpdateLoadingTextOpacity(0.0f);
291
292 columnNode_->AddChild(loadingTextNode_, -1);
293 host->AddChild(columnNode_, 0);
294 }
295
OnColorConfigurationUpdate()296 void RefreshPattern::OnColorConfigurationUpdate()
297 {
298 if (isCustomBuilderExist_) {
299 return;
300 }
301 CHECK_NULL_VOID(progressChild_);
302 auto pipelineContext = GetContext();
303 CHECK_NULL_VOID(pipelineContext);
304 auto theme = pipelineContext->GetTheme<RefreshTheme>();
305 CHECK_NULL_VOID(theme);
306 auto layoutProperty = GetLayoutProperty<RefreshLayoutProperty>();
307 CHECK_NULL_VOID(layoutProperty);
308 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
309 CHECK_NULL_VOID(progressPaintProperty);
310 progressPaintProperty->UpdateColor(theme->GetProgressColor());
311 if (hasLoadingText_) {
312 CHECK_NULL_VOID(loadingTextNode_);
313 auto textLayoutProperty = loadingTextNode_->GetLayoutProperty<TextLayoutProperty>();
314 CHECK_NULL_VOID(textLayoutProperty);
315 textLayoutProperty->UpdateFontSize(theme->GetTextStyle().GetFontSize());
316 textLayoutProperty->UpdateTextColor(theme->GetTextStyle().GetTextColor());
317 }
318 }
319
InitChildNode()320 void RefreshPattern::InitChildNode()
321 {
322 if (isCustomBuilderExist_) {
323 return;
324 }
325 auto host = GetHost();
326 CHECK_NULL_VOID(host);
327 auto accessibilityProperty = host->GetAccessibilityProperty<NG::AccessibilityProperty>();
328 CHECK_NULL_VOID(accessibilityProperty);
329 auto accessibilityLevel = accessibilityProperty->GetAccessibilityLevel();
330 if (!progressChild_) {
331 InitProgressNode();
332 if (Container::GreatOrEqualAPIVersionWithCheck(PlatformVersion::VERSION_ELEVEN)) {
333 CHECK_NULL_VOID(progressChild_);
334 auto progressContext = progressChild_->GetRenderContext();
335 CHECK_NULL_VOID(progressContext);
336 progressContext->UpdateOpacity(0.0f);
337 } else {
338 UpdateLoadingProgress();
339 }
340 }
341 CHECK_NULL_VOID(progressChild_);
342 auto progressAccessibilityProperty = progressChild_->GetAccessibilityProperty<AccessibilityProperty>();
343 CHECK_NULL_VOID(progressAccessibilityProperty);
344 progressAccessibilityProperty->SetAccessibilityLevel(accessibilityLevel);
345
346 if (hasLoadingText_ && !loadingTextNode_) {
347 InitProgressColumn();
348 isTextNodeChanged_ = true;
349 } else if (!hasLoadingText_ && loadingTextNode_) {
350 host->RemoveChild(columnNode_);
351 columnNode_ = nullptr;
352 loadingTextNode_ = nullptr;
353 isTextNodeChanged_ = true;
354 host->MarkDirtyNode(PROPERTY_UPDATE_LAYOUT);
355 }
356
357 if (hasLoadingText_ && loadingTextNode_) {
358 auto loadingTextLayoutProperty = loadingTextNode_->GetLayoutProperty<TextLayoutProperty>();
359 CHECK_NULL_VOID(loadingTextLayoutProperty);
360 auto layoutProperty = host->GetLayoutProperty<RefreshLayoutProperty>();
361 CHECK_NULL_VOID(layoutProperty);
362 loadingTextLayoutProperty->UpdateContent(layoutProperty->GetLoadingTextValue());
363 loadingTextNode_->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
364 auto textAccessibilityProperty = loadingTextNode_->GetAccessibilityProperty<AccessibilityProperty>();
365 CHECK_NULL_VOID(textAccessibilityProperty);
366 textAccessibilityProperty->SetAccessibilityLevel(accessibilityLevel);
367 }
368 }
369
RefreshStatusChangeEffect()370 void RefreshPattern::RefreshStatusChangeEffect()
371 {
372 auto layoutProperty = GetLayoutProperty<RefreshLayoutProperty>();
373 CHECK_NULL_VOID(layoutProperty);
374 auto refreshingProp = layoutProperty->GetIsRefreshing().value_or(false);
375 if (isRefreshing_ != refreshingProp) {
376 if (refreshingProp) {
377 QuickStartFresh();
378 } else {
379 QuickEndFresh();
380 }
381 }
382 }
383
QuickStartFresh()384 void RefreshPattern::QuickStartFresh()
385 {
386 UpdateRefreshStatus(RefreshStatus::REFRESH);
387 if (Container::GreatOrEqualAPIVersionWithCheck(PlatformVersion::VERSION_ELEVEN)) {
388 QuickFirstChildAppear();
389 return;
390 }
391
392 if (isCustomBuilderExist_) {
393 CustomBuilderRefreshingAnimation(false);
394 } else {
395 LoadingProgressRefreshingAnimation(false);
396 }
397 }
398
QuickEndFresh()399 void RefreshPattern::QuickEndFresh()
400 {
401 SwitchToFinish();
402 if (Container::GreatOrEqualAPIVersionWithCheck(PlatformVersion::VERSION_ELEVEN)) {
403 QuickFirstChildDisappear();
404 return;
405 }
406
407 if (isCustomBuilderExist_) {
408 CustomBuilderExit();
409 } else {
410 LoadingProgressExit();
411 }
412 }
413
OnKeyEvent(const KeyEvent & event)414 bool RefreshPattern::OnKeyEvent(const KeyEvent& event)
415 {
416 if (event.code == KeyCode::KEY_F5 || (event.IsCombinationKey() && event.IsCtrlWith(KeyCode::KEY_R))) {
417 if (!isRefreshing_) {
418 QuickStartFresh();
419 }
420 return true;
421 }
422 return false;
423 }
424
HandleDragStart(bool isDrag,float mainSpeed)425 void RefreshPattern::HandleDragStart(bool isDrag, float mainSpeed)
426 {
427 if (Container::GreatOrEqualAPIVersionWithCheck(PlatformVersion::VERSION_ELEVEN)) {
428 isSourceFromAnimation_ = !isDrag;
429 ResetAnimation();
430 } else {
431 HandleDragStartLowVersion();
432 }
433 // AccessibilityEventType::SCROLL_START
434 }
435
HandleDragUpdate(float delta,float mainSpeed)436 ScrollResult RefreshPattern::HandleDragUpdate(float delta, float mainSpeed)
437 {
438 UpdateDragFRCSceneInfo(REFRESH_DRAG_SCENE, mainSpeed, SceneStatus::RUNNING);
439 if (Container::GreatOrEqualAPIVersionWithCheck(PlatformVersion::VERSION_ELEVEN)) {
440 // If dragging does not expand the refresh, there is no need to continue executing the code
441 if (NearZero(scrollOffset_) && NonPositive(delta)) {
442 return { delta, true };
443 }
444 auto pullDownRatio = CalculatePullDownRatio();
445 scrollOffset_ = std::clamp(scrollOffset_ + delta * pullDownRatio, 0.0f, MAX_OFFSET);
446 UpdateFirstChildPlacement();
447 FireOnOffsetChange(scrollOffset_);
448 if (!isSourceFromAnimation_) {
449 if (isRefreshing_) {
450 UpdateLoadingProgressStatus(RefreshAnimationState::RECYCLE, GetFollowRatio());
451 return { 0.f, true };
452 }
453 UpdateLoadingProgressStatus(RefreshAnimationState::FOLLOW_HAND, GetFollowRatio());
454 if (LessNotEqual(scrollOffset_, static_cast<float>(refreshOffset_.ConvertToPx()))) {
455 UpdateRefreshStatus(RefreshStatus::DRAG);
456 } else {
457 UpdateRefreshStatus(RefreshStatus::OVER_DRAG);
458 }
459 }
460 } else {
461 HandleDragUpdateLowVersion(delta);
462 }
463 return { 0.f, true };
464 }
465
HandleDragEnd(float speed)466 void RefreshPattern::HandleDragEnd(float speed)
467 {
468 if (Container::GreatOrEqualAPIVersionWithCheck(PlatformVersion::VERSION_ELEVEN)) {
469 SpeedTriggerAnimation(speed);
470 } else {
471 HandleDragEndLowVersion();
472 }
473 }
474
HandleDragCancel()475 void RefreshPattern::HandleDragCancel()
476 {
477 HandleDragEnd(0.0f);
478 }
479
CalculatePullDownRatio()480 float RefreshPattern::CalculatePullDownRatio()
481 {
482 auto host = GetHost();
483 CHECK_NULL_RETURN(host, 1.0f);
484 auto layoutProperty = GetLayoutProperty<RefreshLayoutProperty>();
485 CHECK_NULL_RETURN(layoutProperty, 1.f);
486 if (layoutProperty->GetPullDownRatio().has_value()) {
487 return layoutProperty->GetPullDownRatio().value();
488 }
489 auto geometryNode = host->GetGeometryNode();
490 CHECK_NULL_RETURN(geometryNode, 1.0f);
491 auto contentHeight = geometryNode->GetPaddingSize().Height();
492 if (NearZero(contentHeight)) {
493 return 1.0f;
494 }
495 if (!ratio_.has_value()) {
496 auto context = GetContext();
497 CHECK_NULL_RETURN(context, 1.0f);
498 auto refreshTheme = context->GetTheme<RefreshTheme>();
499 CHECK_NULL_RETURN(refreshTheme, 1.0f);
500 ratio_ = refreshTheme->GetRatio();
501 }
502 auto gamma = scrollOffset_ / contentHeight;
503 if (GreatOrEqual(gamma, 1.0)) {
504 gamma = 1.0f;
505 }
506 return exp(-ratio_.value() * gamma);
507 }
508
GetFollowRatio()509 float RefreshPattern::GetFollowRatio()
510 {
511 auto loadingVisibleHeight = GetLoadingVisibleHeight();
512 auto ratio = 0.0f;
513 if (!NearEqual(static_cast<float>(refreshOffset_.ConvertToPx()), loadingVisibleHeight)) {
514 ratio = static_cast<float>(
515 (scrollOffset_ - loadingVisibleHeight) / (refreshOffset_.ConvertToPx() - loadingVisibleHeight));
516 }
517 return std::clamp(ratio, 0.0f, 1.0f);
518 }
519
FireStateChange(int32_t value)520 void RefreshPattern::FireStateChange(int32_t value)
521 {
522 auto refreshEventHub = GetEventHub<RefreshEventHub>();
523 CHECK_NULL_VOID(refreshEventHub);
524 refreshEventHub->FireOnStateChange(value);
525 if (refreshStatus_ == RefreshStatus::REFRESH && Recorder::EventRecorder::Get().IsComponentRecordEnable()) {
526 auto host = GetHost();
527 CHECK_NULL_VOID(host);
528 auto inspectorId = host->GetInspectorId().value_or("");
529 Recorder::EventParamsBuilder builder;
530 builder.SetId(inspectorId)
531 .SetType(host->GetTag())
532 .SetEventType(Recorder::EventType::REFRESH)
533 .SetHost(host)
534 .SetDescription(host->GetAutoEventParamValue(""));
535 Recorder::EventRecorder::Get().OnEvent(std::move(builder));
536 }
537 }
538
FireRefreshing()539 void RefreshPattern::FireRefreshing()
540 {
541 auto refreshEventHub = GetEventHub<RefreshEventHub>();
542 CHECK_NULL_VOID(refreshEventHub);
543 refreshEventHub->FireOnRefreshing();
544 }
545
FireChangeEvent(const std::string & value)546 void RefreshPattern::FireChangeEvent(const std::string& value)
547 {
548 auto refreshEventHub = GetEventHub<RefreshEventHub>();
549 CHECK_NULL_VOID(refreshEventHub);
550 refreshEventHub->FireChangeEvent(value);
551 }
552
FireOnOffsetChange(float value)553 void RefreshPattern::FireOnOffsetChange(float value)
554 {
555 if (NearZero(value)) {
556 value = 0.0f;
557 }
558 if (!NearEqual(lastScrollOffset_, value)) {
559 UpdateCustomBuilderVisibility();
560 auto refreshEventHub = GetEventHub<RefreshEventHub>();
561 CHECK_NULL_VOID(refreshEventHub);
562 refreshEventHub->FireOnOffsetChange(Dimension(value).ConvertToVp());
563 lastScrollOffset_ = value;
564 }
565 }
566
UpdateCustomBuilderVisibility()567 void RefreshPattern::UpdateCustomBuilderVisibility()
568 {
569 if (!isCustomBuilderExist_) {
570 return;
571 }
572 CHECK_NULL_VOID(customBuilder_);
573 auto customBuilderLayoutProperty = customBuilder_->GetLayoutProperty();
574 CHECK_NULL_VOID(customBuilderLayoutProperty);
575 if (customBuilderLayoutProperty->IsUserSetVisibility()) {
576 return;
577 }
578 if (LessOrEqual(scrollOffset_, 0.0f)) {
579 customBuilderLayoutProperty->UpdateVisibility(VisibleType::INVISIBLE);
580 } else {
581 customBuilderLayoutProperty->UpdateVisibility(VisibleType::VISIBLE);
582 }
583 }
584
AddCustomBuilderNode(const RefPtr<NG::UINode> & builder)585 void RefreshPattern::AddCustomBuilderNode(const RefPtr<NG::UINode>& builder)
586 {
587 auto host = GetHost();
588 CHECK_NULL_VOID(host);
589 if (!builder) {
590 if (isCustomBuilderExist_) {
591 host->RemoveChild(customBuilder_);
592 isCustomBuilderExist_ = false;
593 customBuilder_ = nullptr;
594 isRemoveCustomBuilder_ = true;
595 TAG_LOGI(AceLogTag::ACE_REFRESH, "CustomNode doesn't exist");
596 }
597 return;
598 }
599
600 if (!isCustomBuilderExist_) {
601 if (progressChild_) {
602 if (columnNode_) {
603 host->RemoveChild(columnNode_);
604 columnNode_ = nullptr;
605 loadingTextNode_ = nullptr;
606 }
607 host->RemoveChild(progressChild_);
608 progressChild_ = nullptr;
609 }
610 host->AddChild(builder, 0);
611 UpdateFirstChildPlacement();
612 UpdateScrollTransition(0.f);
613 TAG_LOGI(AceLogTag::ACE_REFRESH, "CustomNode exists");
614 } else {
615 auto customNodeChild = host->GetFirstChild();
616 CHECK_NULL_VOID(customNodeChild);
617 if (customNodeChild != builder) {
618 host->ReplaceChild(customNodeChild, builder);
619 host->MarkDirtyNode(PROPERTY_UPDATE_LAYOUT);
620 }
621 }
622 customBuilder_ = AceType::DynamicCast<FrameNode>(builder);
623 isCustomBuilderExist_ = true;
624 UpdateCustomBuilderVisibility();
625 }
626
SetAccessibilityAction()627 void RefreshPattern::SetAccessibilityAction()
628 {
629 auto host = GetHost();
630 CHECK_NULL_VOID(host);
631 auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
632 CHECK_NULL_VOID(accessibilityProperty);
633 accessibilityProperty->SetActionScrollForward([weakPtr = WeakClaim(this)]() {
634 const auto& pattern = weakPtr.Upgrade();
635 CHECK_NULL_VOID(pattern);
636 if (pattern->IsRefreshing()) {
637 return;
638 }
639 pattern->HandleDragStart(true, 0.0f);
640 for (float delta = 0.0f; LessNotEqual(delta, static_cast<float>(MAX_SCROLL_DISTANCE.ConvertToPx()));
641 delta += pattern->triggerLoadingDistanceTheme_.ConvertToPx()) {
642 pattern->HandleDragUpdate(delta, 0.0f);
643 }
644 pattern->HandleDragEnd(0.0f);
645 });
646 }
647
InitCoordinationEvent(RefPtr<ScrollableCoordinationEvent> & coordinationEvent)648 void RefreshPattern::InitCoordinationEvent(RefPtr<ScrollableCoordinationEvent>& coordinationEvent)
649 {
650 auto onScrollEvent = [weak = WeakClaim(this)](float offset, float mainSpeed) -> bool {
651 auto pattern = weak.Upgrade();
652 CHECK_NULL_RETURN(pattern, false);
653 pattern->HandleDragUpdate(offset, mainSpeed);
654 return Positive(pattern->scrollOffset_) || NonNegative(offset);
655 };
656 coordinationEvent->SetOnScrollEvent(onScrollEvent);
657 auto onScrollStartEvent = [weak = WeakClaim(this)](bool isDrag, float mainSpeed) {
658 TAG_LOGI(AceLogTag::ACE_REFRESH, "Drag start and drag motion triggered by scrollable child");
659 auto pattern = weak.Upgrade();
660 CHECK_NULL_VOID(pattern);
661 pattern->HandleDragStart(isDrag, mainSpeed);
662 };
663 coordinationEvent->SetOnScrollStartEvent(onScrollStartEvent);
664 auto onScrollEndEvent = [weak = WeakClaim(this)](float speed) {
665 TAG_LOGI(AceLogTag::ACE_REFRESH, "Drag end and drag motion triggered by scrollable child");
666 auto pattern = weak.Upgrade();
667 CHECK_NULL_VOID(pattern);
668 pattern->HandleDragEnd(speed);
669 };
670 coordinationEvent->SetOnScrollEndEvent(onScrollEndEvent);
671 }
672
UpdateRefreshStatus(RefreshStatus newStatus)673 void RefreshPattern::UpdateRefreshStatus(RefreshStatus newStatus)
674 {
675 if (refreshStatus_ == newStatus) {
676 return;
677 }
678 refreshStatus_ = newStatus;
679 if (refreshStatus_ == RefreshStatus::REFRESH) {
680 isRefreshing_ = true;
681 // the two-way binding of 'refreshing' variable need to be changed before 'onRefreshing' function is triggered
682 FireChangeEvent("true");
683 FireRefreshing();
684 } else {
685 isRefreshing_ = false;
686 FireChangeEvent("false");
687 }
688 FireStateChange(static_cast<int>(refreshStatus_));
689 TAG_LOGI(AceLogTag::ACE_REFRESH, "Refresh status changed %{public}d", static_cast<int32_t>(refreshStatus_));
690 }
691
SwitchToFinish()692 void RefreshPattern::SwitchToFinish()
693 {
694 if (refreshStatus_ != RefreshStatus::REFRESH && refreshStatus_ != RefreshStatus::DONE) {
695 UpdateRefreshStatus(RefreshStatus::INACTIVE);
696 } else {
697 UpdateRefreshStatus(RefreshStatus::DONE);
698 }
699 }
700
UpdateLoadingProgressStatus(RefreshAnimationState state,float ratio)701 void RefreshPattern::UpdateLoadingProgressStatus(RefreshAnimationState state, float ratio)
702 {
703 // Need to check loadingProgress mode
704 CHECK_NULL_VOID(progressChild_);
705 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
706 CHECK_NULL_VOID(progressPaintProperty);
707 progressPaintProperty->UpdateRefreshAnimationState(state);
708 switch (state) {
709 case RefreshAnimationState::FOLLOW_HAND:
710 case RefreshAnimationState::RECYCLE:
711 progressPaintProperty->UpdateRefreshSizeScaleRatio(ratio);
712 break;
713 default:
714 break;
715 }
716 if (CheckNeedRender(progressPaintProperty->GetPropertyChangeFlag())) {
717 progressChild_->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
718 }
719 }
720
InitOffsetProperty()721 void RefreshPattern::InitOffsetProperty()
722 {
723 if (!offsetProperty_) {
724 auto propertyCallback = [weak = AceType::WeakClaim(this)](float scrollOffset) {
725 auto pattern = weak.Upgrade();
726 CHECK_NULL_VOID(pattern);
727 pattern->scrollOffset_ = scrollOffset;
728 pattern->UpdateFirstChildPlacement();
729 pattern->FireOnOffsetChange(scrollOffset);
730 };
731 offsetProperty_ = AceType::MakeRefPtr<NodeAnimatablePropertyFloat>(0.0, std::move(propertyCallback));
732 auto host = GetHost();
733 CHECK_NULL_VOID(host);
734 auto renderContext = host->GetRenderContext();
735 CHECK_NULL_VOID(renderContext);
736 renderContext->AttachNodeAnimatableProperty(offsetProperty_);
737 offsetProperty_->SetPropertyUnit(PropertyUnit::PIXEL_POSITION);
738 }
739 }
740
UpdateFirstChildPlacement()741 void RefreshPattern::UpdateFirstChildPlacement()
742 {
743 auto host = GetHost();
744 CHECK_NULL_VOID(host);
745 auto geometryNode = host->GetGeometryNode();
746 CHECK_NULL_VOID(geometryNode);
747 auto refreshHeight = geometryNode->GetFrameSize().Height();
748 auto scrollOffset = std::clamp(scrollOffset_, 0.0f, refreshHeight);
749 if (progressChild_) {
750 if (isSourceFromAnimation_) {
751 UpdateLoadingProgressTranslate(0.0f);
752 UpdateScrollTransition(scrollOffset);
753 } else {
754 UpdateLoadingProgressTranslate(scrollOffset);
755 UpdateScrollTransition(scrollOffset);
756 UpdateLoadingProgressStatus(GetLoadingProgressStatus(), GetFollowRatio());
757 }
758 } else {
759 UpdateBuilderHeight(scrollOffset);
760 }
761 }
762
UpdateScrollTransition(float scrollOffset)763 void RefreshPattern::UpdateScrollTransition(float scrollOffset)
764 {
765 auto host = GetHost();
766 CHECK_NULL_VOID(host);
767 int32_t childCount = host->TotalChildCount();
768 // If the refresh has no children without loadingProgress and text, it does not need to update offset.
769 if (childCount < 2 || (childCount == 2 && columnNode_)) { // 2 means loadingProgress and text child components.
770 return;
771 }
772 // Need to search for frameNode and skip ComponentNode
773 auto childNode = host->GetLastChild();
774 CHECK_NULL_VOID(childNode);
775 while (!AceType::InstanceOf<FrameNode>(childNode) && !childNode->GetChildren().empty()) {
776 childNode = childNode->GetFirstChild();
777 }
778 auto scrollableNode = AceType::DynamicCast<FrameNode>(childNode);
779 CHECK_NULL_VOID(scrollableNode);
780 auto scrollableRenderContext = scrollableNode->GetRenderContext();
781 CHECK_NULL_VOID(scrollableRenderContext);
782 scrollableRenderContext->UpdateTransformTranslate({ 0.0f, scrollOffset, 0.0f });
783 }
784
UpdateBuilderHeight(float builderHeight)785 void RefreshPattern::UpdateBuilderHeight(float builderHeight)
786 {
787 auto host = GetHost();
788 CHECK_NULL_VOID(host);
789 auto layoutProperty = GetLayoutProperty<RefreshLayoutProperty>();
790 CHECK_NULL_VOID(layoutProperty);
791 builderMeasureBaseHeight_ = builderHeight;
792 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
793 }
794
UpdateLoadingProgressTranslate(float scrollOffset)795 void RefreshPattern::UpdateLoadingProgressTranslate(float scrollOffset)
796 {
797 CHECK_NULL_VOID(progressChild_);
798 auto renderContext = progressChild_->GetRenderContext();
799 CHECK_NULL_VOID(renderContext);
800 auto loadingVisibleHeight = GetLoadingVisibleHeight();
801 if (GreatOrEqual(scrollOffset, loadingVisibleHeight) &&
802 !NearEqual(loadingVisibleHeight, static_cast<float>(GetTriggerRefreshDisTance().ConvertToPx()))) {
803 auto ratio = static_cast<float>(
804 (scrollOffset - loadingVisibleHeight) / (GetTriggerRefreshDisTance().ConvertToPx() - loadingVisibleHeight));
805 renderContext->UpdateOpacity(std::clamp(ratio, 0.0f, 1.0f));
806 renderContext->UpdateTransformTranslate({ 0.0f, (scrollOffset - loadingVisibleHeight) * HALF, 0.0f });
807 if (loadingTextNode_) {
808 UpdateLoadingTextOpacity(std::clamp(ratio, 0.0f, 1.0f));
809 auto loadingTextRenderContext = loadingTextNode_->GetRenderContext();
810 CHECK_NULL_VOID(loadingTextRenderContext);
811 loadingTextRenderContext->UpdateTransformTranslate({ 0.0f,
812 scrollOffset_ - triggerLoadingDistanceTheme_.ConvertToPx() - loadingProgressSizeTheme_.ConvertToPx() -
813 LOADING_TEXT_TOP_MARGIN.ConvertToPx(),
814 0.0f });
815 }
816 } else {
817 renderContext->UpdateOpacity(0.0f);
818 UpdateLoadingTextOpacity(0.0f);
819 }
820 }
821
GetLoadingVisibleHeight()822 float RefreshPattern::GetLoadingVisibleHeight()
823 {
824 float loadingHeight = 0.0f;
825 CHECK_NULL_RETURN(progressChild_, 0.0f);
826 auto renderContext = progressChild_->GetRenderContext();
827 CHECK_NULL_RETURN(renderContext, 0.0f);
828 auto geometryNode = progressChild_->GetGeometryNode();
829 CHECK_NULL_RETURN(geometryNode, 0.0f);
830 if (loadingTextNode_) {
831 auto loadingTextGeometryNode = loadingTextNode_->GetGeometryNode();
832 CHECK_NULL_RETURN(loadingTextGeometryNode, 0.0f);
833 loadingHeight = geometryNode->GetFrameSize().Height() + loadingTextGeometryNode->GetFrameSize().Height() +
834 LOADING_TEXT_TOP_MARGIN.ConvertToPx();
835 } else {
836 loadingHeight = geometryNode->GetFrameSize().Height();
837 }
838 return (HALF + BASE_SCALE * HALF) * loadingHeight;
839 }
840
SpeedTriggerAnimation(float speed)841 void RefreshPattern::SpeedTriggerAnimation(float speed)
842 {
843 auto targetOffset = (isSourceFromAnimation_ ||
844 LessNotEqual(scrollOffset_, refreshOffset_.ConvertToPx()) || !pullToRefresh_)
845 ? 0.0f
846 : refreshOffset_.ConvertToPx();
847 auto dealSpeed = 0.0f;
848 if (!NearEqual(scrollOffset_, targetOffset)) {
849 auto pullDownRatio = CalculatePullDownRatio();
850 dealSpeed = (pullDownRatio * speed) / (targetOffset - scrollOffset_);
851 } else if (NearZero(scrollOffset_) && NonPositive(speed)) {
852 SwitchToFinish();
853 return;
854 }
855 bool recycle = true;
856 if (pullToRefresh_ && !isSourceFromAnimation_ && refreshStatus_ == RefreshStatus::OVER_DRAG) {
857 UpdateRefreshStatus(RefreshStatus::REFRESH);
858 UpdateLoadingProgressStatus(RefreshAnimationState::FOLLOW_TO_RECYCLE, GetFollowRatio());
859 } else if (NearZero(targetOffset)) {
860 recycle = false;
861 SwitchToFinish();
862 }
863 ResetAnimation();
864 AnimationOption option;
865 auto curve = AceType::MakeRefPtr<InterpolatingSpring>(dealSpeed, 1.0f, 228.0f, 30.0f);
866 option.SetCurve(curve);
867 animation_ = AnimationUtils::StartAnimation(
868 option,
869 [&, weak = AceType::WeakClaim(this)]() {
870 auto pattern = weak.Upgrade();
871 CHECK_NULL_VOID(pattern);
872 pattern->offsetProperty_->Set(targetOffset);
873 },
874 [weak = AceType::WeakClaim(this), recycle]() {
875 auto pattern = weak.Upgrade();
876 CHECK_NULL_VOID(pattern);
877 if (recycle) {
878 pattern->UpdateLoadingProgressStatus(RefreshAnimationState::RECYCLE, pattern->GetFollowRatio());
879 }
880 });
881 auto context = GetContext();
882 CHECK_NULL_VOID(context);
883 context->RequestFrame();
884 }
885
GetTargetOffset()886 float RefreshPattern::GetTargetOffset()
887 {
888 if (isSourceFromAnimation_) {
889 return 0.0f;
890 }
891 auto targetOffset = 0.0f;
892 switch (refreshStatus_) {
893 case RefreshStatus::OVER_DRAG:
894 case RefreshStatus::REFRESH:
895 targetOffset = refreshOffset_.ConvertToPx();
896 break;
897 default:
898 targetOffset = 0.0f;
899 break;
900 }
901 return targetOffset;
902 }
903
SpeedAnimationFinish()904 void RefreshPattern::SpeedAnimationFinish()
905 {
906 if (isRefreshing_) {
907 UpdateLoadingProgressStatus(RefreshAnimationState::RECYCLE, GetFollowRatio());
908 } else {
909 UpdateLoadingProgressStatus(RefreshAnimationState::FOLLOW_HAND, GetFollowRatio());
910 }
911 }
912
QuickFirstChildAppear()913 void RefreshPattern::QuickFirstChildAppear()
914 {
915 isSourceFromAnimation_ = false;
916 UpdateLoadingProgressStatus(RefreshAnimationState::RECYCLE, GetFollowRatio());
917 ResetAnimation();
918 AnimationOption option;
919 option.SetCurve(DEFAULT_CURVE);
920 option.SetDuration(LOADING_ANIMATION_DURATION);
921 animation_ = AnimationUtils::StartAnimation(
922 option, [&]() { offsetProperty_->Set(static_cast<float>(refreshOffset_.ConvertToPx())); });
923 }
924
QuickFirstChildDisappear()925 void RefreshPattern::QuickFirstChildDisappear()
926 {
927 ResetAnimation();
928 AnimationOption option;
929 option.SetCurve(DEFAULT_CURVE);
930 option.SetDuration(LOADING_ANIMATION_DURATION);
931 animation_ = AnimationUtils::StartAnimation(
932 option, [&]() { offsetProperty_->Set(0.0f); },
933 [weak = AceType::WeakClaim(this)]() {
934 auto pattern = weak.Upgrade();
935 CHECK_NULL_VOID(pattern);
936 pattern->SpeedAnimationFinish();
937 });
938 }
939
GetLoadingProgressStatus()940 RefreshAnimationState RefreshPattern::GetLoadingProgressStatus()
941 {
942 auto defaultValue = RefreshAnimationState::FOLLOW_HAND;
943 CHECK_NULL_RETURN(progressChild_, defaultValue);
944 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
945 CHECK_NULL_RETURN(progressPaintProperty, defaultValue);
946 return progressPaintProperty->GetRefreshAnimationState().value_or(defaultValue);
947 }
948
ResetAnimation()949 void RefreshPattern::ResetAnimation()
950 {
951 float currentOffset = scrollOffset_;
952 if (Container::GreatOrEqualAPIVersionWithCheck(PlatformVersion::VERSION_ELEVEN)) {
953 AnimationOption option;
954 option.SetCurve(DEFAULT_CURVE);
955 option.SetDuration(0);
956 animation_ =
957 AnimationUtils::StartAnimation(option, [weak = AceType::WeakClaim(this), offset = currentOffset]() {
958 auto pattern = weak.Upgrade();
959 CHECK_NULL_VOID(pattern);
960 auto offsetProperty = pattern->offsetProperty_;
961 CHECK_NULL_VOID(offsetProperty);
962 offsetProperty->Set(offset);
963 });
964 } else {
965 AnimationUtils::StopAnimation(animation_);
966 CHECK_NULL_VOID(lowVersionOffset_);
967 lowVersionOffset_->Set(currentOffset);
968 }
969 }
970
UpdateDragFRCSceneInfo(const std::string & scene,float speed,SceneStatus sceneStatus)971 void RefreshPattern::UpdateDragFRCSceneInfo(const std::string& scene, float speed, SceneStatus sceneStatus)
972 {
973 auto host = GetHost();
974 CHECK_NULL_VOID(host);
975 host->AddFRCSceneInfo(scene, std::abs(speed), sceneStatus);
976 }
977
InitLowVersionOffset()978 void RefreshPattern::InitLowVersionOffset()
979 {
980 if (!lowVersionOffset_) {
981 auto propertyCallback = [weak = AceType::WeakClaim(this)](float scrollOffset) {
982 auto pattern = weak.Upgrade();
983 CHECK_NULL_VOID(pattern);
984 pattern->scrollOffset_ = scrollOffset;
985 pattern->UpdateChild();
986 };
987 lowVersionOffset_ = AceType::MakeRefPtr<NodeAnimatablePropertyFloat>(0.0, std::move(propertyCallback));
988 auto host = GetHost();
989 CHECK_NULL_VOID(host);
990 auto renderContext = host->GetRenderContext();
991 CHECK_NULL_VOID(renderContext);
992 renderContext->AttachNodeAnimatableProperty(lowVersionOffset_);
993 }
994 }
995
UpdateChild()996 void RefreshPattern::UpdateChild()
997 {
998 if (customBuilder_) {
999 UpdateCustomBuilderProperty();
1000 } else {
1001 UpdateLoadingProgress();
1002 }
1003 }
1004
HandleDragStartLowVersion()1005 void RefreshPattern::HandleDragStartLowVersion()
1006 {
1007 if (isRefreshing_) {
1008 return;
1009 }
1010 scrollOffset_ = 0.0f;
1011 UpdateLoadingProgressStatus(RefreshAnimationState::FOLLOW_HAND, 0.0f);
1012 }
1013
HandleDragUpdateLowVersion(float delta)1014 void RefreshPattern::HandleDragUpdateLowVersion(float delta)
1015 {
1016 if (isRefreshing_) {
1017 return;
1018 }
1019 scrollOffset_ = GetScrollOffset(delta);
1020 if (LessNotEqual(scrollOffset_, static_cast<float>(GetTriggerRefreshDisTance().ConvertToPx()))) {
1021 UpdateRefreshStatus(RefreshStatus::DRAG);
1022 } else {
1023 UpdateRefreshStatus(RefreshStatus::OVER_DRAG);
1024 }
1025 if (customBuilder_) {
1026 HandleCustomBuilderDragUpdateStage();
1027 return;
1028 }
1029 UpdateLoadingProgress();
1030 if (GreatNotEqual(scrollOffset_, triggerLoadingDistance_)) {
1031 CHECK_NULL_VOID(progressChild_);
1032 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
1033 CHECK_NULL_VOID(progressPaintProperty);
1034 float triggerRefreshDistance = GetTriggerRefreshDisTance().ConvertToPx();
1035 float ratio =
1036 NearEqual(triggerRefreshDistance, triggerLoadingDistance_)
1037 ? 1.0f
1038 : (scrollOffset_ - triggerLoadingDistance_) / (triggerRefreshDistance - triggerLoadingDistance_);
1039 progressPaintProperty->UpdateRefreshSizeScaleRatio(std::clamp(ratio, 0.0f, 1.0f));
1040 }
1041 }
1042
HandleDragEndLowVersion()1043 void RefreshPattern::HandleDragEndLowVersion()
1044 {
1045 if (isRefreshing_) {
1046 return;
1047 }
1048 if (customBuilder_) {
1049 HandleCustomBuilderDragEndStage();
1050 return;
1051 }
1052 if (refreshStatus_ == RefreshStatus::OVER_DRAG) {
1053 UpdateRefreshStatus(RefreshStatus::REFRESH);
1054 LoadingProgressRefreshingAnimation(true);
1055 } else {
1056 SwitchToFinish();
1057 LoadingProgressExit();
1058 }
1059 // AccessibilityEventType::SCROLL_END
1060 }
1061
LoadingProgressRefreshingAnimation(bool isDrag)1062 void RefreshPattern::LoadingProgressRefreshingAnimation(bool isDrag)
1063 {
1064 UpdateLoadingProgressStatus(RefreshAnimationState::RECYCLE, 1.0f);
1065 ResetAnimation();
1066 CHECK_NULL_VOID(lowVersionOffset_);
1067 AnimationOption option;
1068 if (isDrag) {
1069 option.SetCurve(AceType::MakeRefPtr<SpringCurve>(0.0f, 1.0f, 228.0f, 30.0f));
1070 option.SetDuration(FOLLOW_TO_RECYCLE_DURATION);
1071 } else {
1072 option.SetCurve(DEFAULT_CURVE);
1073 option.SetDuration(LOADING_ANIMATION_DURATION);
1074 }
1075 animation_ = AnimationUtils::StartAnimation(
1076 option, [&]() { lowVersionOffset_->Set(GetTriggerRefreshDisTance().ConvertToPx()); });
1077 }
1078
LoadingProgressExit()1079 void RefreshPattern::LoadingProgressExit()
1080 {
1081 ResetAnimation();
1082 CHECK_NULL_VOID(lowVersionOffset_);
1083 AnimationOption option;
1084 option.SetCurve(DEFAULT_CURVE);
1085 option.SetDuration(LOADING_ANIMATION_DURATION);
1086 animation_ = AnimationUtils::StartAnimation(
1087 option, [&]() { lowVersionOffset_->Set(0.0f); },
1088 [weak = AceType::WeakClaim(this)]() {
1089 auto pattern = weak.Upgrade();
1090 CHECK_NULL_VOID(pattern);
1091 pattern->UpdateLoadingProgressStatus(RefreshAnimationState::FOLLOW_HAND, 0.0f);
1092 });
1093 }
1094
UpdateLoadingProgress()1095 void RefreshPattern::UpdateLoadingProgress()
1096 {
1097 CHECK_NULL_VOID(progressChild_);
1098 float loadingProgressOffset =
1099 std::clamp(scrollOffset_, triggerLoadingDistance_, static_cast<float>(MAX_SCROLL_DISTANCE.ConvertToPx()));
1100 UpdateLoadingMarginTop(loadingProgressOffset);
1101 float triggerRefreshDistance = GetTriggerRefreshDisTance().ConvertToPx();
1102 float ratio = NearEqual(triggerRefreshDistance, triggerLoadingDistance_)
1103 ? 1.0f
1104 : (loadingProgressOffset - triggerLoadingDistance_) /
1105 (GetTriggerRefreshDisTance().ConvertToPx() - triggerLoadingDistance_);
1106 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
1107 CHECK_NULL_VOID(progressPaintProperty);
1108 progressPaintProperty->UpdateRefreshSizeScaleRatio(ratio);
1109 auto progressContext = progressChild_->GetRenderContext();
1110 CHECK_NULL_VOID(progressContext);
1111 progressContext->UpdateOpacity(std::clamp(ratio, 0.0f, 1.0f));
1112 UpdateLoadingTextOpacity(std::clamp(ratio, 0.0f, 1.0f));
1113 progressChild_->MarkDirtyNode(PROPERTY_UPDATE_LAYOUT);
1114 }
1115
CustomBuilderRefreshingAnimation(bool isDrag)1116 void RefreshPattern::CustomBuilderRefreshingAnimation(bool isDrag)
1117 {
1118 ResetAnimation();
1119 CHECK_NULL_VOID(lowVersionOffset_);
1120 AnimationOption option;
1121 if (isDrag) {
1122 option.SetCurve(AceType::MakeRefPtr<SpringCurve>(0.0f, 1.0f, 228.0f, 30.0f));
1123 option.SetDuration(FOLLOW_TO_RECYCLE_DURATION);
1124 } else {
1125 option.SetCurve(DEFAULT_CURVE);
1126 option.SetDuration(CUSTOM_BUILDER_ANIMATION_DURATION);
1127 }
1128 animation_ = AnimationUtils::StartAnimation(
1129 option, [&]() { lowVersionOffset_->Set(GetTriggerRefreshDisTance().ConvertToPx()); });
1130 }
1131
CustomBuilderExit()1132 void RefreshPattern::CustomBuilderExit()
1133 {
1134 ResetAnimation();
1135 CHECK_NULL_VOID(lowVersionOffset_);
1136 AnimationOption option;
1137 option.SetDuration(CUSTOM_BUILDER_ANIMATION_DURATION);
1138 option.SetCurve(DEFAULT_CURVE);
1139 animation_ = AnimationUtils::StartAnimation(option, [&]() { lowVersionOffset_->Set(0.0f); });
1140 }
1141
UpdateCustomBuilderProperty()1142 void RefreshPattern::UpdateCustomBuilderProperty()
1143 {
1144 auto customBuilderSize = customBuilder_->GetGeometryNode()->GetFrameSize();
1145 auto maxScroll = static_cast<float>(MAX_SCROLL_DISTANCE.ConvertToPx());
1146 customBuilderOffset_ = std::clamp(scrollOffset_, triggerLoadingDistance_, maxScroll - customBuilderSize.Height());
1147 float triggerRefreshDistance = GetTriggerRefreshDisTance().ConvertToPx();
1148 float ratio = NearEqual(triggerRefreshDistance, triggerLoadingDistance_)
1149 ? 1.0f
1150 : (customBuilderOffset_ - triggerLoadingDistance_) /
1151 (GetTriggerRefreshDisTance().ConvertToPx() - triggerLoadingDistance_);
1152 auto customBuilderContext = customBuilder_->GetRenderContext();
1153 CHECK_NULL_VOID(customBuilderContext);
1154 customBuilderContext->UpdateOpacity(std::clamp(ratio, 0.0f, 1.0f));
1155 auto host = GetHost();
1156 CHECK_NULL_VOID(host);
1157 host->MarkDirtyNode(PROPERTY_UPDATE_LAYOUT);
1158 }
1159
HandleCustomBuilderDragUpdateStage()1160 void RefreshPattern::HandleCustomBuilderDragUpdateStage()
1161 {
1162 auto customBuilderSize = customBuilder_->GetGeometryNode()->GetMarginFrameSize();
1163 auto maxScroll = MAX_SCROLL_DISTANCE.ConvertToPx();
1164 if (NearZero(static_cast<double>(customBuilder_->GetGeometryNode()->GetMarginFrameSize().Height()))) {
1165 return;
1166 }
1167 if (LessNotEqual(static_cast<double>(maxScroll - customBuilderSize.Height()),
1168 static_cast<double>(triggerLoadingDistance_))) {
1169 return;
1170 }
1171 UpdateCustomBuilderProperty();
1172 }
1173
HandleCustomBuilderDragEndStage()1174 void RefreshPattern::HandleCustomBuilderDragEndStage()
1175 {
1176 if (refreshStatus_ == RefreshStatus::OVER_DRAG) {
1177 UpdateRefreshStatus(RefreshStatus::REFRESH);
1178 CustomBuilderRefreshingAnimation(true);
1179 } else {
1180 SwitchToFinish();
1181 CustomBuilderExit();
1182 }
1183 }
1184
UpdateLoadingMarginTop(float top)1185 void RefreshPattern::UpdateLoadingMarginTop(float top)
1186 {
1187 CHECK_NULL_VOID(progressChild_);
1188 auto progressLayoutProperty = progressChild_->GetLayoutProperty<LoadingProgressLayoutProperty>();
1189 CHECK_NULL_VOID(progressLayoutProperty);
1190 MarginProperty marginProperty;
1191 marginProperty.left = CalcLength(0.0f);
1192 marginProperty.right = CalcLength(0.0f);
1193 marginProperty.bottom = CalcLength(0.0f);
1194 marginProperty.top = CalcLength(top);
1195 progressLayoutProperty->UpdateMargin(marginProperty);
1196 }
1197
GetScrollOffset(float delta)1198 float RefreshPattern::GetScrollOffset(float delta)
1199 {
1200 auto layoutProperty = GetLayoutProperty<RefreshLayoutProperty>();
1201 CHECK_NULL_RETURN(layoutProperty, 0.0f);
1202 auto frictionRatio = static_cast<float>(layoutProperty->GetFriction().value_or(DEFAULT_FRICTION)) * PERCENT;
1203 auto scrollY = delta * frictionRatio;
1204 return std::clamp(scrollOffset_ + scrollY, 0.0f, static_cast<float>(MAX_SCROLL_DISTANCE.ConvertToPx()));
1205 }
1206
HandleScroll(float offset,int32_t source,NestedState state,float velocity)1207 ScrollResult RefreshPattern::HandleScroll(float offset, int32_t source, NestedState state, float velocity)
1208 {
1209 ScrollResult result = { offset, true };
1210 auto nestedScroll = GetNestedScroll();
1211 if (NearZero(offset)) {
1212 return result;
1213 }
1214 isSourceFromAnimation_ = (source == SCROLL_FROM_ANIMATION);
1215 auto parent = GetNestedScrollParent();
1216 if (state == NestedState::CHILD_SCROLL) {
1217 if (Negative(offset) && Positive(scrollOffset_)) {
1218 if (parent && nestedScroll.forward == NestedScrollMode::PARENT_FIRST) {
1219 result = parent->HandleScroll(offset, source, NestedState::CHILD_SCROLL, velocity);
1220 result = HandleDragUpdate(result.remain, velocity);
1221 } else if (parent && nestedScroll.forward == NestedScrollMode::SELF_FIRST) {
1222 result = HandleDragUpdate(offset, velocity);
1223 result = parent->HandleScroll(result.remain, source, NestedState::CHILD_SCROLL, velocity);
1224 } else {
1225 result = HandleDragUpdate(offset, velocity);
1226 }
1227 } else {
1228 bool selfScroll = !parent || ((Negative(offset) && (nestedScroll.forward == NestedScrollMode::SELF_ONLY ||
1229 nestedScroll.forward == NestedScrollMode::PARALLEL)) ||
1230 (Positive(offset) && (nestedScroll.backward == NestedScrollMode::SELF_ONLY ||
1231 nestedScroll.backward == NestedScrollMode::PARALLEL)));
1232 if (!selfScroll) {
1233 result = parent->HandleScroll(offset, source, NestedState::CHILD_SCROLL, velocity);
1234 }
1235 }
1236 } else if (state == NestedState::CHILD_OVER_SCROLL) {
1237 bool parentScroll = parent && ((Negative(offset) && nestedScroll.forward == NestedScrollMode::SELF_FIRST) ||
1238 (Positive(offset) && nestedScroll.backward == NestedScrollMode::SELF_FIRST));
1239 if (parentScroll) {
1240 result = parent->HandleScroll(offset, source, NestedState::CHILD_OVER_SCROLL, velocity);
1241 if (!NearZero(result.remain)) {
1242 result = HandleDragUpdate(result.remain, velocity);
1243 }
1244 return { 0.f, true };
1245 } else {
1246 result = HandleDragUpdate(offset, velocity);
1247 }
1248 } else if (state == NestedState::CHILD_CHECK_OVER_SCROLL && Positive(scrollOffset_) && Negative(offset)) {
1249 result = HandleDragUpdate(offset, velocity);
1250 }
1251 return result;
1252 }
1253
OnScrollStartRecursive(WeakPtr<NestableScrollContainer> child,float position,float velocity)1254 void RefreshPattern::OnScrollStartRecursive(WeakPtr<NestableScrollContainer> child, float position, float velocity)
1255 {
1256 SetIsNestedInterrupt(false);
1257 if (!GetIsFixedNestedScrollMode()) {
1258 SetParentScrollable();
1259 }
1260 auto nestedScroll = GetNestedScroll();
1261 HandleDragStart(true, velocity);
1262 auto parent = GetNestedScrollParent();
1263 if (parent && nestedScroll.NeedParent() &&
1264 (nestedScroll.forward != NestedScrollMode::PARALLEL || nestedScroll.backward != NestedScrollMode::PARALLEL)) {
1265 parent->OnScrollStartRecursive(child, position, velocity);
1266 }
1267 }
1268
HandleScrollVelocity(float velocity,const RefPtr<NestableScrollContainer> & child)1269 bool RefreshPattern::HandleScrollVelocity(float velocity, const RefPtr<NestableScrollContainer>& child)
1270 {
1271 auto parent = GetNestedScrollParent();
1272 auto nestedScroll = GetNestedScroll();
1273 bool result = false;
1274 if (parent && ((Negative(velocity) && nestedScroll.forward == NestedScrollMode::PARENT_FIRST) ||
1275 (Positive(velocity) && nestedScroll.backward == NestedScrollMode::PARENT_FIRST))) {
1276 result = parent->HandleScrollVelocity(velocity);
1277 if (result) {
1278 return true;
1279 }
1280 }
1281 if (Positive(scrollOffset_) || Positive(velocity)) {
1282 result = true;
1283 } else if (parent && ((Negative(velocity) && nestedScroll.forward == NestedScrollMode::SELF_FIRST) ||
1284 (Positive(velocity) && nestedScroll.backward == NestedScrollMode::SELF_FIRST))) {
1285 result = parent->HandleScrollVelocity(velocity);
1286 }
1287 HandleDragEnd(velocity);
1288 return result;
1289 }
1290
OnScrollEndRecursive(const std::optional<float> & velocity)1291 void RefreshPattern::OnScrollEndRecursive(const std::optional<float>& velocity)
1292 {
1293 HandleDragEnd(velocity.value_or(0.f));
1294 auto parent = GetNestedScrollParent();
1295 auto nestedScroll = GetNestedScroll();
1296 if (parent && (nestedScroll.NeedParent() || GetIsNestedInterrupt())) {
1297 parent->OnScrollEndRecursive(velocity);
1298 }
1299 SetIsNestedInterrupt(false);
1300 }
1301
GetLoadingProgressOpacity()1302 float RefreshPattern::GetLoadingProgressOpacity()
1303 {
1304 CHECK_NULL_RETURN(progressChild_, -1.0f);
1305 auto renderContext = progressChild_->GetRenderContext();
1306 CHECK_NULL_RETURN(renderContext, -1.0f);
1307 return renderContext->GetOpacityValue(1.0f);
1308 }
1309
GetLoadingTextOpacity()1310 float RefreshPattern::GetLoadingTextOpacity()
1311 {
1312 CHECK_NULL_RETURN(loadingTextNode_, -1.0f);
1313 auto renderContext = loadingTextNode_->GetRenderContext();
1314 CHECK_NULL_RETURN(renderContext, -1.0f);
1315 return renderContext->GetOpacityValue(1.0f);
1316 }
1317
GetLoadingProgressColor()1318 Color RefreshPattern::GetLoadingProgressColor()
1319 {
1320 CHECK_NULL_RETURN(progressChild_, Color::BLACK);
1321 auto paintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
1322 CHECK_NULL_RETURN(paintProperty, Color::BLACK);
1323 return paintProperty->GetColorValue(Color::BLACK);
1324 }
1325
DumpInfo()1326 void RefreshPattern::DumpInfo()
1327 {
1328 DumpLog::GetInstance().AddDesc(
1329 std::string("RefreshStatus: ").append(std::to_string(static_cast<int32_t>(refreshStatus_))));
1330 DumpLog::GetInstance().AddDesc(
1331 std::string("LoadingProgressOpacity: ").append(std::to_string(GetLoadingProgressOpacity())));
1332 DumpLog::GetInstance().AddDesc(
1333 std::string("LoadingTextOpacity: ").append(std::to_string(GetLoadingTextOpacity())));
1334 DumpLog::GetInstance().AddDesc(
1335 std::string("LoadingProgressColor: ").append(GetLoadingProgressColor().ColorToString()));
1336 }
1337
DumpInfo(std::unique_ptr<JsonValue> & json)1338 void RefreshPattern::DumpInfo(std::unique_ptr<JsonValue>& json)
1339 {
1340 json->Put("RefreshStatus", static_cast<int32_t>(refreshStatus_));
1341 json->Put("LoadingProgressOpacity", GetLoadingProgressOpacity());
1342 json->Put("LoadingTextOpacity", GetLoadingTextOpacity());
1343 json->Put("LoadingProgressColor", GetLoadingProgressColor().ColorToString().c_str());
1344 }
1345 } // namespace OHOS::Ace::NG
1346