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