1/* 2 * Copyright (c) 2020-2021 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 "components/ui_abstract_scroll.h" 17 18#include "securec.h" 19 20#include "animator/interpolation.h" 21#include "common/screen.h" 22#include "components/ui_abstract_scroll_bar.h" 23#include "components/ui_arc_scroll_bar.h" 24#include "components/ui_box_scroll_bar.h" 25#if DEFAULT_ANIMATION 26#include "graphic_timer.h" 27#endif 28 29namespace OHOS { 30#if DEFAULT_ANIMATION 31class BarEaseInOutAnimator final : public AnimatorCallback { 32public: 33 BarEaseInOutAnimator() = delete; 34 BarEaseInOutAnimator(const BarEaseInOutAnimator&) = delete; 35 BarEaseInOutAnimator& operator=(const BarEaseInOutAnimator&) = delete; 36 BarEaseInOutAnimator(BarEaseInOutAnimator&&) = delete; 37 BarEaseInOutAnimator& operator=(BarEaseInOutAnimator&&) = delete; 38 39 explicit BarEaseInOutAnimator(UIAbstractScroll& scrollView) 40 : scrollView_(scrollView), 41 timer_(APPEAR_PERIOD, TimerCb, this), 42 animator_(this, nullptr, ANIMATOR_DURATION, false) 43 { 44 } 45 46 ~BarEaseInOutAnimator() 47 { 48 timer_.Stop(); 49 animator_.Stop(); 50 } 51 52 void RefreshBar() 53 { 54 if (animator_.GetState() == Animator::START) { 55 if (!isEaseIn_) { 56 animator_.SetRunTime(ANIMATOR_DURATION - animator_.GetRunTime()); 57 } 58 } else if (scrollView_.yScrollBar_->GetOpacity() == OPA_TRANSPARENT) { 59 animator_.Start(); 60 } else { 61 timer_.Start(); // updates the start time of timer, ensuring that timer is triggered two seconds after the 62 // last operation 63 } 64 isEaseIn_ = true; 65 } 66 67private: 68 void Callback(UIView* view) override 69 { 70 uint8_t opa = OPA_OPAQUE * animator_.GetRunTime() / ANIMATOR_DURATION; 71 if (!isEaseIn_) { 72 opa = OPA_OPAQUE - opa; 73 } 74 float bezielY = opa; 75 bezielY = 76 Interpolation::GetBezierY(bezielY / OPA_OPAQUE, BEZIER_CONTROL_POINT_X_1, 0, BEZIER_CONTROL_POINT_X_2, 1); 77 opa = static_cast<uint8_t>(bezielY * opa); 78 if (scrollView_.yScrollBarVisible_) { 79 scrollView_.yScrollBar_->SetOpacity(opa); 80 } 81 if (scrollView_.xScrollBarVisible_) { 82 scrollView_.xScrollBar_->SetOpacity(opa); 83 } 84 scrollView_.Invalidate(); 85 } 86 87 void OnStop(UIView& view) override 88 { 89 if (isEaseIn_) { 90 if (scrollView_.yScrollBarVisible_) { 91 scrollView_.yScrollBar_->SetOpacity(OPA_OPAQUE); 92 } 93 if (Screen::GetInstance().GetScreenShape() == ScreenShape::RECTANGLE && scrollView_.xScrollBarVisible_) { 94 scrollView_.xScrollBar_->SetOpacity(OPA_OPAQUE); 95 } 96 timer_.Start(); // The timer is triggered when animation stops. 97 } else { 98 if (scrollView_.yScrollBarVisible_) { 99 scrollView_.yScrollBar_->SetOpacity(OPA_TRANSPARENT); 100 } 101 if (scrollView_.xScrollBarVisible_) { 102 scrollView_.xScrollBar_->SetOpacity(OPA_TRANSPARENT); 103 } 104 } 105 scrollView_.Invalidate(); 106 } 107 108 static void TimerCb(void* arg) 109 { 110 BarEaseInOutAnimator* barAnimator = reinterpret_cast<BarEaseInOutAnimator*>(arg); 111 barAnimator->isEaseIn_ = false; 112 barAnimator->animator_.Start(); 113 } 114 static constexpr uint16_t ANIMATOR_DURATION = 250; 115 static constexpr uint16_t APPEAR_PERIOD = 2000; 116 static constexpr float BEZIER_CONTROL_POINT_X_1 = 0.33f; 117 static constexpr float BEZIER_CONTROL_POINT_X_2 = 0.67f; 118 UIAbstractScroll& scrollView_; 119 GraphicTimer timer_; 120 Animator animator_; 121 bool isEaseIn_ = true; 122}; 123#endif 124 125UIAbstractScroll::UIAbstractScroll() 126 : direction_(VERTICAL), 127 deltaIndex_(0), 128 rotateIndex_(0), 129 reserve_(0), 130 easingFunc_(EasingEquation::CubicEaseOut), 131 scrollAnimator_(&animatorCallback_, this, 0, true), 132 scrollBarSide_(SCROLL_BAR_RIGHT_SIDE), 133 scrollBarCenter_({0, 0}), 134 scrollBarCenterSetFlag_(false) 135{ 136#if defined(ENABLE_FOCUS_MANAGER) && ENABLE_FOCUS_MANAGER 137 focusable_ = true; 138#endif 139#if defined(ENABLE_ROTATE_INPUT) && ENABLE_ROTATE_INPUT 140 rotateFactor_ = DEFAULT_SCROLL_VIEW_ROTATE_FACTOR; 141 rotateThrowthreshold_ = ABSTRACT_ROTATE_THROW_THRESHOLD; 142 rotateAccCoefficient_ = ABSTRACT_ROTATE_DISTANCE_COEFF; 143 isRotating_ = false; 144#endif 145 isViewGroup_ = true; 146 touchable_ = true; 147 draggable_ = true; 148 dragParentInstead_ = false; 149} 150 151UIAbstractScroll::~UIAbstractScroll() 152{ 153#if defined(DEFAULT_ANIMATION) && DEFAULT_ANIMATION 154 if (barEaseInOutAnimator_ != nullptr) { 155 delete barEaseInOutAnimator_; 156 barEaseInOutAnimator_ = nullptr; 157 } 158#endif 159 if (xScrollBar_ != nullptr) { 160 delete xScrollBar_; 161 xScrollBar_ = nullptr; 162 } 163 if (yScrollBar_ != nullptr) { 164 delete yScrollBar_; 165 yScrollBar_ = nullptr; 166 } 167} 168 169void UIAbstractScroll::MoveChildByOffset(int16_t offsetX, int16_t offsetY) 170{ 171 if ((offsetX == 0) && (offsetY == 0)) { 172 return; 173 } 174 UIView* view = GetChildrenHead(); 175 while (view != nullptr) { 176 int16_t x = view->GetX() + offsetX; 177 int16_t y = view->GetY() + offsetY; 178 view->SetPosition(x, y); 179 view = view->GetNextSibling(); 180 } 181 Invalidate(); 182} 183 184int16_t UIAbstractScroll::GetMaxDelta() const 185{ 186 int16_t result = 0; 187 for (int16_t i = 0; i < MAX_DELTA_SIZE; i++) { 188 if (result < MATH_ABS(lastDelta_[i])) { 189 result = MATH_ABS(lastDelta_[i]); 190 } 191 } 192 return result; 193} 194 195int16_t UIAbstractScroll::GetMaxRotate() const 196{ 197 int16_t result = 0; 198 for (int16_t i = 0; i < MAX_DELTA_SIZE; i++) { 199 if (MATH_ABS(result) < MATH_ABS(lastRotate_[i])) { 200 result = lastRotate_[i]; 201 } 202 } 203 return result; 204} 205 206void UIAbstractScroll::InitDelta() 207{ 208 if (memset_s(lastDelta_, sizeof(lastDelta_), 0, sizeof(lastDelta_)) != EOK) { 209 GRAPHIC_LOGE("memset_s error"); 210 } 211} 212 213void UIAbstractScroll::InitRotate() 214{ 215 if (memset_s(lastRotate_, sizeof(lastRotate_), 0, sizeof(lastRotate_)) != EOK) { 216 GRAPHIC_LOGE("memset_s error"); 217 } 218} 219 220void UIAbstractScroll::StopAnimator() 221{ 222 scrollAnimator_.Stop(); 223 animatorCallback_.ResetCallback(); 224 isDragging_ = false; 225} 226 227bool UIAbstractScroll::DragThrowAnimator(Point currentPos, Point lastPos, uint8_t dragDirection, bool dragBack) 228{ 229 if (!throwDrag_ && (reboundSize_ == 0)) { 230 return false; 231 } 232 int16_t dragDistanceX = 0; 233 int16_t dragDistanceY = 0; 234 if (throwDrag_) { 235 CalculateDragDistance(currentPos, lastPos, dragDirection, dragDistanceX, dragDistanceY); 236 } 237 if (reboundSize_ != 0) { 238 CalculateReboundDistance(dragDistanceX, dragDistanceY); 239 } 240 241 if (!dragBack) { 242 FixDistance(dragDistanceX, dragDistanceY); 243 } 244 245 StartAnimator(dragDistanceX, dragDistanceY); 246 return true; 247} 248 249void UIAbstractScroll::StartAnimator(int16_t dragDistanceX, int16_t dragDistanceY) 250{ 251 int16_t dragTimes = MATH_MAX(MATH_ABS(dragDistanceX), MATH_ABS(dragDistanceY)) / DRAG_TIMES_COEFFICIENT; 252 if (dragTimes < MIN_DRAG_TIMES) { 253 dragTimes = MIN_DRAG_TIMES; 254 } 255 animatorCallback_.ResetCallback(); 256 animatorCallback_.SetDragStartValue(0, 0); 257 animatorCallback_.SetDragEndValue(dragDistanceX, dragDistanceY); 258 animatorCallback_.SetDragTimes(dragTimes * DRAG_ACC_FACTOR / GetDragACCLevel()); 259 scrollAnimator_.Start(); 260} 261 262void UIAbstractScroll::CalculateDragDistance(Point currentPos, 263 Point lastPos, 264 uint8_t dragDirection, 265 int16_t& dragDistanceX, 266 int16_t& dragDistanceY) 267{ 268 if ((direction_ == VERTICAL) || (direction_ == HORIZONTAL_AND_VERTICAL)) { 269 dragDistanceY = currentPos.y - lastPos.y; 270 if (isRotating_) { 271 dragDistanceY *= rotateAccCoefficient_; 272 } else { 273 dragDistanceY *= DRAG_DISTANCE_COEFFICIENT; 274 if (dragDistanceY > 0 || (dragDistanceY == 0 && dragDirection == DragEvent::DIRECTION_TOP_TO_BOTTOM)) { 275 dragDistanceY += GetMaxDelta() * GetSwipeACCLevel() / DRAG_ACC_FACTOR; 276 } else if (dragDistanceY < 0 || 277 (dragDistanceY == 0 && dragDirection == DragEvent::DIRECTION_BOTTOM_TO_TOP)) { 278 dragDistanceY -= GetMaxDelta() * GetSwipeACCLevel() / DRAG_ACC_FACTOR; 279 } 280 } 281 } 282 283 if ((direction_ == HORIZONTAL) || (direction_ == HORIZONTAL_AND_VERTICAL)) { 284 dragDistanceX = currentPos.x - lastPos.x; 285 if (isRotating_) { 286 dragDistanceX *= rotateAccCoefficient_; 287 } else { 288 dragDistanceX *= DRAG_DISTANCE_COEFFICIENT; 289 if (dragDistanceX > 0 || (dragDistanceX == 0 && dragDirection == DragEvent::DIRECTION_LEFT_TO_RIGHT)) { 290 dragDistanceX += GetMaxDelta() * GetSwipeACCLevel() / DRAG_ACC_FACTOR; 291 } else if (dragDistanceX < 0 || 292 (dragDistanceX == 0 && dragDirection == DragEvent::DIRECTION_RIGHT_TO_LEFT)) { 293 dragDistanceX -= GetMaxDelta() * GetSwipeACCLevel() / DRAG_ACC_FACTOR; 294 } 295 } 296 } 297 298 if (maxScrollDistance_ != 0) { 299 if (MATH_ABS(dragDistanceY) > maxScrollDistance_) { 300 int16_t calculatedValue = (dragDistanceY > 0) ? 1 : -1; 301 dragDistanceY = calculatedValue * maxScrollDistance_; 302 } 303 if (MATH_ABS(dragDistanceX) > maxScrollDistance_) { 304 int16_t calculatedValue = (dragDistanceX > 0) ? 1 : -1; 305 dragDistanceX = calculatedValue * maxScrollDistance_; 306 } 307 } 308} 309 310void UIAbstractScroll::ListAnimatorCallback::Callback(UIView* view) 311{ 312 if (view == nullptr) { 313 return; 314 } 315 316 UIAbstractScroll* scrollView = static_cast<UIAbstractScroll*>(view); 317 scrollView->isDragging_ = true; 318 curtTime_++; 319 if (curtTime_ <= dragTimes_) { 320 bool needStopX = false; 321 bool needStopY = false; 322 if (startValueY_ != endValueY_) { 323 int16_t actY = scrollView->easingFunc_(startValueY_, endValueY_, curtTime_, dragTimes_); 324 if (!scrollView->DragYInner(actY - previousValueY_)) { 325 needStopY = true; 326 } 327 previousValueY_ = actY; 328 } else { 329 needStopY = true; 330 } 331 if (startValueX_ != endValueX_) { 332 int16_t actX = scrollView->easingFunc_(startValueX_, endValueX_, curtTime_, dragTimes_); 333 if (!scrollView->DragXInner(actX - previousValueX_)) { 334 needStopX = true; 335 } 336 previousValueX_ = actX; 337 } else { 338 needStopX = true; 339 } 340 if (needStopX && needStopY) { 341 scrollView->StopAnimator(); 342 } 343 } else { 344 scrollView->StopAnimator(); 345 } 346} 347 348#if ENABLE_ROTATE_INPUT 349bool UIAbstractScroll::OnRotateStartEvent(const RotateEvent& event) 350{ 351 isRotating_ = true; 352 if (scrollAnimator_.GetState() != Animator::STOP) { 353 UIAbstractScroll::StopAnimator(); 354 } 355 return UIView::OnRotateStartEvent(event); 356} 357 358bool UIAbstractScroll::OnRotateEvent(const RotateEvent& event) 359{ 360 int16_t rotateLen = static_cast<int16_t>(event.GetRotate() * rotateFactor_); 361 RefreshRotate(rotateLen); 362 if (direction_ == HORIZONTAL) { 363 DragXInner(rotateLen); 364 } else { 365 DragYInner(rotateLen); 366 } 367 return UIView::OnRotateEvent(event); 368} 369 370bool UIAbstractScroll::OnRotateEndEvent(const RotateEvent& event) 371{ 372 InitDelta(); 373 374 uint8_t dir; 375 int16_t lastRotateLen = GetMaxRotate(); 376 if (direction_ == HORIZONTAL) { 377 dir = (lastRotateLen >= 0) ? DragEvent::DIRECTION_LEFT_TO_RIGHT : DragEvent::DIRECTION_RIGHT_TO_LEFT; 378 } else { 379 dir = (lastRotateLen >= 0) ? DragEvent::DIRECTION_TOP_TO_BOTTOM : DragEvent::DIRECTION_BOTTOM_TO_TOP; 380 } 381 bool triggerAnimator = (MATH_ABS(lastRotateLen) >= rotateThrowthreshold_); 382 if (throwDrag_ && triggerAnimator) { 383 Point current; 384 if (direction_ == HORIZONTAL) { 385 current = {lastRotateLen, 0}; 386 } else { 387 current = {0, lastRotateLen}; 388 } 389 DragThrowAnimator(current, {0, 0}, dir, dragBack_); 390 } else { 391 DragThrowAnimator({0, 0}, {0, 0}, dir, dragBack_); 392 } 393 isRotating_ = false; 394 InitRotate(); 395 return UIView::OnRotateEndEvent(event); 396} 397#endif 398 399void UIAbstractScroll::SetXScrollBarVisible(bool visible) 400{ 401 if (Screen::GetInstance().GetScreenShape() == ScreenShape::CIRCLE) { 402 return; 403 } else if (visible && xScrollBar_ == nullptr) { 404 xScrollBar_ = new UIBoxScrollBar(); 405 } 406 xScrollBarVisible_ = visible; 407#if DEFAULT_ANIMATION 408 if (xScrollBarVisible_ && barEaseInOutAnimator_ == nullptr) { 409 barEaseInOutAnimator_ = new BarEaseInOutAnimator(*this); 410 } 411#endif 412} 413 414void UIAbstractScroll::SetYScrollBarVisible(bool visible) 415{ 416 yScrollBarVisible_ = visible; 417 if (yScrollBarVisible_ && yScrollBar_ == nullptr) { 418 if (Screen::GetInstance().GetScreenShape() == ScreenShape::CIRCLE) { 419 yScrollBar_ = new UIArcScrollBar(); 420 } else { 421 yScrollBar_ = new UIBoxScrollBar(); 422 } 423 } 424#if DEFAULT_ANIMATION 425 if (yScrollBarVisible_ && barEaseInOutAnimator_ == nullptr) { 426 barEaseInOutAnimator_ = new BarEaseInOutAnimator(*this); 427 } 428#endif 429} 430 431void UIAbstractScroll::OnPostDraw(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea) 432{ 433 Rect scrollRect = GetRect(); 434 uint8_t opa = GetMixOpaScale(); 435 if (Screen::GetInstance().GetScreenShape() == ScreenShape::RECTANGLE) { 436 if (yScrollBarVisible_) { 437 if (scrollBarSide_ == SCROLL_BAR_RIGHT_SIDE) { 438 yScrollBar_->SetPosition(scrollRect.GetRight() - SCROLL_BAR_WIDTH + 1, scrollRect.GetTop(), 439 SCROLL_BAR_WIDTH, scrollRect.GetHeight()); 440 } else { 441 yScrollBar_->SetPosition(scrollRect.GetLeft(), scrollRect.GetTop(), SCROLL_BAR_WIDTH, 442 scrollRect.GetHeight()); 443 } 444 yScrollBar_->OnDraw(gfxDstBuffer, invalidatedArea, opa); 445 } 446 if (xScrollBarVisible_) { 447 if (scrollBarSide_ == SCROLL_BAR_RIGHT_SIDE) { 448 xScrollBar_->SetPosition(scrollRect.GetLeft(), scrollRect.GetBottom() - SCROLL_BAR_WIDTH + 1, 449 scrollRect.GetWidth() - SCROLL_BAR_WIDTH, SCROLL_BAR_WIDTH); 450 } else { 451 xScrollBar_->SetPosition(scrollRect.GetLeft() + SCROLL_BAR_WIDTH, 452 scrollRect.GetBottom() - SCROLL_BAR_WIDTH + 1, 453 scrollRect.GetWidth() - SCROLL_BAR_WIDTH, SCROLL_BAR_WIDTH); 454 } 455 xScrollBar_->OnDraw(gfxDstBuffer, invalidatedArea, opa); 456 } 457 } else { 458 if (yScrollBarVisible_) { 459 yScrollBar_->SetScrollBarSide(scrollBarSide_); 460 int16_t x; 461 int16_t y; 462 if (scrollBarCenterSetFlag_) { 463 x = scrollRect.GetX() + scrollBarCenter_.x; 464 y = scrollRect.GetY() + scrollBarCenter_.y; 465 } else { 466 x = scrollRect.GetX() + (GetWidth() / 2); // 2: half 467 y = scrollRect.GetY() + (GetHeight() / 2); // 2: half 468 } 469 yScrollBar_->SetPosition(x, y, SCROLL_BAR_WIDTH, GetWidth() / 2); // 2: half 470 yScrollBar_->OnDraw(gfxDstBuffer, invalidatedArea, opa); 471 } 472 } 473 UIView::OnPostDraw(gfxDstBuffer, invalidatedArea); 474} 475 476void UIAbstractScroll::RefreshAnimator() 477{ 478#if DEFAULT_ANIMATION 479 barEaseInOutAnimator_->RefreshBar(); 480#endif 481} 482} // namespace OHOS 483