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