• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-2022 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/refresh/render_refresh.h"
17 
18 #include <chrono>
19 #include <ctime>
20 
21 #include "base/i18n/localization.h"
22 #include "base/utils/string_utils.h"
23 #include "base/utils/utils.h"
24 #include "core/animation/curve_animation.h"
25 #include "core/event/ace_event_helper.h"
26 
27 namespace OHOS::Ace {
28 namespace {
29 
30 constexpr int32_t ANIMATION_DURATION = 350; // Default animation duration
31 constexpr int32_t MAX_ALPHA = 255;
32 constexpr int32_t BASE_YEAR = 1900;
33 constexpr double DEFAULT_TIME_BOX_BOTTOM_SIZE = 8.0; // Default box height for time
34 constexpr double PERCENT = 0.01;                     // Percent
35 constexpr double HALF = 0.5;
36 const char REFRESH_LAST_UPDATED[] = "refresh.last_updated"; // I18n for last updated
37 const char LAST_UPDATE_FORMAT[] = "yyyy/M/d HH:mm";         // Date format for last updated
38 const char PULL_DOWN_START[] = "start";                     // Pull-down event, state is start
39 const char PULL_DOWN_END[] = "end";                         // Pull-down event, state is end
40 
41 } // namespace
42 
RenderRefresh()43 RenderRefresh::RenderRefresh()
44 {
45     loadingComponent_ = AceType::MakeRefPtr<LoadingProgressComponent>();
46     loading_ = AceType::DynamicCast<RenderLoadingProgress>(loadingComponent_->CreateRenderNode());
47     loading_->SetLoadingMode(MODE_DRAG);
48 
49     loadingBackgroundBoxComponent_ = AceType::MakeRefPtr<BoxComponent>();
50     loadingBackgroundBoxComponent_->SetEnableDebugBoundary(true);
51 
52     decoration_ = AceType::MakeRefPtr<Decoration>();
53     loadingBackgroundBox_ = AceType::DynamicCast<RenderBox>(loadingBackgroundBoxComponent_->CreateRenderNode());
54 
55     loadingBoxComponent_ = AceType::MakeRefPtr<BoxComponent>();
56     loadingBoxComponent_->SetFlex(BoxFlex::FLEX_X);
57 
58     loadingBox_ = AceType::DynamicCast<RenderBox>(loadingBoxComponent_->CreateRenderNode());
59 
60     loadingBackgroundBox_->AddChild(loading_);
61     loadingBox_->AddChild(loadingBackgroundBox_);
62 
63     timeBoxComponent_ = AceType::MakeRefPtr<BoxComponent>();
64     timeBoxComponent_->SetFlex(BoxFlex::FLEX_X);
65     timeBox_ = AceType::DynamicCast<RenderBox>(timeBoxComponent_->CreateRenderNode());
66 
67     lastTimeText_ = Localization::GetInstance()->GetEntryLetters(REFRESH_LAST_UPDATED);
68     timeText_ = StringUtils::FormatString(lastTimeText_.c_str(), "");
69     timeComponent_ = AceType::MakeRefPtr<TextComponent>(timeText_);
70 
71     displayComponent_ = AceType::MakeRefPtr<DisplayComponent>();
72 
73     display_ = AceType::DynamicCast<RenderDisplay>(displayComponent_->CreateRenderNode());
74     time_ = AceType::DynamicCast<RenderText>(timeComponent_->CreateRenderNode());
75     display_->AddChild(timeBox_);
76     timeBox_->AddChild(time_);
77 }
78 
Update(const RefPtr<Component> & component)79 void RenderRefresh::Update(const RefPtr<Component>& component)
80 {
81     const RefPtr<RefreshComponent> refresh = AceType::DynamicCast<RefreshComponent>(component);
82     if (!refresh) {
83         LOGW("RefreshComponent is null");
84         return;
85     }
86     if (refresh->GetOnStateChange()) {
87         onStateChange_ = *refresh->GetOnStateChange();
88     }
89     if (refresh->GetOnRefreshing()) {
90         onRefreshing_ = *refresh->GetOnRefreshing();
91     }
92 
93     refreshComponent_ = AceType::WeakClaim(AceType::RawPtr(component));
94     refreshing_ = refresh->IsRefreshing();
95     showLastTime_ = refresh->IsShowLastTime();
96     isUseOffset_ = refresh->IsUseOffset();
97     refreshType_ = refresh->GetRefreshType();
98     progressColor_ = refresh->GetProgressColor();
99     backgroundColor_ = refresh->GetBackgroundColor();
100     frictionRatio_ = refresh->GetFriction() * PERCENT;
101     isRefresh_ = refresh->GetIsRefresh();
102     if (!refresh->GetChangeEvent().IsEmpty()) {
103         changeEvent_ =
104             AceAsyncEvent<void(const std::string&)>::Create(refresh->GetChangeEvent(), context_);
105     }
106 
107     loadingComponent_->SetProgressColor(progressColor_);
108     loadingComponent_->SetDiameter(Dimension(GetLoadingDiameter()));
109 
110     // Set the progress background color
111     decoration_->SetBackgroundColor(backgroundColor_);
112 
113     if (refresh->GetTextDirection() == TextDirection::LTR) {
114         SetTextDirection(TextDirection::LTR);
115         time_->SetTextDirection(TextDirection::LTR);
116     } else {
117         SetTextDirection(TextDirection::RTL);
118         time_->SetTextDirection(TextDirection::RTL);
119     }
120 
121     if (!isInitialized_) {
122         loading_->Attach(GetContext());
123         loadingBackgroundBox_->Attach(GetContext());
124         loadingBox_->Attach(GetContext());
125         AddChild(loadingBox_);
126 
127         timeBox_->Attach(GetContext());
128         display_->Attach(GetContext());
129         time_->Attach(GetContext());
130         timeText_ = StringUtils::FormatString(lastTimeText_.c_str(), GetFormatDateTime().c_str());
131         timeComponent_->SetData(timeText_);
132         timeComponent_->SetTextStyle(refresh->GetTextStyle());
133         time_->Update(timeComponent_);
134         display_->Update(displayComponent_);
135         timeBox_->Update(timeBoxComponent_);
136         AddChild(display_);
137 
138         // Just run on the first time
139         isInitialized_ = true;
140     }
141 
142     loading_->Update(loadingComponent_);
143     loadingBackgroundBox_->Update(loadingBackgroundBoxComponent_);
144     loadingBox_->Update(loadingBoxComponent_);
145 
146     refreshEvent_ = AceAsyncEvent<void(const std::string&)>::Create(refresh->GetRefreshEventId(), context_);
147     pullDownEvent_ = AceAsyncEvent<void(const std::string&)>::Create(refresh->GetPulldownEventId(), context_);
148     CalcLoadingParams(component);
149     Initialize();
150     MarkNeedLayout();
151 }
152 
CalcLoadingParams(const RefPtr<Component> & component)153 void RenderRefresh::CalcLoadingParams(const RefPtr<Component>& component)
154 {
155     auto refresh = AceType::DynamicCast<RefreshComponent>(component);
156     if (refresh == nullptr) {
157         LOGW("RefreshComponent is null");
158         return;
159     }
160     auto context = context_.Upgrade();
161     if (context == nullptr) {
162         LOGW("context is nullptr!");
163         return;
164     }
165     scale_ = context->GetDipScale();
166     triggerLoadingDistance_ = NormalizeToPx(refresh->GetLoadingDistance());
167     triggerShowTimeDistance_ = NormalizeToPx(refresh->GetShowTimeDistance());
168     if (showLastTime_) {
169         timeDistance_ = NormalizeToPx(Dimension(DEFAULT_TIME_BOX_BOTTOM_SIZE, DimensionUnit::VP));
170         triggerRefreshDistance_ = triggerShowTimeDistance_;
171     } else {
172         triggerRefreshDistance_ = NormalizeToPx(refresh->GetRefreshDistance());
173         inspectorOffset_ = refresh->GetRefreshDistance();
174     }
175     loadingDiameter_ = NormalizeToPx(refresh->GetProgressDiameter());
176     maxScrollOffset_ = NormalizeToPx(refresh->GetMaxDistance());
177     indicatorOffset_ = NormalizeToPx(refresh->GetIndicatorOffset());
178     timeOffset_ = NormalizeToPx(refresh->GetTimeOffset());
179     loading_->SetDiameter(refresh->GetProgressDiameter());
180     loading_->SetDragRange(triggerLoadingDistance_, triggerRefreshDistance_);
181     loadingBox_->SetHeight(loadingDiameter_);
182     decoration_->SetBorderRadius(Radius(loadingDiameter_ * HALF));
183     loadingBackgroundBox_->SetBackDecoration(decoration_);
184     loadingBackgroundBox_->SetWidth(loadingDiameter_);
185 }
186 
Initialize()187 void RenderRefresh::Initialize()
188 {
189     LOGI("RenderRefresh Initialize state:%{public}d", refreshing_);
190     if (!dragDetector_) {
191         dragDetector_ = AceType::MakeRefPtr<VerticalDragRecognizer>();
192         dragDetector_->SetOnDragUpdate([weakFresh = AceType::WeakClaim(this)](const DragUpdateInfo& info) {
193             auto refresh = weakFresh.Upgrade();
194             if (refresh) {
195                 refresh->HandleDragUpdate(info.GetMainDelta());
196             }
197         });
198         dragDetector_->SetOnDragEnd([weakFresh = AceType::WeakClaim(this)](const DragEndInfo& info) {
199             auto refresh = weakFresh.Upgrade();
200             if (refresh) {
201                 refresh->HandleDragEnd();
202             }
203         });
204 
205         dragDetector_->SetOnDragCancel([weakFresh = AceType::WeakClaim(this)]() {
206             auto refresh = weakFresh.Upgrade();
207             if (refresh) {
208                 refresh->HandleDragCancel();
209             }
210         });
211     }
212     if (!animator_) {
213         animator_ = CREATE_ANIMATOR(GetContext());
214     }
215     if (!refreshController_) {
216         refreshController_ = AceType::MakeRefPtr<RefreshController>();
217         refreshController_->SetRefresh(AceType::WeakClaim(this));
218     }
219     InitAccessibilityEventListener();
220 }
221 
InitAccessibilityEventListener()222 void RenderRefresh::InitAccessibilityEventListener()
223 {
224     auto refNode = accessibilityNode_.Upgrade();
225     if (!refNode) {
226         return;
227     }
228     refNode->AddSupportAction(AceAction::ACTION_SCROLL_FORWARD);
229     auto weakPtr = AceType::WeakClaim(this);
230     refNode->SetActionScrollForward([weakPtr]() {
231         auto refresh = weakPtr.Upgrade();
232         if (refresh) {
233             refresh->SetRefreshStatus(true);
234             refresh->MarkNeedLayout();
235         }
236         return true;
237     });
238 }
239 
UpdateTouchRect()240 void RenderRefresh::UpdateTouchRect()
241 {
242     touchRect_.SetSize(viewPort_);
243     touchRect_.SetOffset(GetPosition());
244     touchRectList_.emplace_back(touchRect_);
245     SetTouchRectList(touchRectList_);
246 }
247 
HandleDragUpdate(double delta)248 void RenderRefresh::HandleDragUpdate(double delta)
249 {
250     if (isRefresh_) {
251         return;
252     }
253     if (NearZero(delta)) {
254         return;
255     }
256     if (refreshStatus_ == RefreshStatus::REFRESH && delta > 0.0) {
257         return;
258     }
259     Offset deltaOffset(0, delta);
260     if (refreshStatus_ == RefreshStatus::DRAG || refreshStatus_ == RefreshStatus::OVER_DRAG ||
261         refreshStatus_ == RefreshStatus::DONE) {
262         deltaOffset.SetY(GetOffset(delta));
263     }
264     scrollableOffset_ += deltaOffset;
265     scrollableOffset_.SetY(std::clamp(scrollableOffset_.GetY(), 0.0, maxScrollOffset_));
266     MarkNeedLayout();
267 }
268 
HandleDragEnd()269 void RenderRefresh::HandleDragEnd()
270 {
271     if (NearEqual(scrollableOffset_.GetY(), 0.0f)) {
272         ResetStatus();
273         return;
274     }
275     if (refreshStatus_ == RefreshStatus::INACTIVE) {
276         return;
277     }
278     if (refreshStatus_ == RefreshStatus::DRAG || refreshStatus_ == RefreshStatus::DONE) {
279         double start = scrollableOffset_.GetY();
280         double end = 0.0;
281         StartAnimation(start, end, false);
282         return;
283     }
284     if (refreshStatus_ == RefreshStatus::OVER_DRAG) {
285         double start = scrollableOffset_.GetY();
286         double end = triggerRefreshDistance_;
287         loading_->SetLoadingMode(MODE_LOOP);
288         StartAnimation(start, end, false);
289     }
290 }
291 
HandleDragCancel()292 void RenderRefresh::HandleDragCancel()
293 {
294     ResetStatus();
295 }
296 
ResetStatus()297 void RenderRefresh::ResetStatus()
298 {
299     refreshing_ = false;
300     if (changeEvent_) {
301         changeEvent_("false");
302     }
303     refreshStatus_ = RefreshStatus::INACTIVE;
304     scrollableOffset_.Reset();
305     loading_->SetLoadingMode(MODE_DRAG);
306     loading_->SetDragDistance(scrollableOffset_.GetY());
307     MarkNeedLayout();
308 }
309 
UpdateScrollOffset(double value)310 void RenderRefresh::UpdateScrollOffset(double value)
311 {
312     scrollableOffset_.SetY(value);
313     MarkNeedLayout();
314 }
315 
FireRefreshEvent() const316 void RenderRefresh::FireRefreshEvent() const
317 {
318     if (refreshEvent_) {
319         LOGI("RefreshEvent, refreshing = %{public}d.", refreshing_);
320         std::string param =
321             std::string(R"("refresh",{"refreshing":)").append(refreshing_ ? "true" : "false").append("},null");
322         refreshEvent_(param);
323     }
324     if (onRefreshing_) {
325         onRefreshing_();
326     }
327 }
328 
FirePullDownEvent(const std::string & state) const329 void RenderRefresh::FirePullDownEvent(const std::string& state) const
330 {
331     if (pullDownEvent_) {
332         LOGI("PullDown Event, state is %{public}s", state.c_str());
333         std::string param = std::string(R"("pulldown",{"state":")").append(state).append("\"},null");
334         pullDownEvent_(param);
335     }
336 }
337 
StartAnimation(double start,double end,bool isFinished)338 void RenderRefresh::StartAnimation(double start, double end, bool isFinished)
339 {
340     animator_->ClearInterpolators();
341     animator_->ClearStopListeners();
342     translate_ = AceType::MakeRefPtr<CurveAnimation<double>>(start, end, Curves::FRICTION);
343     auto weak = AceType::WeakClaim(this);
344     translate_->AddListener(Animation<double>::ValueCallback([weak](double value) {
345         auto scroll = weak.Upgrade();
346         if (scroll) {
347             scroll->UpdateScrollOffset(value);
348         }
349     }));
350     animator_->SetDuration(ANIMATION_DURATION);
351     animator_->AddInterpolator(translate_);
352     animator_->AddStopListener([weak, isFinished]() {
353         auto refresh = weak.Upgrade();
354         if (refresh) {
355             refresh->HandleStopListener(isFinished);
356         }
357     });
358 
359     animator_->Play();
360 }
361 
HandleStopListener(const bool isFinished)362 void RenderRefresh::HandleStopListener(const bool isFinished)
363 {
364     // Update the last loading time
365     if (isFinished) {
366         timeComponent_->SetData(timeText_);
367         time_->Update(timeComponent_);
368         return;
369     }
370 
371     if (NearEqual(scrollableOffset_.GetY(), triggerRefreshDistance_)) {
372         if (refreshing_) {
373             loading_->SetLoadingMode(MODE_LOOP);
374         }
375         refreshing_ = true;
376         if (changeEvent_) {
377             changeEvent_("true");
378         }
379         FireRefreshEvent();
380     } else if (NearEqual(scrollableOffset_.GetY(), 0.0f)) {
381         ResetStatus();
382     } else {
383         loading_->SetLoadingMode(MODE_DRAG);
384     }
385 }
386 
GetNextStatus()387 RefreshStatus RenderRefresh::GetNextStatus()
388 {
389     RefreshStatus nextStatus;
390     auto context = context_.Upgrade();
391     switch (refreshStatus_) {
392         case RefreshStatus::INACTIVE:
393             if (refreshing_) {
394                 StartAnimation(0.0, triggerRefreshDistance_, false);
395                 nextStatus = RefreshStatus::REFRESH;
396                 break;
397             }
398             if (LessOrEqual(scrollableOffset_.GetY(), 0.0)) {
399                 nextStatus = RefreshStatus::INACTIVE;
400                 break;
401             }
402             // No break here, continue next case
403             FirePullDownEvent(PULL_DOWN_START);
404             [[fallthrough]];
405         case RefreshStatus::DRAG:
406             if (LessOrEqual(scrollableOffset_.GetY(), 0.0)) {
407                 FirePullDownEvent(PULL_DOWN_END);
408                 nextStatus = RefreshStatus::INACTIVE;
409             } else if (scrollableOffset_.GetY() < triggerRefreshDistance_) {
410                 nextStatus = RefreshStatus::DRAG;
411             } else {
412                 nextStatus = RefreshStatus::OVER_DRAG;
413             }
414             break;
415         case RefreshStatus::OVER_DRAG:
416             if (!refreshEvent_ && !context->GetIsDeclarative()) {
417                 nextStatus = RefreshStatus::DONE;
418                 break;
419             }
420             if (scrollableOffset_.GetY() > triggerRefreshDistance_) {
421                 nextStatus = RefreshStatus::OVER_DRAG;
422                 break;
423             }
424             // No break here, continue get next status.
425             [[fallthrough]];
426         case RefreshStatus::REFRESH:
427             if (!refreshing_) {
428                 timeText_ = StringUtils::FormatString(lastTimeText_.c_str(), GetFormatDateTime().c_str());
429                 StartAnimation(scrollableOffset_.GetY(), 0.0, true);
430                 nextStatus = RefreshStatus::DONE;
431                 break;
432             }
433             nextStatus = RefreshStatus::REFRESH;
434             break;
435         case RefreshStatus::DONE:
436             if (scrollableOffset_.GetY() > 0.0) {
437                 nextStatus = RefreshStatus::DONE;
438             } else {
439                 FirePullDownEvent(PULL_DOWN_END);
440                 nextStatus = RefreshStatus::INACTIVE;
441             }
442             break;
443         default:
444             nextStatus = RefreshStatus::INACTIVE;
445             break;
446     }
447     if (onStateChange_ && (refreshStatus_ != nextStatus)) {
448         onStateChange_(static_cast<int>(nextStatus));
449     }
450     return nextStatus;
451 }
452 
GetFriction(double percentage) const453 double RenderRefresh::GetFriction(double percentage) const
454 {
455     if (NearEqual(percentage, 1.0)) {
456         return 0.0;
457     } else {
458         return frictionRatio_ * std::pow(1.0 - percentage, SQUARE);
459     }
460 }
461 
GetOffset(double delta) const462 double RenderRefresh::GetOffset(double delta) const
463 {
464     double height = GetLayoutSize().Height();
465     if (!NearZero(height)) {
466         double friction = GetFriction(std::abs(scrollableOffset_.GetY() / height));
467         return friction * delta;
468     }
469     return delta;
470 }
471 
MaxScrollableHeight() const472 double RenderRefresh::MaxScrollableHeight() const
473 {
474     return GetLayoutParam().GetMaxSize().Height();
475 }
476 
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)477 void RenderRefresh::OnTouchTestHit(
478     const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
479 {
480     if (!dragDetector_ || hasScrollableChild_) {
481         return;
482     }
483     dragDetector_->SetCoordinateOffset(coordinateOffset);
484     result.emplace_back(dragDetector_);
485 }
486 
GetLoadingDiameter() const487 double RenderRefresh::GetLoadingDiameter() const
488 {
489     double diameter = 0.0;
490     if (scrollableOffset_.GetY() < triggerLoadingDistance_) {
491         return diameter;
492     } else if (scrollableOffset_.GetY() < triggerRefreshDistance_) {
493         double maxDistance = triggerRefreshDistance_ - triggerLoadingDistance_;
494         double actualDistance = scrollableOffset_.GetY() - triggerLoadingDistance_;
495 
496         // Get the diameter by actual distance
497         diameter = ((actualDistance * loadingDiameter_ * HALF) / maxDistance) + loadingDiameter_ * HALF;
498     } else {
499         diameter = loadingDiameter_;
500     }
501     return diameter;
502 }
503 
GetLoadingOffset() const504 Offset RenderRefresh::GetLoadingOffset() const
505 {
506     auto pipelineContext = GetContext().Upgrade();
507     if (!pipelineContext) {
508         LOGE("pipelineContext update with nullptr");
509         return Offset::Zero();
510     }
511 
512     if (scrollableOffset_.GetY() < triggerLoadingDistance_) {
513         return Offset::Zero();
514     }
515     if (!isUseOffset_) {
516         return scrollableOffset_ * HALF - Offset(0.0, GetLoadingDiameter() * HALF);
517     }
518     double factor =
519         (scrollableOffset_.GetY() - triggerLoadingDistance_) / (triggerRefreshDistance_ - triggerLoadingDistance_);
520     return Offset(0.0, indicatorOffset_ * factor);
521 }
522 
GetShowTimeOffset() const523 Offset RenderRefresh::GetShowTimeOffset() const
524 {
525     auto pipelineContext = GetContext().Upgrade();
526     if (!pipelineContext) {
527         LOGE("pipelineContext update with nullptr");
528         return Offset::Zero();
529     }
530 
531     double bottomOffset = timeBox_->GetLayoutSize().Height() +
532                           pipelineContext->NormalizeToPx(Dimension(DEFAULT_TIME_BOX_BOTTOM_SIZE, DimensionUnit::VP));
533     return scrollableOffset_ - Offset(0.0, bottomOffset + timeOffset_);
534 }
535 
GetOpacity() const536 double RenderRefresh::GetOpacity() const
537 {
538     double factor = 0.0;
539     if (scrollableOffset_.GetY() < triggerRefreshDistance_ - timeDistance_) {
540         return factor;
541     } else if (scrollableOffset_.GetY() < triggerRefreshDistance_) {
542         double actualDistance = scrollableOffset_.GetY() - triggerRefreshDistance_ + timeDistance_;
543 
544         // Get the factor, timeDistance_ never be zero
545         if (!NearZero(timeDistance_)) {
546             factor = actualDistance / timeDistance_;
547         }
548     } else {
549         factor = 1.0;
550     }
551     return factor;
552 }
553 
GetFormatDateTime()554 std::string RenderRefresh::GetFormatDateTime()
555 {
556     auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
557     auto local = std::localtime(&now);
558     if (local == nullptr) {
559         LOGE("Get localtime failed.");
560         return "";
561     }
562 
563     // This is for i18n date time
564     DateTime dateTime;
565     dateTime.year = static_cast<uint32_t>(local->tm_year + BASE_YEAR);
566     dateTime.month = static_cast<uint32_t>(local->tm_mon);
567     dateTime.day = static_cast<uint32_t>(local->tm_mday);
568     dateTime.hour = static_cast<uint32_t>(local->tm_hour);
569     dateTime.minute = static_cast<uint32_t>(local->tm_min);
570     std::string time = Localization::GetInstance()->FormatDateTime(dateTime, LAST_UPDATE_FORMAT);
571     return time;
572 }
573 
UpdateScrollableOffset(double delta)574 void RenderRefresh::UpdateScrollableOffset(double delta)
575 {
576     if (NearZero(delta)) {
577         LOGW("Delta is near zero!");
578         return;
579     }
580     if (refreshStatus_ == RefreshStatus::REFRESH) {
581         LOGW("The refresh status is refreshing!");
582         return;
583     }
584     Offset offset = Offset(0.0, GetOffset(delta));
585     scrollableOffset_ = scrollableOffset_ + offset;
586     scrollableOffset_.SetY(std::min(scrollableOffset_.GetY(), maxScrollOffset_));
587     MarkNeedLayout();
588 }
589 
OnHiddenChanged(bool hidden)590 void RenderRefresh::OnHiddenChanged(bool hidden)
591 {
592     if (hidden) {
593         return;
594     }
595     ResetStatus();
596 }
597 
PerformLayout()598 void RenderRefresh::PerformLayout()
599 {
600     const auto& children = GetChildren();
601     if (children.empty()) {
602         LOGW("Refresh has no child!");
603         return;
604     }
605     auto context = context_.Upgrade();
606     if (context == nullptr) {
607         LOGW("context is nullptr!");
608         return;
609     }
610 
611     RefreshStatus nextState = GetNextStatus();
612     if (nextState != RefreshStatus::REFRESH && refreshStatus_ == RefreshStatus::REFRESH) {
613         loading_->SetLoadingMode(MODE_EXIT);
614     }
615     refreshStatus_ = nextState;
616     LayoutParam innerLayout = GetLayoutParam();
617     innerLayout.SetMinSize(Size(0.0, 0.0));
618     if (!NearEqual(scale_, context->GetDipScale())) {
619         // Notify loading to updated when window size changed.
620         CalcLoadingParams(refreshComponent_.Upgrade());
621         loading_->Layout(innerLayout);
622     }
623 
624     loading_->SetDragDistance(scrollableOffset_.GetY());
625     loadingBox_->SetPosition(GetLoadingOffset());
626 
627     display_->UpdateOpacity(GetOpacity() * MAX_ALPHA);
628     display_->SetPosition(GetShowTimeOffset());
629     loadingBox_->SetHidden(scrollableOffset_.GetY() < triggerLoadingDistance_);
630     loadingBox_->SetVisible(scrollableOffset_.GetY() > triggerLoadingDistance_);
631 
632     columnChild_ = children.back();
633     columnChild_->Layout(innerLayout);
634     if (refreshType_ == RefreshType::PULL_DOWN && GreatNotEqual(scrollableOffset_.GetY(), 0.0)) {
635         columnChild_->SetPosition(scrollableOffset_);
636     } else {
637         columnChild_->SetPosition(Offset::Zero());
638     }
639 
640     display_->Layout(innerLayout);
641     loadingBox_->Layout(innerLayout);
642     timeBox_->Layout(innerLayout);
643     SetLayoutSize(GetLayoutParam().GetMaxSize());
644 }
645 
646 } // namespace OHOS::Ace
647