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