1 /*
2 * Copyright (c) 2022-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "core/components_ng/pattern/refresh/refresh_pattern.h"
17
18 #include "base/geometry/dimension.h"
19 #include "base/geometry/ng/offset_t.h"
20 #include "base/memory/ace_type.h"
21 #include "base/utils/utils.h"
22 #include "core/animation/spring_curve.h"
23 #include "core/common/container.h"
24 #include "core/components/common/properties/animation_option.h"
25 #include "core/components/refresh/refresh_theme.h"
26 #include "core/components_ng/base/frame_node.h"
27 #include "core/components_ng/event/event_hub.h"
28 #include "core/components_ng/pattern/loading_progress/loading_progress_layout_property.h"
29 #include "core/components_ng/pattern/loading_progress/loading_progress_paint_property.h"
30 #include "core/components_ng/pattern/refresh/refresh_layout_property.h"
31 #include "core/components_ng/pattern/scrollable/scrollable_pattern.h"
32 #include "core/components_ng/property/property.h"
33 #include "core/components_ng/render/animation_utils.h"
34 #include "core/pipeline/base/element_register.h"
35 #include "core/pipeline_ng/pipeline_context.h"
36 #include "frameworks/base/i18n/localization.h"
37 #include "frameworks/base/utils/time_util.h"
38 #include "frameworks/base/utils/utils.h"
39 #include "frameworks/core/components/common/layout/constants.h"
40 #include "frameworks/core/components_ng/pattern/loading_progress/loading_progress_pattern.h"
41 #include "frameworks/core/components_ng/pattern/text/text_pattern.h"
42
43 namespace OHOS::Ace::NG {
44
45 namespace {
46 constexpr float PERCENT = 0.01; // Percent
47 constexpr float FOLLOW_TO_RECYCLE_DURATION = 600;
48 constexpr float CUSTOM_BUILDER_RECYCLE_DURATION = 100;
49 constexpr float CUSTOM_BUILDER_EXIT_DURATION = 100;
50 constexpr float LOADING_EXIT_DURATION = 350;
51 constexpr Dimension TRIGGER_LOADING_DISTANCE = 16.0_vp;
52 constexpr Dimension TRIGGER_REFRESH_DISTANCE = 64.0_vp;
53 constexpr Dimension MAX_SCROLL_DISTANCE = 128.0_vp;
54 constexpr Dimension LOADING_PROGRESS_SIZE = 32.0_vp;
55 constexpr Dimension CUSTOM_BUILDER_HIGHT_LIGHT_SIZE = 32.0_vp;
56 constexpr float DEFAULT_FRICTION = 64.0f;
57 constexpr int32_t STATE_PROGRESS_LOADING = 1;
58 constexpr int32_t STATE_PROGRESS_RECYCLE = 2;
59 constexpr int32_t STATE_PROGRESS_DRAG = 3;
60 } // namespace
61
OnModifyDone()62 void RefreshPattern::OnModifyDone()
63 {
64 Pattern::OnModifyDone();
65 auto host = GetHost();
66 CHECK_NULL_VOID(host);
67 auto hub = host->GetEventHub<EventHub>();
68 CHECK_NULL_VOID(hub);
69 auto gestureHub = hub->GetOrCreateGestureEventHub();
70 CHECK_NULL_VOID(gestureHub);
71 auto layoutProperty = GetLayoutProperty<RefreshLayoutProperty>();
72 CHECK_NULL_VOID(layoutProperty);
73 triggerLoadingDistance_ = static_cast<float>(
74 std::clamp(layoutProperty->GetIndicatorOffset().value_or(TRIGGER_LOADING_DISTANCE).ConvertToPx(),
75 -1.0f * TRIGGER_LOADING_DISTANCE.ConvertToPx(), TRIGGER_REFRESH_DISTANCE.ConvertToPx()));
76 InitPanEvent(gestureHub);
77 CheckCoordinationEvent();
78 InitOnKeyEvent();
79 auto paintProperty = GetPaintProperty<RefreshRenderProperty>();
80 CHECK_NULL_VOID(paintProperty);
81 auto refreshingProp = paintProperty->GetIsRefreshing().value_or(false);
82 if (isCustomBuilderExist_ && HasCustomBuilderIndex()) {
83 if (!customBuilder_) {
84 customBuilder_ = AceType::DynamicCast<FrameNode>(host->GetChildAtIndex(customBuilderIndex_.value_or(0)));
85 UpdateCustomBuilderProperty(RefreshState::STATE_LOADING, 0.0f);
86 }
87 } else if (!progressChild_) {
88 progressChild_ = AceType::DynamicCast<FrameNode>(host->GetChildAtIndex(host->TotalChildCount() - 1));
89 }
90 if (isRefreshing_ != refreshingProp) {
91 if (refreshingProp) {
92 QuickStartFresh();
93 } else {
94 QuickEndFresh();
95 }
96 }
97 SetAccessibilityAction();
98 }
99
CreateLayoutAlgorithm()100 RefPtr<LayoutAlgorithm> RefreshPattern::CreateLayoutAlgorithm()
101 {
102 auto refreshLayoutAlgorithm = MakeRefPtr<RefreshLayoutAlgorithm>();
103 if (HasCustomBuilderIndex()) {
104 refreshLayoutAlgorithm->SetCustomBuilderIndex(customBuilderIndex_.value_or(0));
105 refreshLayoutAlgorithm->SetCustomBuilderOffset(customBuilderOffset_);
106 }
107 refreshLayoutAlgorithm->SetScrollOffset(scrollOffset_.GetY());
108 return refreshLayoutAlgorithm;
109 }
110
InitOnKeyEvent()111 void RefreshPattern::InitOnKeyEvent()
112 {
113 if (isKeyEventRegisted_) {
114 return;
115 }
116 auto host = GetHost();
117 CHECK_NULL_VOID(host);
118 auto focusHub = host->GetFocusHub();
119 CHECK_NULL_VOID(focusHub);
120 auto onKeyEvent = [wp = WeakClaim(this)](const KeyEvent& event) -> bool {
121 auto pattern = wp.Upgrade();
122 CHECK_NULL_RETURN_NOLOG(pattern, false);
123 return pattern->OnKeyEvent(event);
124 };
125 isKeyEventRegisted_ = true;
126 focusHub->SetOnKeyEventInternal(std::move(onKeyEvent));
127 }
128
QuickStartFresh()129 void RefreshPattern::QuickStartFresh()
130 {
131 if (isCustomBuilderExist_) {
132 CustomBuilderAppear();
133 if (!isRefreshing_) {
134 TriggerRefresh();
135 }
136 return;
137 }
138 ReplaceLoadingProgressNode();
139 TriggerRefresh();
140 LoadingProgressAppear();
141 }
142
QuickEndFresh()143 void RefreshPattern::QuickEndFresh()
144 {
145 if (isCustomBuilderExist_) {
146 CustomBuilderExit();
147 return;
148 }
149 LoadingProgressExit();
150 }
151
OnKeyEvent(const KeyEvent & event)152 bool RefreshPattern::OnKeyEvent(const KeyEvent& event)
153 {
154 if (event.code == KeyCode::KEY_F5 || (event.IsCombinationKey() && event.IsCtrlWith(KeyCode::KEY_R))) {
155 if (isRefreshing_) {
156 return true;
157 }
158 QuickStartFresh();
159 return true;
160 }
161 return false;
162 }
163
CheckCoordinationEvent()164 void RefreshPattern::CheckCoordinationEvent()
165 {
166 auto host = GetHost();
167 CHECK_NULL_VOID(host);
168 auto scrollableNode = FindScrollableChild();
169 scrollableNode_ = WeakClaim(AceType::RawPtr(scrollableNode));
170 CHECK_NULL_VOID(scrollableNode);
171 auto scrollablePattern = scrollableNode->GetPattern<ScrollablePattern>();
172 CHECK_NULL_VOID(scrollablePattern);
173 auto coordinationEvent = AceType::MakeRefPtr<ScrollableCoordinationEvent>();
174 auto onScrollEvent = [weak = WeakClaim(this)](double offset) -> bool {
175 auto pattern = weak.Upgrade();
176 CHECK_NULL_RETURN(pattern, false);
177 pattern->HandleDragUpdate(static_cast<float>(offset));
178 return Positive(pattern->scrollOffset_.GetY()) || NonNegative(offset);
179 };
180 coordinationEvent->SetOnScrollEvent(onScrollEvent);
181 auto onScrollStartEvent = [weak = WeakClaim(this)]() {
182 auto pattern = weak.Upgrade();
183 CHECK_NULL_VOID(pattern);
184 pattern->HandleDragStart();
185 };
186 coordinationEvent->SetOnScrollStartEvent(onScrollStartEvent);
187 auto onScrollEndEvent = [weak = WeakClaim(this)]() {
188 auto pattern = weak.Upgrade();
189 CHECK_NULL_VOID(pattern);
190 pattern->HandleDragEnd();
191 };
192 coordinationEvent->SetOnScrollEndEvent(onScrollEndEvent);
193 scrollablePattern->SetCoordinationEvent(coordinationEvent);
194 }
195
FindScrollableChild()196 RefPtr<FrameNode> RefreshPattern::FindScrollableChild()
197 {
198 auto host = GetHost();
199 CHECK_NULL_RETURN(host, nullptr);
200 std::queue<RefPtr<FrameNode>> frameNodeQueue;
201 frameNodeQueue.push(host);
202 while (!frameNodeQueue.empty()) {
203 auto size = frameNodeQueue.size();
204 while (size > 0) {
205 auto node = frameNodeQueue.front();
206 CHECK_NULL_RETURN(node, nullptr);
207 if (AceType::InstanceOf<ScrollablePattern>(node->GetPattern())) {
208 return node;
209 }
210 frameNodeQueue.pop();
211 auto children = node->GetChildren();
212 for (auto const& child : children) {
213 auto childNode = DynamicCast<FrameNode>(child);
214 if (childNode) {
215 frameNodeQueue.push(childNode);
216 }
217 }
218 size--;
219 }
220 }
221 return nullptr;
222 }
223
TriggerRefresh()224 void RefreshPattern::TriggerRefresh()
225 {
226 isRefreshing_ = true;
227 FireChangeEvent("true");
228 FireRefreshing();
229 TriggerStatusChange(RefreshStatus::REFRESH);
230 }
231
LoadingProgressRecycle()232 void RefreshPattern::LoadingProgressRecycle()
233 {
234 CHECK_NULL_VOID(progressChild_);
235 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
236 CHECK_NULL_VOID(progressPaintProperty);
237 progressPaintProperty->UpdateRefreshAnimationState(static_cast<int32_t>(RefreshAnimationState::RECYCLE));
238 progressChild_->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
239 }
240
ReplaceLoadingProgressNode()241 void RefreshPattern::ReplaceLoadingProgressNode()
242 {
243 auto host = GetHost();
244 CHECK_NULL_VOID(host);
245 if (progressChild_) {
246 host->RemoveChild(progressChild_);
247 }
248 auto loadingProgressChild = FrameNode::CreateFrameNode(V2::LOADING_PROGRESS_ETS_TAG,
249 ElementRegister::GetInstance()->MakeUniqueId(), AceType::MakeRefPtr<LoadingProgressPattern>());
250 CHECK_NULL_VOID(loadingProgressChild);
251 host->AddChild(loadingProgressChild);
252 progressChild_ = loadingProgressChild;
253 LoadingProgressReset();
254 host->RebuildRenderContextTree();
255 }
256
LoadingProgressReset()257 void RefreshPattern::LoadingProgressReset()
258 {
259 CHECK_NULL_VOID(progressChild_);
260 auto gestureHub = progressChild_->GetEventHub<EventHub>();
261 if (gestureHub) {
262 gestureHub->SetEnabled(false);
263 }
264 UpdateLoadingProgress(STATE_PROGRESS_LOADING, 0.0f);
265 auto progressLayoutProperty = progressChild_->GetLayoutProperty<LoadingProgressLayoutProperty>();
266 CHECK_NULL_VOID(progressLayoutProperty);
267 progressLayoutProperty->UpdateUserDefinedIdealSize(
268 CalcSize(CalcLength(LOADING_PROGRESS_SIZE.ConvertToPx()), CalcLength(LOADING_PROGRESS_SIZE.ConvertToPx())));
269 ResetLoadingProgressColor();
270 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
271 CHECK_NULL_VOID(progressPaintProperty);
272 progressPaintProperty->UpdateLoadingProgressOwner(LoadingProgressOwner::REFRESH);
273 scrollOffset_.SetY(0.0f);
274 progressChild_->MarkDirtyNode();
275 }
276
OnExitAnimationFinish()277 void RefreshPattern::OnExitAnimationFinish()
278 {
279 if (isCustomBuilderExist_ && customBuilder_) {
280 scrollOffset_.Reset();
281 CustomBuilderReset();
282 TriggerFinish();
283 auto host = GetHost();
284 CHECK_NULL_VOID(host);
285 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
286 return;
287 }
288 ReplaceLoadingProgressNode();
289 TriggerFinish();
290 CHECK_NULL_VOID(progressChild_);
291 progressChild_->MarkDirtyNode(PROPERTY_UPDATE_LAYOUT);
292 }
293
TriggerInActive()294 void RefreshPattern::TriggerInActive()
295 {
296 isRefreshing_ = false;
297 FireChangeEvent("false");
298 TriggerStatusChange(RefreshStatus::INACTIVE);
299 }
300
TriggerDone()301 void RefreshPattern::TriggerDone()
302 {
303 isRefreshing_ = false;
304 FireChangeEvent("false");
305 TriggerStatusChange(RefreshStatus::DONE);
306 }
307
TriggerFinish()308 void RefreshPattern::TriggerFinish()
309 {
310 if (refreshStatus_ == RefreshStatus::REFRESH) {
311 TriggerDone();
312 } else {
313 TriggerInActive();
314 }
315 }
316
InitPanEvent(const RefPtr<GestureEventHub> & gestureHub)317 void RefreshPattern::InitPanEvent(const RefPtr<GestureEventHub>& gestureHub)
318 {
319 auto actionStartTask = [weak = WeakClaim(this)](const GestureEvent& /*info*/) {
320 auto pattern = weak.Upgrade();
321 CHECK_NULL_VOID(pattern);
322 pattern->HandleDragStart();
323 };
324 auto actionUpdateTask = [weak = WeakClaim(this)](const GestureEvent& info) {
325 auto pattern = weak.Upgrade();
326 CHECK_NULL_VOID(pattern);
327 pattern->HandleDragUpdate(static_cast<float>(info.GetMainDelta()));
328 };
329 auto actionEndTask = [weak = WeakClaim(this)](const GestureEvent& /*info*/) {
330 auto pattern = weak.Upgrade();
331 CHECK_NULL_VOID(pattern);
332 pattern->HandleDragEnd();
333 };
334 auto actionCancelTask = [weak = WeakClaim(this)]() {
335 auto pattern = weak.Upgrade();
336 CHECK_NULL_VOID(pattern);
337 pattern->HandleDragCancel();
338 };
339 PanDirection panDirection;
340 panDirection.type = PanDirection::VERTICAL;
341 if (panEvent_) {
342 gestureHub->RemovePanEvent(panEvent_);
343 }
344
345 panEvent_ = MakeRefPtr<PanEvent>(
346 std::move(actionStartTask), std::move(actionUpdateTask), std::move(actionEndTask), std::move(actionCancelTask));
347 gestureHub->AddPanEvent(panEvent_, panDirection, 1, DEFAULT_PAN_DISTANCE);
348 }
349
HandleDragStart()350 void RefreshPattern::HandleDragStart()
351 {
352 if (isRefreshing_) {
353 return;
354 }
355 TriggerStatusChange(RefreshStatus::DRAG);
356 if (customBuilder_) {
357 scrollOffset_.Reset();
358 return;
359 }
360 CHECK_NULL_VOID(progressChild_);
361 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
362 CHECK_NULL_VOID(progressPaintProperty);
363 progressPaintProperty->UpdateRefreshAnimationState(static_cast<int32_t>(RefreshAnimationState::FOLLOW_HAND));
364 progressChild_->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
365 auto frameNode = GetHost();
366 CHECK_NULL_VOID(frameNode);
367 frameNode->OnAccessibilityEvent(AccessibilityEventType::SCROLL_START);
368 }
369
HandleDragUpdate(float delta)370 void RefreshPattern::HandleDragUpdate(float delta)
371 {
372 if (NearZero(delta) || isRefreshing_) {
373 LOGI("Delta is near zero or isRefreshing!");
374 return;
375 }
376
377 scrollOffset_.SetY(GetScrollOffset(delta));
378 if (customBuilder_) {
379 HandleCustomBuilderDragUpdateStage();
380 return;
381 }
382 CHECK_NULL_VOID(progressChild_);
383 if (scrollOffset_.GetY() > triggerLoadingDistance_) {
384 auto refreshFollowRadio = GetFollowRatio();
385 UpdateLoadingProgress(STATE_PROGRESS_DRAG, refreshFollowRadio);
386 UpdateLoadingMarginTop(scrollOffset_.GetY());
387 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
388 CHECK_NULL_VOID(progressPaintProperty);
389 progressPaintProperty->UpdateRefreshFollowRatio(refreshFollowRadio);
390 }
391
392 if (scrollOffset_.GetY() > TRIGGER_REFRESH_DISTANCE.ConvertToPx()) {
393 TriggerStatusChange(RefreshStatus::OVER_DRAG);
394 }
395 progressChild_->MarkDirtyNode(PROPERTY_UPDATE_LAYOUT);
396 }
397
UpdateLoadingProgress(int32_t state,float ratio)398 void RefreshPattern::UpdateLoadingProgress(int32_t state, float ratio)
399 {
400 CHECK_NULL_VOID(progressChild_);
401 auto progressLayoutProperty = progressChild_->GetLayoutProperty<LoadingProgressLayoutProperty>();
402 CHECK_NULL_VOID(progressLayoutProperty);
403 auto scale = std::clamp(ratio, 0.0f, 1.0f);
404 switch (state) {
405 case STATE_PROGRESS_LOADING:
406 scale = 0.0f;
407 UpdateLoadingMarginTop(triggerLoadingDistance_);
408 break;
409 case STATE_PROGRESS_RECYCLE:
410 scale = 1.0f;
411 UpdateLoadingMarginTop(TRIGGER_REFRESH_DISTANCE.ConvertToPx());
412 break;
413 default:;
414 }
415 auto progressContext = progressChild_->GetRenderContext();
416 CHECK_NULL_VOID_NOLOG(progressContext);
417 progressContext->UpdateOpacity(scale);
418 }
419
GetFollowRatio()420 float RefreshPattern::GetFollowRatio()
421 {
422 auto triggerLoading = std::clamp(triggerLoadingDistance_, 0.0f,
423 static_cast<float>(TRIGGER_REFRESH_DISTANCE.ConvertToPx()));
424 if (GreatNotEqual(TRIGGER_REFRESH_DISTANCE.ConvertToPx(), triggerLoading)) {
425 return (scrollOffset_.GetY() - triggerLoading) / (TRIGGER_REFRESH_DISTANCE.ConvertToPx() - triggerLoading);
426 }
427 return 1.0f;
428 }
429
GetFadeAwayRatio()430 float RefreshPattern::GetFadeAwayRatio()
431 {
432 CHECK_NULL_RETURN(progressChild_, 0.0f);
433 auto progressLayoutProperty = progressChild_->GetLayoutProperty<LoadingProgressLayoutProperty>();
434 CHECK_NULL_RETURN(progressLayoutProperty, 0.0f);
435 auto& marginProperty = progressLayoutProperty->GetMarginProperty();
436 CHECK_NULL_RETURN(marginProperty, 0.0f);
437 auto triggerLoading = std::clamp(triggerLoadingDistance_, 0.0f,
438 static_cast<float>(TRIGGER_REFRESH_DISTANCE.ConvertToPx()));
439 if (GreatNotEqual(TRIGGER_REFRESH_DISTANCE.ConvertToPx(), triggerLoading)) {
440 return (marginProperty->top->GetDimension().ConvertToPx() - triggerLoading) /
441 (TRIGGER_REFRESH_DISTANCE.ConvertToPx() - triggerLoading);
442 }
443 return 0.0f;
444 }
445
TransitionPeriodAnimation()446 void RefreshPattern::TransitionPeriodAnimation()
447 {
448 CHECK_NULL_VOID(progressChild_);
449 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
450 CHECK_NULL_VOID(progressPaintProperty);
451
452 auto progressLayoutProperty = progressChild_->GetLayoutProperty<LoadingProgressLayoutProperty>();
453 CHECK_NULL_VOID(progressLayoutProperty);
454 progressPaintProperty->UpdateRefreshAnimationState(static_cast<int32_t>(RefreshAnimationState::FOLLOW_TO_RECYCLE));
455 progressPaintProperty->UpdateRefreshTransitionRatio(0.0f);
456 progressChild_->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
457 auto pipeline = AceType::DynamicCast<PipelineContext>(PipelineContext::GetCurrentContext());
458 CHECK_NULL_VOID(pipeline);
459 pipeline->AddAnimationClosure([weak = AceType::WeakClaim(this)]() {
460 auto pattern = weak.Upgrade();
461 CHECK_NULL_VOID(pattern);
462 auto pipeline = PipelineContext::GetCurrentContext();
463 CHECK_NULL_VOID(pipeline);
464 auto curve = AceType::MakeRefPtr<SpringCurve>(0.0f, 1.0f, 228.0f, 30.0f);
465 AnimationOption option;
466 option.SetDuration(FOLLOW_TO_RECYCLE_DURATION);
467 option.SetCurve(curve);
468 option.SetIteration(1);
469
470 AnimationUtils::OpenImplicitAnimation(option, curve, [weak]() {
471 auto pattern = weak.Upgrade();
472 CHECK_NULL_VOID(pattern);
473 pattern->LoadingProgressRecycle();
474 });
475 auto distance = TRIGGER_REFRESH_DISTANCE.ConvertToPx();
476 pattern->scrollOffset_.SetY(distance);
477 pattern->UpdateLoadingMarginTop(distance);
478 pattern->progressChild_->MarkDirtyNode(PROPERTY_UPDATE_LAYOUT);
479 pipeline->FlushUITasks();
480 AnimationUtils::CloseImplicitAnimation();
481 });
482 }
483
LoadingProgressAppear()484 void RefreshPattern::LoadingProgressAppear()
485 {
486 CHECK_NULL_VOID(progressChild_);
487 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
488 CHECK_NULL_VOID(progressPaintProperty);
489 progressPaintProperty->UpdateRefreshAnimationState(static_cast<int32_t>(RefreshAnimationState::RECYCLE));
490 progressChild_->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
491 auto pipeline = AceType::DynamicCast<PipelineContext>(PipelineContext::GetCurrentContext());
492 CHECK_NULL_VOID(pipeline);
493 pipeline->AddAnimationClosure([weak = AceType::WeakClaim(this)]() {
494 auto pattern = weak.Upgrade();
495 CHECK_NULL_VOID(pattern);
496 auto pipeline = PipelineContext::GetCurrentContext();
497 CHECK_NULL_VOID(pipeline);
498 AnimationOption option;
499 option.SetDuration(LOADING_EXIT_DURATION);
500 auto curve = AceType::MakeRefPtr<CubicCurve>(0.2f, 0.0f, 0.1f, 1.0f);
501 AnimationUtils::OpenImplicitAnimation(option, curve, nullptr);
502 pattern->scrollOffset_.SetY(TRIGGER_REFRESH_DISTANCE.ConvertToPx());
503 pattern->UpdateLoadingProgress(STATE_PROGRESS_RECYCLE, 1.0f);
504 pattern->progressChild_->MarkDirtyNode(PROPERTY_UPDATE_LAYOUT);
505 pipeline->FlushUITasks();
506 AnimationUtils::CloseImplicitAnimation();
507 });
508 }
509
LoadingProgressExit()510 void RefreshPattern::LoadingProgressExit()
511 {
512 CHECK_NULL_VOID(progressChild_);
513 auto progressPaintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
514 CHECK_NULL_VOID(progressPaintProperty);
515 progressPaintProperty->UpdateRefreshAnimationState(static_cast<int32_t>(RefreshAnimationState::FADEAWAY));
516 progressPaintProperty->UpdateRefreshFadeAwayRatio(GetFadeAwayRatio());
517 progressChild_->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
518 auto pipeline = PipelineContext::GetCurrentContext();
519 CHECK_NULL_VOID(pipeline);
520 pipeline->FlushUITasks();
521
522 AnimationOption option;
523 option.SetDuration(LOADING_EXIT_DURATION);
524 auto curve = AceType::MakeRefPtr<CubicCurve>(0.2f, 0.0f, 0.1f, 1.0f);
525 AnimationUtils::OpenImplicitAnimation(option, curve, [weak = AceType::WeakClaim(this)]() {
526 auto pattern = weak.Upgrade();
527 CHECK_NULL_VOID(pattern);
528 pattern->OnExitAnimationFinish();
529 });
530
531 scrollOffset_.SetY(0.0f);
532 UpdateLoadingProgress(STATE_PROGRESS_LOADING, 0.0f);
533 progressPaintProperty->UpdateRefreshFadeAwayRatio(0.0f);
534 progressChild_->MarkDirtyNode(PROPERTY_UPDATE_LAYOUT);
535 pipeline->FlushUITasks();
536 AnimationUtils::CloseImplicitAnimation();
537 }
538
HandleDragEnd()539 void RefreshPattern::HandleDragEnd()
540 {
541 if (isRefreshing_) {
542 return;
543 }
544 auto triggerRefreshDistance = TRIGGER_REFRESH_DISTANCE.ConvertToPx();
545 if (customBuilder_) {
546 HandleCustomBuilderDragEndStage();
547 return;
548 }
549 if (scrollOffset_.GetY() >= triggerRefreshDistance) {
550 TriggerRefresh();
551 TransitionPeriodAnimation();
552 auto frameNode = GetHost();
553 CHECK_NULL_VOID(frameNode);
554 frameNode->OnAccessibilityEvent(AccessibilityEventType::SCROLL_END);
555 return;
556 }
557 LoadingProgressExit();
558 }
559
TriggerStatusChange(RefreshStatus newStatus)560 void RefreshPattern::TriggerStatusChange(RefreshStatus newStatus)
561 {
562 if (refreshStatus_ == newStatus) {
563 return;
564 }
565 refreshStatus_ = newStatus;
566 FireStateChange(static_cast<int>(refreshStatus_));
567 }
568
HandleDragCancel()569 void RefreshPattern::HandleDragCancel()
570 {
571 if (customBuilder_) {
572 CustomBuilderExit();
573 return;
574 }
575 LoadingProgressExit();
576 }
577
FireStateChange(int32_t value)578 void RefreshPattern::FireStateChange(int32_t value)
579 {
580 auto refreshEventHub = GetEventHub<RefreshEventHub>();
581 CHECK_NULL_VOID(refreshEventHub);
582 refreshEventHub->FireOnStateChange(value);
583 }
584
FireRefreshing()585 void RefreshPattern::FireRefreshing()
586 {
587 auto refreshEventHub = GetEventHub<RefreshEventHub>();
588 CHECK_NULL_VOID(refreshEventHub);
589 refreshEventHub->FireOnRefreshing();
590 }
591
FireChangeEvent(const std::string & value)592 void RefreshPattern::FireChangeEvent(const std::string& value)
593 {
594 auto refreshEventHub = GetEventHub<RefreshEventHub>();
595 CHECK_NULL_VOID(refreshEventHub);
596 refreshEventHub->FireChangeEvent(value);
597 }
598
GetScrollOffset(float delta)599 float RefreshPattern::GetScrollOffset(float delta)
600 {
601 auto layoutProperty = GetLayoutProperty<RefreshLayoutProperty>();
602 CHECK_NULL_RETURN(layoutProperty, 0.0f);
603 auto frictionRatio = static_cast<float>(layoutProperty->GetFriction().value_or(DEFAULT_FRICTION)) * PERCENT;
604 auto scrollY = delta * frictionRatio;
605 auto scrollOffset = std::clamp(scrollOffset_.GetY() + scrollY, static_cast<float>(0.0f),
606 static_cast<float>(MAX_SCROLL_DISTANCE.ConvertToPx()));
607 return scrollOffset;
608 }
609
ResetLoadingProgressColor()610 void RefreshPattern::ResetLoadingProgressColor()
611 {
612 auto pipeline = PipelineContext::GetCurrentContext();
613 CHECK_NULL_VOID(pipeline);
614 auto themeManager = pipeline->GetThemeManager();
615 CHECK_NULL_VOID(themeManager);
616 auto theme = themeManager->GetTheme<RefreshTheme>();
617 CHECK_NULL_VOID(theme);
618 CHECK_NULL_VOID(progressChild_);
619 auto paintProperty = progressChild_->GetPaintProperty<LoadingProgressPaintProperty>();
620 CHECK_NULL_VOID(paintProperty);
621 paintProperty->UpdateColor(theme->GetProgressColor());
622 }
623
AddCustomBuilderNode(const RefPtr<NG::UINode> & builder)624 void RefreshPattern::AddCustomBuilderNode(const RefPtr<NG::UINode>& builder)
625 {
626 CHECK_NULL_VOID(builder);
627 auto host = GetHost();
628 CHECK_NULL_VOID(host);
629
630 if (!HasCustomBuilderIndex()) {
631 host->AddChild(builder);
632 UpdateCustomBuilderIndex(host->TotalChildCount() - 1);
633 } else {
634 auto customNodeChild = host->GetChildAtIndex(customBuilderIndex_.value_or(0));
635 CHECK_NULL_VOID(customNodeChild);
636 if (builder->GetId() != customNodeChild->GetId()) {
637 host->ReplaceChild(customNodeChild, builder);
638 }
639 }
640 isCustomBuilderExist_ = true;
641 }
642
CustomBuilderAppear()643 void RefreshPattern::CustomBuilderAppear()
644 {
645 auto refreshingDistance = TRIGGER_REFRESH_DISTANCE.ConvertToPx();
646 if (GreatOrEqual(static_cast<double>(customBuilderOffset_), refreshingDistance)) {
647 return;
648 }
649 AnimationOption option;
650 auto curve = AceType::MakeRefPtr<CubicCurve>(0.2f, 0.0f, 0.1f, 1.0f);
651 option.SetDuration(CUSTOM_BUILDER_RECYCLE_DURATION);
652 option.SetCurve(curve);
653
654 AnimationUtils::Animate(
655 option,
656 [weak = AceType::WeakClaim(this)]() {
657 auto pattern = weak.Upgrade();
658 CHECK_NULL_VOID(pattern);
659 pattern->UpdateCustomBuilderProperty(RefreshState::STATE_RECYCLE, 1.0f);
660 },
661 nullptr);
662 }
663
CustomBuilderExit()664 void RefreshPattern::CustomBuilderExit()
665 {
666 if (LessNotEqual(static_cast<double>(customBuilderOffset_), static_cast<double>(triggerLoadingDistance_))) {
667 return;
668 }
669 AnimationOption option;
670 option.SetDuration(CUSTOM_BUILDER_EXIT_DURATION);
671 auto finishCallback = [weak = AceType::WeakClaim(this)]() {
672 auto pattern = weak.Upgrade();
673 CHECK_NULL_VOID(pattern);
674 pattern->OnExitAnimationFinish();
675 };
676
677 AnimationUtils::Animate(
678 option,
679 [weak = AceType::WeakClaim(this)]() {
680 auto pattern = weak.Upgrade();
681 CHECK_NULL_VOID(pattern);
682 pattern->UpdateCustomBuilderProperty(RefreshState::STATE_LOADING, 0.0f);
683 },
684 std::move(finishCallback));
685 }
686
HandleCustomBuilderDragUpdateStage()687 void RefreshPattern::HandleCustomBuilderDragUpdateStage()
688 {
689 CHECK_NULL_VOID(customBuilder_);
690 auto host = GetHost();
691 CHECK_NULL_VOID(host);
692 auto customBuilderSize = customBuilder_->GetGeometryNode()->GetMarginFrameSize();
693 auto maxScroll = MAX_SCROLL_DISTANCE.ConvertToPx();
694 if (NearZero(static_cast<double>(customBuilder_->GetGeometryNode()->GetMarginFrameSize().Height()))) {
695 return;
696 }
697 if (LessNotEqual(static_cast<double>(maxScroll - customBuilderSize.Height()),
698 static_cast<double>(triggerLoadingDistance_))) {
699 return;
700 }
701 if (LessOrEqual(static_cast<double>(scrollOffset_.GetY()),
702 static_cast<double>(triggerLoadingDistance_ + customBuilderSize.Height()))) {
703 UpdateCustomBuilderProperty(RefreshState::STATE_LOADING, 0.0f);
704 } else {
705 auto refreshFollowRadio = GetCustomBuilderOpacityRatio();
706 UpdateCustomBuilderProperty(RefreshState::STATE_DRAG, refreshFollowRadio);
707 if (GreatNotEqual(static_cast<double>(customBuilderOffset_),
708 TRIGGER_REFRESH_DISTANCE.ConvertToPx())) {
709 TriggerStatusChange(RefreshStatus::OVER_DRAG);
710 }
711 }
712 host->MarkDirtyNode(PROPERTY_UPDATE_LAYOUT);
713 }
714
HandleCustomBuilderDragEndStage()715 void RefreshPattern::HandleCustomBuilderDragEndStage()
716 {
717 CHECK_NULL_VOID(customBuilder_);
718 auto host = GetHost();
719 CHECK_NULL_VOID(host);
720 auto customBuilderSize = customBuilder_->GetGeometryNode()->GetMarginFrameSize();
721 auto maxScroll = MAX_SCROLL_DISTANCE.ConvertToPx();
722 if (LessNotEqual(static_cast<double>(maxScroll - customBuilderSize.Height()),
723 static_cast<double>(triggerLoadingDistance_))) {
724 return;
725 }
726
727 if (GreatNotEqual(static_cast<double>(customBuilderOffset_), TRIGGER_REFRESH_DISTANCE.ConvertToPx())) {
728 TriggerRefresh();
729 CustomBuilderRefreshingAnimation();
730 scrollOffset_.SetY(TRIGGER_REFRESH_DISTANCE.ConvertToPx() + customBuilderSize.Height());
731 } else {
732 CustomBuilderExit();
733 scrollOffset_.Reset();
734 }
735 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
736 }
737
CustomBuilderReset()738 void RefreshPattern::CustomBuilderReset()
739 {
740 auto host = GetHost();
741 CHECK_NULL_VOID(host);
742 if (isCustomBuilderExist_ && HasCustomBuilderIndex()) {
743 customBuilder_ = AceType::DynamicCast<FrameNode>(host->GetChildAtIndex(customBuilderIndex_.value_or(0)));
744 }
745 CHECK_NULL_VOID(customBuilder_);
746 UpdateCustomBuilderProperty(RefreshState::STATE_LOADING, 0.0f);
747 }
748
UpdateCustomBuilderProperty(RefreshState state,float ratio)749 void RefreshPattern::UpdateCustomBuilderProperty(RefreshState state, float ratio)
750 {
751 CHECK_NULL_VOID(customBuilder_);
752 ratio = std::clamp(ratio, 0.0f, 1.0f);
753 auto verticalOffset = scrollOffset_.GetY();
754 auto customBuilderSize = customBuilder_->GetGeometryNode()->GetMarginFrameSize();
755 auto maxScroll = static_cast<float>(MAX_SCROLL_DISTANCE.ConvertToPx());
756 auto custombuilderOffset = verticalOffset - customBuilderSize.Height();
757 custombuilderOffset =
758 std::clamp(custombuilderOffset, triggerLoadingDistance_, maxScroll - customBuilderSize.Height());
759 switch (state) {
760 case RefreshState::STATE_LOADING:
761 customBuilderOffset_ = triggerLoadingDistance_;
762 break;
763 case RefreshState::STATE_DRAG:
764 customBuilderOffset_ = custombuilderOffset;
765 break;
766 case RefreshState::STATE_RECYCLE:
767 customBuilderOffset_ = TRIGGER_REFRESH_DISTANCE.ConvertToPx();
768 break;
769 default:;
770 }
771 auto customBuilderContext = customBuilder_->GetRenderContext();
772 CHECK_NULL_VOID(customBuilderContext);
773 customBuilderContext->UpdateOpacity(ratio);
774 }
775
CustomBuilderRefreshingAnimation()776 void RefreshPattern::CustomBuilderRefreshingAnimation()
777 {
778 auto refreshingDistance = TRIGGER_REFRESH_DISTANCE.ConvertToPx();
779 if (LessNotEqual(static_cast<double>(customBuilderOffset_), refreshingDistance)) {
780 return;
781 }
782 AnimationOption option;
783 auto curve = AceType::MakeRefPtr<CubicCurve>(0.2f, 0.0f, 0.1f, 1.0f);
784 option.SetDuration(CUSTOM_BUILDER_RECYCLE_DURATION);
785 option.SetCurve(curve);
786
787 AnimationUtils::Animate(
788 option,
789 [weak = AceType::WeakClaim(this)]() {
790 auto pattern = weak.Upgrade();
791 CHECK_NULL_VOID(pattern);
792 pattern->UpdateCustomBuilderProperty(RefreshState::STATE_RECYCLE, 1.0f);
793 },
794 nullptr);
795 }
796
GetCustomBuilderOpacityRatio()797 float RefreshPattern::GetCustomBuilderOpacityRatio()
798 {
799 auto verticalOffset = scrollOffset_.GetY();
800 auto customBuilderSize = customBuilder_->GetGeometryNode()->GetMarginFrameSize();
801 auto adjustOffset = verticalOffset - customBuilderSize.Height();
802 float opacityRatio = 0.0f;
803 if (GreatOrEqual(
804 static_cast<double>(customBuilderSize.Height() + std::clamp(static_cast<double>(triggerLoadingDistance_),
805 0.0, TRIGGER_REFRESH_DISTANCE.ConvertToPx())),
806 TRIGGER_REFRESH_DISTANCE.ConvertToPx() + CUSTOM_BUILDER_HIGHT_LIGHT_SIZE.ConvertToPx())) {
807 opacityRatio = 1.0f;
808 } else {
809 opacityRatio = (adjustOffset - std::clamp(triggerLoadingDistance_, 0.0f,
810 static_cast<float>(TRIGGER_REFRESH_DISTANCE.ConvertToPx()))) /
811 (TRIGGER_REFRESH_DISTANCE.ConvertToPx() - TRIGGER_LOADING_DISTANCE.ConvertToPx());
812 }
813 return std::clamp(static_cast<float>(opacityRatio), 0.0f, 1.0f);
814 }
815
UpdateLoadingMarginTop(float top)816 void RefreshPattern::UpdateLoadingMarginTop(float top)
817 {
818 if (LessNotEqual(top, 0.0)) {
819 return;
820 }
821 CHECK_NULL_VOID(progressChild_);
822 auto progressLayoutProperty = progressChild_->GetLayoutProperty<LoadingProgressLayoutProperty>();
823 CHECK_NULL_VOID(progressLayoutProperty);
824 MarginProperty marginProperty;
825 if (progressLayoutProperty->GetMarginProperty()) {
826 const auto& originMargin = (*progressLayoutProperty->GetMarginProperty());
827 marginProperty.left = originMargin.left;
828 marginProperty.right = originMargin.right;
829 marginProperty.bottom = originMargin.bottom;
830 }
831 marginProperty.top = CalcLength(top);
832 progressLayoutProperty->UpdateMargin(marginProperty);
833 }
834
SetAccessibilityAction()835 void RefreshPattern::SetAccessibilityAction()
836 {
837 auto host = GetHost();
838 CHECK_NULL_VOID(host);
839 auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
840 CHECK_NULL_VOID(accessibilityProperty);
841 accessibilityProperty->SetActionScrollForward([weakPtr = WeakClaim(this)]() {
842 const auto& pattern = weakPtr.Upgrade();
843 CHECK_NULL_VOID(pattern);
844 if (pattern->IsRefreshing()) {
845 return;
846 }
847 pattern->HandleDragStart();
848 for (float delta = 0.0f; delta < MAX_SCROLL_DISTANCE.ConvertToPx();
849 delta += TRIGGER_LOADING_DISTANCE.ConvertToPx()) {
850 pattern->HandleDragUpdate(delta);
851 }
852 pattern->HandleDragEnd();
853 });
854 }
855
UpdateCustomBuilderIndex(int32_t index)856 void RefreshPattern::UpdateCustomBuilderIndex(int32_t index)
857 {
858 if (!HasCustomBuilderIndex() || customBuilderIndex_.value() != index) {
859 customBuilderIndex_ = index;
860 }
861 }
862 } // namespace OHOS::Ace::NG
863