• 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/scroll/render_multi_child_scroll.h"
17 
18 #include "core/animation/curve.h"
19 #include "core/animation/curve_animation.h"
20 #include "core/common/vibrator/vibrator_proxy.h"
21 #include "core/components/box/render_box.h"
22 #include "core/event/ace_event_helper.h"
23 
24 namespace OHOS::Ace {
25 namespace {
26 
27 constexpr double NONE_SCROLL_POSITION = -1.0;
28 constexpr double MIN_EXTENT = 5.0;
29 constexpr double EXTENT_RATIO = 2.0;
30 constexpr int32_t ANIMATION_DURATION = 200;
31 constexpr double LIST_INCONTINUOUS_ROTATION_SENSITYVITY_NORMAL = 0.6;
32 constexpr double LIST_CONTINUOUS_ROTATION_SENSITYVITY_NORMAL = 1.0;
33 constexpr double LIST_ITEMCENTER_ROTATION_THRESHOLD = 0.7;
34 constexpr int32_t COMPATIBLE_VERSION = 6;
35 
36 #ifdef WEARABLE_PRODUCT
37 const std::string& VIBRATOR_TYPE_WATCH_CROWN_STRENGTH3 = "watchhaptic.crown.strength3";
38 #endif
39 
40 } // namespace
41 
AddChild(const RefPtr<RenderList> & child)42 void RenderMultiChildScroll::AddChild(const RefPtr<RenderList>& child)
43 {
44     RenderNode::AddChild(child);
45 }
46 
MakeInnerLayoutParam() const47 LayoutParam RenderMultiChildScroll::MakeInnerLayoutParam() const
48 {
49     LayoutParam layout;
50     if (axis_ == Axis::VERTICAL) {
51         layout.SetMinSize(Size(viewPort_.Width(), layout.GetMinSize().Height()));
52         layout.SetMaxSize(Size(viewPort_.Width(), layout.GetMaxSize().Height()));
53     } else {
54         layout.SetMinSize(Size(layout.GetMinSize().Width(), viewPort_.Height()));
55         layout.SetMaxSize(Size(layout.GetMaxSize().Width(), viewPort_.Height()));
56     }
57     return layout;
58 }
59 
ProcessScrollExtent()60 void RenderMultiChildScroll::ProcessScrollExtent()
61 {
62     // When the scrollBar is off, return
63     if (!scrollBar_ || !scrollBar_->NeedScrollBar()) {
64         return;
65     }
66 
67     if (NearEqual(scrollBarExtent_, 0.0)) {
68         scrollBarExtent_ = mainScrollExtent_;
69         return;
70     }
71 
72     if (mainScrollExtent_ - scrollBarExtent_ > MIN_EXTENT && !scrollBarExtentFlag_) {
73         scrollBarExtentFlag_ = true;
74         auto animation =
75             AceType::MakeRefPtr<CurveAnimation<double>>(scrollBarExtent_, mainScrollExtent_, Curves::SHARP);
76         animation->AddListener([weakScroll = AceType::WeakClaim(this)](double value) {
77             auto scroll = weakScroll.Upgrade();
78             if (scroll) {
79                 scroll->SetMainScrollExtentForBar(value);
80                 scroll->MarkNeedLayout(true);
81             }
82         });
83         if (animateController_) {
84             animateController_->ClearInterpolators();
85             animateController_->SetDuration(ANIMATION_DURATION);
86 
87             // add the new animation
88             animateController_->AddInterpolator(animation);
89             animateController_->Play();
90         }
91     } else if (!scrollBarExtentFlag_) {
92         scrollBarExtent_ = mainScrollExtent_;
93     }
94 }
95 
CalculateMainScrollExtent()96 bool RenderMultiChildScroll::CalculateMainScrollExtent()
97 {
98     Size itemSize; // Calculate all children layout size.
99     for (const auto& child : GetChildren()) {
100         itemSize += child->GetLayoutSize();
101     }
102 
103     bool isScrollable = false;
104     if (axis_ == Axis::VERTICAL) {
105         double paddingVertical = NormalizeToPx(padding_.Top()) + NormalizeToPx(padding_.Bottom());
106         mainScrollExtent_ = itemSize.Height() + paddingVertical + outBoundaryExtent_;
107         ProcessScrollExtent();
108         if (mainScrollExtent_ > viewPort_.Height()) {
109             isScrollable = true;
110         }
111     } else {
112         double paddingHorizontal = NormalizeToPx(padding_.Left()) + NormalizeToPx(padding_.Right());
113         mainScrollExtent_ = itemSize.Width() + paddingHorizontal + outBoundaryExtent_;
114         scrollBarExtent_ = mainScrollExtent_;
115         if (mainScrollExtent_ > viewPort_.Width()) {
116             isScrollable = true;
117         }
118     }
119 
120     // If not scrollable, reset scrollable_ to null.
121     if (!isScrollable) {
122         if (scrollable_) {
123             scrollable_->MarkAvailable(false);
124             if (scrollable_->Idle() && GetMainOffset(currentOffset_) > 0.0) {
125                 scrollEffect_->ProcessScrollOver(0.0);
126             }
127         }
128         if (scrollBar_) {
129             scrollBar_->SetScrollable(false);
130         }
131         if (positionController_) {
132             positionController_->SetNonScrollable();
133         }
134     } else {
135         if (scrollable_ && scrollable_->Available()) {
136             if (scrollable_->Idle() &&
137                 GreatNotEqual(GetMainOffset(currentOffset_), mainScrollExtent_ - GetMainSize(viewPort_))) {
138                 // scroll to bottom
139                 scrollEffect_->ProcessScrollOver(0.0);
140             }
141         } else {
142             if (scrollable_) {
143                 scrollable_->MarkAvailable(true);
144             }
145         }
146         if (scrollBar_) {
147             scrollBar_->SetScrollable(true);
148         }
149     }
150 
151     return isScrollable;
152 }
153 
JumpToIndex(int32_t index,int32_t source)154 void RenderMultiChildScroll::JumpToIndex(int32_t index, int32_t source)
155 {
156     if (GetChildren().empty()) {
157         LOGE("no list in scroll");
158         return;
159     }
160     auto listBase = AceType::DynamicCast<RenderList>(GetChildren().front());
161     if (!listBase) {
162         LOGE("no list to jump");
163         return;
164     }
165 
166     double position = listBase->CalculateItemPosition(index, ScrollType::SCROLL_INDEX);
167     if (position < 0.0) {
168         LOGE("no this index: %{public}d", index);
169         return;
170     }
171     LOGI("jump to index:%{public}d position:%{public}lf", index, position);
172     if (CalculateMainScrollExtent()) {
173         RenderScroll::JumpToPosition(position, source);
174     } else {
175         LOGW("Current is not allow to jump index.");
176     }
177 }
178 
JumpToPosition(double position,int32_t source)179 void RenderMultiChildScroll::JumpToPosition(double position, int32_t source)
180 {
181     if (GetChildren().empty()) {
182         LOGE("no list in scroll");
183         return;
184     }
185     auto listBase = AceType::DynamicCast<RenderList>(GetChildren().front());
186     if (!listBase) {
187         LOGE("no list to jump");
188         return;
189     }
190     listBase->CalculateItemPosition(position);
191     LOGI("jump to position:%{public}lf", position);
192     if (CalculateMainScrollExtent()) {
193         RenderScroll::JumpToPosition(position, source);
194     } else {
195         LOGW("Current is not allow to jump position.");
196     }
197 }
198 
UpdateEdgeEffect(const RefPtr<ListComponent> & listComponent)199 void RenderMultiChildScroll::UpdateEdgeEffect(const RefPtr<ListComponent>& listComponent)
200 {
201     auto newEffect = listComponent->GetScrollEffect();
202     if (scrollEffect_ != newEffect) {
203         scrollEffect_ = newEffect;
204         if (scrollEffect_) {
205             ResetEdgeEffect();
206         }
207     }
208 }
209 
UpdateGradient(const RefPtr<ListComponent> & listComponent)210 void RenderMultiChildScroll::UpdateGradient(const RefPtr<ListComponent>& listComponent)
211 {
212     gradientWidth_ = listComponent->GetGradientWidth();
213     backgroundColor_ = listComponent->GetBackgroundColor();
214 }
215 
Update(const RefPtr<Component> & component)216 void RenderMultiChildScroll::Update(const RefPtr<Component>& component)
217 {
218     auto listComponent = AceType::DynamicCast<ListComponent>(component);
219     if (!listComponent) {
220         LOGE("component is not a ListComponent");
221         return;
222     }
223 
224     auto context = GetContext().Upgrade();
225     if (!context) {
226         LOGE("context is nullptr");
227         return;
228     }
229     if (context->IsJsCard()) {
230         cacheExtent_ = (std::numeric_limits<double>::max)();
231     }
232 
233     scrollVibrate_ = listComponent->GetScrollVibrate();
234     if (scrollVibrate_ && !vibrator_ && context) {
235         vibrator_ = VibratorProxy::GetInstance().GetVibrator(context->GetTaskExecutor());
236     }
237 
238     rotationVibrate_ = listComponent->IsRotationVibrate();
239     if (rotationVibrate_ && !vibrator_ && context) {
240         vibrator_ = VibratorProxy::GetInstance().GetVibrator(context->GetTaskExecutor());
241     }
242 
243     if (listComponent->IsInRefresh()) {
244         auto parent = GetParent().Upgrade();
245         while (parent) {
246             auto refresh = AceType::DynamicCast<RenderRefresh>(parent);
247             if (refresh) {
248                 refreshParent_ = AceType::WeakClaim(AceType::RawPtr(refresh));
249                 break;
250             }
251             parent = parent->GetParent().Upgrade();
252         }
253     }
254 
255     bool directionFlag = false;
256     LOGI("RenderMultiChildScroll Update:GetDirection(): %{public}d, listComponent->GetDirection() is: %{public}d",
257         GetDirection(), listComponent->GetDirection());
258     if (GetDirection() != listComponent->GetDirection()) {
259         SetDirection(listComponent->GetDirection());
260         directionFlag = true;
261     }
262 
263     auto axis = (GetDirection() == FlexDirection::COLUMN || GetDirection() == FlexDirection::COLUMN_REVERSE)
264                     ? Axis::VERTICAL
265                     : Axis::HORIZONTAL;
266     if (axis_ != axis) {
267         axis_ = axis;
268         directionFlag = true;
269     }
270     if (directionFlag) {
271         ResetScrollable();
272     }
273 
274     if (scrollable_) {
275         scrollable_->SetOverSpringProperty(listComponent->OverSpringProperty());
276         scrollable_->MarkNeedCenterFix(listComponent->GetSupportItemCenter());
277     }
278 
279     // sync scrollpage from List child
280     SetScrollPage(listComponent->GetScrollPage());
281 
282     // Update its child.
283     auto children = GetChildren();
284     if (!children.empty()) {
285         auto listNode = children.front();
286         if (listNode) {
287             listNode->Update(component);
288             listNode->Attach(GetContext());
289         }
290     }
291 
292     UpdateGradient(listComponent);
293     UpdateEdgeEffect(listComponent);
294 
295     auto newController = listComponent->GetPositionController();
296     if (positionController_ != newController) {
297         positionController_ = newController;
298         if (positionController_) {
299             positionController_->SetScrollEvent(ScrollEvent::SCROLL_TOP,
300                 AceAsyncEvent<void(std::shared_ptr<ScrollEventInfo>&)>::Create(listComponent->GetOnScrollTop(),
301                 GetContext()));
302             positionController_->SetScrollEvent(ScrollEvent::SCROLL_BOTTOM,
303                 AceAsyncEvent<void(std::shared_ptr<ScrollEventInfo>&)>::Create(listComponent->GetOnScrollBottom(),
304                 GetContext()));
305             positionController_->SetScrollEvent(ScrollEvent::SCROLL_TOUCHUP,
306                 AceAsyncEvent<void(std::shared_ptr<ScrollEventInfo>&)>::Create(listComponent->GetOnScrollTouchUp(),
307                 GetContext()));
308             positionController_->SetScrollEvent(ScrollEvent::SCROLL_END,
309                 AceAsyncEvent<void(std::shared_ptr<ScrollEventInfo>&)>::Create(listComponent->GetOnScrollEnd(),
310                 GetContext()));
311             positionController_->SetScrollEvent(ScrollEvent::SCROLL_POSITION,
312                 AceAsyncEvent<void(std::shared_ptr<ScrollEventInfo>&)>::Create(listComponent->GetOnScroll(),
313                 GetContext()));
314             positionController_->SetScrollNode(AceType::WeakClaim(this));
315         }
316     }
317     if (positionController_) {
318         initialIndex_ = positionController_->GetInitialIndex();
319         initialOffset_ = positionController_->GetInitialOffset();
320     }
321 
322     if (!animateController_) {
323         animateController_ = CREATE_ANIMATOR(GetContext());
324         animateController_->AddStopListener([weakScroll = AceType::WeakClaim(this)]() {
325             auto scroll = weakScroll.Upgrade();
326             if (scroll) {
327                 scroll->scrollBarExtentFlag_ = false;
328             }
329         });
330     }
331 
332     auto scrollBar = listComponent->GetScrollBar();
333     rightToLeft_ = listComponent->GetRightToLeft();
334     InitScrollBar(scrollBar);
335 
336     // This should be put after setting positionController_.
337     RenderScroll::Update(component);
338 }
339 
ReachMaxCount() const340 bool RenderMultiChildScroll::ReachMaxCount() const
341 {
342     bool reached = true;
343     for (const auto& child : GetChildren()) {
344         auto listBase = AceType::DynamicCast<RenderList>(child);
345         if (!listBase) {
346             continue;
347         }
348         int32_t currentIndex = listBase->GetCurrentMaxIndex();
349         int32_t maxCount = listBase->GetMaxCount();
350         if (maxCount <= 0 || currentIndex != (maxCount - 1)) {
351             reached = false;
352             break;
353         }
354     }
355     return reached;
356 }
357 
OnPredictLayout(int64_t deadline)358 void RenderMultiChildScroll::OnPredictLayout(int64_t deadline)
359 {
360     int32_t childrenSize = static_cast<int32_t>(GetChildren().size());
361     if (currentIndex_ < 0 || currentIndex_ >= childrenSize || childrenSize == 0) {
362         LOGE("invalid current index: %{public}d, size is: %{public}d", currentIndex_, childrenSize);
363         return;
364     }
365     // only in list widget enabled build next
366     auto child = GetChildren().front();
367     auto listBase = AceType::DynamicCast<RenderList>(child);
368     if (listBase) {
369         Offset lastOffset = Offset::Zero() - currentOffset_ - correctedDelta_;
370         Offset curOffset = lastOffset;
371         double mainOffset = GetMainOffset(curOffset);
372         double mainExtent = GetMainSize(viewPort_);
373         double layoutHead = 0.0;
374         double layoutTail = mainExtent;
375         if (IsRowReverse() || IsColReverse()) {
376             layoutHead = layoutHead - cacheExtent_ + mainOffset;
377             layoutTail = layoutTail + cacheExtent_ + mainOffset;
378         } else {
379             layoutHead = layoutHead - cacheExtent_ - mainOffset;
380             layoutTail = layoutTail + cacheExtent_ - mainOffset;
381         }
382         listBase->BuildNextItem(layoutHead, layoutTail, curOffset, viewPort_);
383     }
384 }
385 
LayoutChild(const RefPtr<RenderNode> & child,const Offset & position,double start,double end)386 void RenderMultiChildScroll::LayoutChild(
387     const RefPtr<RenderNode>& child, const Offset& position, double start, double end)
388 {
389     auto listBase = AceType::DynamicCast<RenderList>(child);
390     if (listBase) {
391         listBase->ResetLayoutRange(start, end, position, viewPort_);
392         listBase->SetLayoutParam(GetLayoutParam());
393         listBase->SetNeedLayout(true);
394         listBase->OnLayout();
395     }
396 }
397 
LayoutChild(const RefPtr<RenderNode> & curChild,int32_t curIndex,const RefPtr<RenderNode> & lastChild)398 bool RenderMultiChildScroll::LayoutChild(
399     const RefPtr<RenderNode>& curChild, int32_t curIndex, const RefPtr<RenderNode>& lastChild)
400 {
401     Offset lastOffset = Offset::Zero() - currentOffset_;
402     Size lastSize;
403     if (lastChild) {
404         lastOffset = lastChild->GetPosition();
405         lastSize = lastChild->GetLayoutSize();
406     }
407 
408     Offset curOffset = lastOffset + lastSize;
409     double mainOffset = GetMainOffset(curOffset);
410     double mainExtent = GetMainSize(viewPort_);
411 
412     // The following children are not visible.
413     if (lastChild && mainOffset >= mainExtent + cacheExtent_) {
414         return false;
415     }
416 
417     // The last child become invisible, change the current index.
418     if (mainOffset <= -cacheExtent_) {
419         currentIndex_ = curIndex;
420     }
421 
422     double layoutHead = 0.0;
423     double layoutTail = mainExtent;
424 
425     if (IsRowReverse() || IsColReverse()) {
426         layoutHead = layoutHead - cacheExtent_ + mainOffset;
427         layoutTail = layoutTail + cacheExtent_ + mainOffset;
428     } else {
429         layoutHead = layoutHead - cacheExtent_ - mainOffset;
430         layoutTail = layoutTail + cacheExtent_ - mainOffset;
431     }
432     LayoutChild(curChild, curOffset, layoutHead, layoutTail);
433 
434     return true;
435 }
436 
LayoutChild()437 void RenderMultiChildScroll::LayoutChild()
438 {
439     int32_t childrenSize = static_cast<int32_t>(GetChildren().size());
440     if (currentIndex_ < 0 || currentIndex_ >= childrenSize) {
441         LOGE("invalid current index: %{public}d", currentIndex_);
442         return;
443     }
444 
445     // currentIndex_ is 0 at the beginning.
446     int32_t currentIndex = 0;
447     RefPtr<RenderNode> lastChild;
448     for (const auto& child : GetChildren()) {
449         if (currentIndex >= currentIndex_) {
450             if (!LayoutChild(child, currentIndex, lastChild)) {
451                 LOGE("layout child failed, index:%{public}d", currentIndex);
452                 break;
453             }
454             lastChild = child;
455         }
456         currentIndex++;
457     }
458 }
459 
PerformLayout()460 void RenderMultiChildScroll::PerformLayout()
461 {
462     auto context = context_.Upgrade();
463     if (context && context->GetMinPlatformVersion() < COMPATIBLE_VERSION) {
464         // List Layout Screen Remaining Space
465         viewPort_ = GetLayoutParam().GetMaxSize();
466         SetLayoutSize(viewPort_);
467         if (NearEqual(viewPort_.Width(), Size::INFINITE_SIZE) || NearEqual(viewPort_.Height(), Size::INFINITE_SIZE)) {
468             LOGW("The main or cross size is INFINITE, wait for the determined value");
469             return;
470         }
471     } else {
472         // List determines its own layout size based on children.
473         if (GetLayoutParam().GetMaxSize().IsInfinite()) {
474             ExtendViewPort(); // Extend the view port for layout more items.
475         } else {
476             viewPort_ = GetLayoutParam().GetMaxSize();
477         }
478     }
479 
480     offsetBeforeLayout_ = GetMainOffset(currentOffset_);
481     LayoutChild();
482     CalculateMainScrollExtent();
483     ApplyGradientColor();
484 
485     if (IsReadyToJump()) {
486         if (!NearEqual(initialOffset_, effectOffset_)) {
487             JumpToPosition(initialOffset_);
488             effectOffset_ = initialOffset_;
489             LOGI("Effect initialOffset_:%{public}lf %{public}s", effectOffset_, currentOffset_.ToString().c_str());
490         }
491         if (NearZero(initialOffset_) && initialIndex_ > 0 && initialIndex_ != effectIndex_) {
492             JumpToIndex(initialIndex_);
493             effectIndex_ = initialIndex_;
494             LOGI("Effect initialIndex_:%{public}d %{public}s", effectIndex_, currentOffset_.ToString().c_str());
495         }
496     }
497 
498     if (scrollable_->Available()) {
499         ValidateOffset(SCROLL_FROM_NONE);
500     } else {
501         currentOffset_ = Offset::Zero();
502     }
503 
504     if (!context || context->GetMinPlatformVersion() >= COMPATIBLE_VERSION) {
505         if (GetLayoutParam().GetMaxSize().IsInfinite()) {
506             // If not set the main axis length: wrap content.
507             Rect rect;
508             for (const auto& child : GetChildren()) {
509                 rect.IsValid() ? rect.CombineRect(child->GetPaintRect()) : rect = child->GetPaintRect();
510             }
511             viewPort_ = rect.GetSize();
512         }
513         SetLayoutSize(viewPort_);
514     }
515 }
516 
ExtendViewPort()517 void RenderMultiChildScroll::ExtendViewPort()
518 {
519     if (GreatNotEqual(GetMainSize(GetLayoutSize()), GetMainSize(viewPort_))) {
520         if (axis_ == Axis::HORIZONTAL) {
521             viewPort_.SetWidth(GetLayoutSize().Width() * EXTENT_RATIO);
522         } else {
523             viewPort_.SetHeight(GetLayoutSize().Height() * EXTENT_RATIO);
524         }
525     } else {
526         if (axis_ == Axis::HORIZONTAL) {
527             viewPort_.SetWidth(viewPort_.Width() * EXTENT_RATIO);
528         } else {
529             viewPort_.SetHeight(viewPort_.Height() * EXTENT_RATIO);
530         }
531     }
532 }
533 
HandleCrashTop()534 bool RenderMultiChildScroll::HandleCrashTop()
535 {
536 #ifdef WEARABLE_PRODUCT
537     if (scrollVibrate_ && vibrator_) {
538         vibrator_->Vibrate(VIBRATOR_TYPE_WATCH_CROWN_STRENGTH3);
539     }
540     if (rotationVibrate_ && IsFromRotate() && vibrator_) {
541         vibrator_->Vibrate(VIBRATOR_TYPE_WATCH_CROWN_STRENGTH3);
542     }
543 #endif
544     return RenderScroll::HandleCrashTop();
545 }
HandleCrashBottom()546 bool RenderMultiChildScroll::HandleCrashBottom()
547 {
548 #ifdef WEARABLE_PRODUCT
549     if (scrollVibrate_ && vibrator_) {
550         vibrator_->Vibrate(VIBRATOR_TYPE_WATCH_CROWN_STRENGTH3);
551     }
552     if (rotationVibrate_ && IsFromRotate() && vibrator_) {
553         vibrator_->Vibrate(VIBRATOR_TYPE_WATCH_CROWN_STRENGTH3);
554     }
555 #endif
556     return RenderScroll::HandleCrashBottom();
557 }
558 
IsReadyToJump() const559 bool RenderMultiChildScroll::IsReadyToJump() const
560 {
561     bool ready = false;
562     for (const auto& child : GetChildren()) {
563         auto listBase = AceType::DynamicCast<RenderList>(child);
564         if (!listBase) {
565             continue;
566         }
567         int32_t maxCount = listBase->GetMaxCount();
568         if (listBase->IsPageReady() || (initialIndex_ != 0 && initialIndex_ < maxCount) ||
569             (!NearZero(initialOffset_) && LessNotEqual(initialOffset_, mainScrollExtent_))) {
570             ready = true;
571             break;
572         }
573     }
574     return ready;
575 }
576 
ApplyGradientColor()577 void RenderMultiChildScroll::ApplyGradientColor()
578 {
579     if (scrollable_ && !scrollable_->Available()) {
580         return;
581     }
582 
583     if (!gradientWidth_.IsValid()) {
584         return;
585     }
586 
587     auto box = AceType::DynamicCast<RenderBox>(GetParent().Upgrade());
588     if (!box) {
589         LOGE("parent is not box");
590         return;
591     }
592 
593     // workaround: set needLayout_ to true to make sure box will not mark flex to be layout
594     // and add box to dirty layout set manually at the end.
595     box->SetNeedLayout(true);
596 
597     Dimension widthPx = Dimension(viewPort_.Width());
598     Dimension heightPx = Dimension(viewPort_.Height());
599     Dimension gradientWidthPx = Dimension(NormalizeToPx(gradientWidth_));
600 
601     Dimension mainPx;
602     Gradient gradient = Gradient();
603     if (axis_ == Axis::HORIZONTAL) {
604         mainPx = widthPx;
605         gradient.SetDirection(GradientDirection::RIGHT);
606     } else {
607         mainPx = heightPx;
608         gradient.SetDirection(GradientDirection::BOTTOM);
609     }
610 
611     if (!IsAtTop()) {
612         GradientColor start;
613         start.SetColor(backgroundColor_);
614         start.SetDimension(Dimension(0.0));
615 
616         GradientColor end;
617         Color endColor = backgroundColor_;
618         endColor = endColor.ChangeAlpha(0);
619         end.SetColor(endColor);
620         end.SetDimension(gradientWidthPx);
621 
622         gradient.AddColor(start);
623         gradient.AddColor(end);
624     }
625 
626     if (!IsAtBottom()) {
627         GradientColor start;
628         Color startColor = backgroundColor_;
629         startColor = startColor.ChangeAlpha(0);
630         start.SetColor(startColor);
631         start.SetDimension(mainPx - gradientWidthPx);
632 
633         GradientColor end;
634         end.SetColor(backgroundColor_);
635         end.SetDimension(mainPx);
636 
637         gradient.AddColor(start);
638         gradient.AddColor(end);
639     }
640 
641     auto frontDecoration = box->GetFrontDecoration();
642     if (!frontDecoration) {
643         frontDecoration = AceType::MakeRefPtr<Decoration>();
644     }
645     frontDecoration->SetGradient(gradient);
646     box->SetFrontDecoration(frontDecoration);
647 
648     auto pipelineContext = context_.Upgrade();
649     if (pipelineContext) {
650         pipelineContext->AddDirtyLayoutNode(box);
651     }
652 }
653 
MoveItemToViewPort(double position,double size,double effectOffset)654 void RenderMultiChildScroll::MoveItemToViewPort(double position, double size, double effectOffset)
655 {
656     if (SystemProperties::GetDeviceType() != DeviceType::TV && SystemProperties::GetDeviceType() != DeviceType::PHONE) {
657         return;
658     }
659     double beginPosition = CalculateBeginPositionInViewPort(position, size, effectOffset);
660     if (beginPosition >= 0.0 && mainScrollExtent_ >= GetMainSize(viewPort_)) {
661         beginPosition = std::clamp(beginPosition, 0.0, mainScrollExtent_ - GetMainSize(viewPort_));
662     }
663     ScrollToPosition(beginPosition, SCROLL_FROM_FOCUS_JUMP, false);
664 }
665 
CalculateBeginPositionInViewPort(double position,double size,double effectOffset)666 double RenderMultiChildScroll::CalculateBeginPositionInViewPort(double position, double size, double effectOffset)
667 {
668     double gradientWidth = NormalizeToPx(gradientWidth_);
669     double viewPortSize = GetMainSize(viewPort_);
670     double offset = GetMainOffset(currentOffset_);
671     double viewMin = offset;
672     double viewMax = offset + viewPortSize - size;
673 
674     if (!IsAtTop()) {
675         viewMin = offset + gradientWidth;
676     }
677     if (!IsAtBottom()) {
678         viewMax = offset + viewPortSize - size - gradientWidth;
679     }
680 
681     if (GreatOrEqual(viewMin, viewMax)) {
682         return NONE_SCROLL_POSITION;
683     }
684 
685     if (position < viewMin) {
686         return std::max(position - gradientWidth - effectOffset, 0.0);
687     }
688     if (position > viewMax) {
689         return std::max(position - viewMax + offset + effectOffset, 0.0);
690     }
691     return NONE_SCROLL_POSITION;
692 }
693 
ScrollToEdge(ScrollEdgeType scrollEdgeType,bool smooth)694 void RenderMultiChildScroll::ScrollToEdge(ScrollEdgeType scrollEdgeType, bool smooth)
695 {
696     if (GetChildren().empty()) {
697         LOGE("no list in scroll");
698         return;
699     }
700     auto renderList = AceType::DynamicCast<RenderList>(GetChildren().front());
701     if (!renderList) {
702         LOGE("no list to jump");
703         return;
704     }
705 
706     double position = 0.0;
707     if (scrollEdgeType == ScrollEdgeType::SCROLL_TOP) {
708         position = renderList->CalculateItemPosition(0, ScrollType::SCROLL_TOP);
709     } else {
710         position = renderList->CalculateItemPosition(0, ScrollType::SCROLL_BOTTOM);
711     }
712     if (position < 0.0) {
713         LOGE("Get edge position failed.");
714         return;
715     }
716     CalculateMainScrollExtent();
717     LOGI("Scroll to position:%{public}lf %{public}d", position, smooth);
718     ScrollToPosition(position, SCROLL_FROM_FOCUS_JUMP, smooth);
719 }
720 
ScrollToPosition(double position,int32_t source,bool smooth)721 bool RenderMultiChildScroll::ScrollToPosition(double position, int32_t source, bool smooth)
722 {
723     if (NearEqual(position, NONE_SCROLL_POSITION)) {
724         return false;
725     }
726 
727     double distance = position - GetMainOffset(currentOffset_);
728     if (NearZero(distance)) {
729         return false;
730     }
731     position = std::max(position, 0.0);
732     if (smooth) {
733         ScrollBy(distance, distance, smooth);
734     } else {
735         JumpToPosition(std::max(position, 0.0), source);
736     }
737     return true;
738 }
739 
ScrollPage(bool reverse,bool smooth,const std::function<void ()> & onFinish)740 bool RenderMultiChildScroll::ScrollPage(bool reverse, bool smooth, const std::function<void()>& onFinish)
741 {
742     if (GetChildren().empty()) {
743         LOGE("no list in scroll");
744         return false;
745     }
746     auto renderList = AceType::DynamicCast<RenderList>(GetChildren().front());
747     if (!renderList) {
748         LOGE("no list to jump");
749         return false;
750     }
751     double position = 0.0;
752     if (reverse) {
753         position = renderList->CalculateItemPosition(0, ScrollType::SCROLL_PAGE_UP);
754     } else {
755         position = renderList->CalculateItemPosition(0, ScrollType::SCROLL_PAGE_DOWN);
756     }
757     if (position < 0.0) {
758         LOGE("Get page:%{public}d position failed.", reverse);
759         return false;
760     }
761     CalculateMainScrollExtent();
762     LOGI("Scroll to position:%{public}lf %{public}d", position, smooth);
763     return ScrollToPosition(position, SCROLL_FROM_FOCUS_JUMP, smooth);
764 }
765 
OnRotation(const RotationEvent & event)766 bool RenderMultiChildScroll::OnRotation(const RotationEvent& event)
767 {
768     if (positionController_ && !positionController_->IsScrollNeedRotation()) {
769         LOGE("OnRotation, current indexer is expand");
770         return false;
771     }
772     float rotateValue = event.value; // value of rotation, means pixels(vp)
773     HandleRotate(rotateValue, axis_ == Axis::VERTICAL);
774     return true;
775 }
776 
HandleRotate(double rotateValue,bool isVertical)777 void RenderMultiChildScroll::HandleRotate(double rotateValue, bool isVertical)
778 {
779     auto context = GetContext().Upgrade();
780     if (!context) {
781         LOGE("context is null");
782         return;
783     }
784     auto listBase = AceType::DynamicCast<RenderList>(GetFirstChild());
785     if (!listBase) {
786         LOGE("no rotatable list");
787         return;
788     }
789 
790     if (listBase->GetOnRotateCallback()) {
791         RotationEvent event = {rotateValue};
792         (listBase->GetOnRotateCallback())(event);
793     }
794 
795     double value = context->NormalizeToPx(Dimension(rotateValue, DimensionUnit::VP)) * (-1.0);
796     if (listBase->IsSupportScale()) {
797         value *= LIST_INCONTINUOUS_ROTATION_SENSITYVITY_NORMAL;
798     } else {
799         value *= LIST_CONTINUOUS_ROTATION_SENSITYVITY_NORMAL;
800     }
801     if (listBase->GetSupportItemCenter()) {
802         auto childItem = listBase->GetChildByIndex(listBase->GetCenterIndex());
803         auto centerItem = RenderListItem::GetRenderListItem(childItem);
804         if (centerItem) {
805             accumulatedRotationValue_ += value;
806             Size itemSize = centerItem->GetLayoutSize();
807             double threshold = 0.0;
808             if (isVertical) {
809                 threshold = itemSize.Height() * LIST_ITEMCENTER_ROTATION_THRESHOLD;
810             } else {
811                 threshold = itemSize.Width() * LIST_ITEMCENTER_ROTATION_THRESHOLD;
812             }
813             if (InRegion(-threshold, threshold, accumulatedRotationValue_)) {
814                 return;
815             }
816             value = accumulatedRotationValue_;
817             accumulatedRotationValue_ = 0.0;
818         }
819 
820         double destPosition = -GetMainOffset(currentOffset_) - value;
821         double fixPosition = GetFixPositionOnWatch(destPosition, -GetMainOffset(currentOffset_));
822         value -= fixPosition - destPosition;
823         AnimateTo(GetCurrentPosition() + value, ANIMATION_DURATION, Curves::FRICTION);
824     } else {
825         // Vertical or horizontal, different axis
826         Offset delta;
827         if (isVertical) {
828             delta.SetX(0.0);
829             delta.SetY(value);
830         } else {
831             delta.SetX(value);
832             delta.SetY(0.0);
833         }
834         UpdateOffset(delta, SCROLL_FROM_ROTATE);
835     }
836 }
837 
838 // notify start position in global main axis
NotifyDragStart(double startPosition)839 void RenderMultiChildScroll::NotifyDragStart(double startPosition)
840 {
841     for (const auto& child : GetChildren()) {
842         auto listBase = AceType::DynamicCast<RenderList>(child);
843         if (!listBase) {
844             continue;
845         }
846         listBase->NotifyDragStart(startPosition);
847     }
848 }
849 
850 // notify drag offset in global main axis
NotifyDragUpdate(double dragOffset,int32_t source)851 void RenderMultiChildScroll::NotifyDragUpdate(double dragOffset, int32_t source)
852 {
853     for (const auto& child : GetChildren()) {
854         auto listBase = AceType::DynamicCast<RenderList>(child);
855         if (!listBase) {
856             continue;
857         }
858         listBase->NotifyDragUpdate(dragOffset);
859         // switch chain control node in flush chain animation
860         double delta = listBase->FlushChainAnimation();
861         // fix currentOffset_ after switch control node.
862         if (axis_ == Axis::HORIZONTAL) {
863             currentOffset_ += Offset(-delta, 0.0);
864         } else {
865             currentOffset_ += Offset(0.0, -delta);
866         }
867     }
868 }
869 
ProcessScrollOverCallback(double velocity)870 void RenderMultiChildScroll::ProcessScrollOverCallback(double velocity)
871 {
872     for (const auto& child : GetChildren()) {
873         auto listBase = AceType::DynamicCast<RenderList>(child);
874         if (!listBase) {
875             continue;
876         }
877         // switch chain control node when notify scroll over
878         listBase->NotifyScrollOver(velocity, IsOutOfTopBoundary(), IsOutOfBottomBoundary());
879         double delta = listBase->FlushChainAnimation();
880         // fix currentOffset_ after switch control node.
881         if (axis_ == Axis::HORIZONTAL) {
882             currentOffset_ += Offset(-delta, 0.0);
883         } else {
884             currentOffset_ += Offset(0.0, -delta);
885         }
886     }
887 }
888 
GetMainScrollExtent() const889 double RenderMultiChildScroll::GetMainScrollExtent() const
890 {
891     const double mainScrollExtent = RenderScroll::GetMainScrollExtent();
892     auto child = GetFirstChild();
893     auto listBase = AceType::DynamicCast<RenderList>(child);
894     if (listBase) {
895         return mainScrollExtent + listBase->GetTailAnimationValue() + listBase->GetHeadAnimationValue();
896     } else {
897         return mainScrollExtent;
898     }
899 }
900 
GetFixPositionOnWatch(double destination,double current)901 double RenderMultiChildScroll::GetFixPositionOnWatch(double destination, double current)
902 {
903     auto listBase = AceType::DynamicCast<RenderList>(GetLastChild());
904     if (!listBase) {
905         return destination;
906     }
907 
908     // find centerIndex
909     int32_t centerIndex = -1;
910     double itemSize = 0.0;
911     double itemPosition = 0.0;
912     double listPosition = GetMainOffset(currentOffset_) - destination + current;
913     if (GreatNotEqual(destination, current)) {
914         // scroll to top direction
915         centerIndex = listBase->EstimateIndexByPosition(listPosition + GetMainSize(viewPort_) * HALF_ITEM_SIZE);
916         itemPosition = listBase->GetItemPosition(centerIndex);
917         itemSize = listBase->GetItemPosition(centerIndex + 1) - itemPosition;
918     } else {
919         // scroll to bottom direction
920         listBase->CalculateItemPosition(listPosition);
921         double centerPosition = listPosition + GetMainSize(viewPort_) * HALF_ITEM_SIZE;
922         for (const auto &itemPair : listBase->GetItems()) {
923             int32_t index = itemPair.first;
924             auto item = RenderListItem::GetRenderListItem(itemPair.second);
925             if (!item) {
926                 LOGW("get render list item is null");
927                 continue;
928             }
929             double start = listBase->GetItemPosition(index);
930             double end = start + listBase->GetMainSize(item->GetLayoutSize());
931 
932             if (start < centerPosition && end > centerPosition) {
933                 centerIndex = index;
934                 itemSize = GetMainSize(item->GetLayoutSize());
935                 itemPosition = item->GetPositionInList();
936                 break;
937             }
938         }
939     }
940     if (centerIndex == -1) {
941         LOGW("invalid center index");
942         return destination;
943     }
944 
945     // calculate destination position after fix
946     double center = listPosition + GetMainSize(viewPort_) * HALF_ITEM_SIZE;
947     double itemCenterPosition = itemPosition + itemSize * HALF_ITEM_SIZE;
948     return destination + center - itemCenterPosition;
949 }
950 
IsOutOfBottomBoundary()951 bool RenderMultiChildScroll::IsOutOfBottomBoundary()
952 {
953     double headOffset = GetMainOffset(currentOffset_);
954     double tailOffset = mainScrollExtent_;
955     auto child = GetLastChild();
956     auto listBase = AceType::DynamicCast<RenderList>(child);
957     if (listBase) {
958         tailOffset = mainScrollExtent_ + listBase->GetTailAnimationValue();
959     }
960     if (IsRowReverse() || IsColReverse()) {
961         return headOffset <= (GetMainSize(viewPort_) - tailOffset) && ReachMaxCount();
962     } else {
963         return headOffset >= (tailOffset - GetMainSize(viewPort_)) && ReachMaxCount();
964     }
965 }
966 
IsOutOfTopBoundary()967 bool RenderMultiChildScroll::IsOutOfTopBoundary()
968 {
969     double headOffset = GetMainOffset(currentOffset_);
970     auto child = GetFirstChild();
971     auto listBase = AceType::DynamicCast<RenderList>(child);
972     if (listBase) {
973         headOffset = GetMainOffset(currentOffset_) - listBase->GetHeadAnimationValue();
974     }
975     if (IsRowReverse() || IsColReverse()) {
976         return headOffset >= 0.0;
977     } else {
978         return headOffset <= 0.0;
979     }
980 }
981 
982 } // namespace OHOS::Ace
983