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