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