1 /*
2 * Copyright (c) 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 "core/animation/card_transition_controller.h"
17
18 #include "core/animation/curve_animation.h"
19 #include "core/components/list/list_item_element.h"
20 #include "core/components/list/render_list.h"
21 #include "core/components/page_transition/page_transition_element.h"
22 #include "core/components/transform/transform_element.h"
23
24 namespace OHOS::Ace {
25 namespace {
26
27 constexpr float OPACITY_CHANGE_END = 0.286f;
28 constexpr float OPACITY_CHANGE_START = 0.571f;
29
30 } // namespace
31
CardTransitionController(const WeakPtr<PipelineContext> & context)32 CardTransitionController::CardTransitionController(const WeakPtr<PipelineContext>& context) : context_(context)
33 {
34 controller_ = CREATE_ANIMATOR(context);
35 };
36
RegisterTransitionListener()37 void CardTransitionController::RegisterTransitionListener()
38 {
39 auto pipelineContext = context_.Upgrade();
40 if (!pipelineContext) {
41 LOGE("Register Transition listener to stage failed. pipeline context is null.");
42 return;
43 }
44 auto weak = AceType::WeakClaim(this);
45 pipelineContext->AddPageTransitionListener(
46 [weak](const TransitionEvent& event, const WeakPtr<PageElement>& in, const WeakPtr<PageElement>& out) {
47 if ((event != TransitionEvent::POP_START) && (event != TransitionEvent::PUSH_START)) {
48 return;
49 }
50 auto controller = weak.Upgrade();
51 if (!controller) {
52 LOGE("handle event: %{public}d failed. controller is null.", event);
53 return;
54 }
55 if (event == TransitionEvent::PUSH_START) {
56 controller->pageDest_ = in;
57 controller->pageSrc_ = out;
58 } else {
59 controller->pageDest_ = out;
60 controller->pageSrc_ = in;
61 }
62 controller->event_ = event;
63 controller->PrepareTransition();
64 });
65 }
66
PrepareTransition()67 void CardTransitionController::PrepareTransition()
68 {
69 // finish previous transition
70 controller_->Finish();
71 // clear controller
72 controller_->ClearAllListeners();
73 controller_->ClearInterpolators();
74 controller_->ClearPrepareListeners();
75 auto pipelineContext = context_.Upgrade();
76 if (!pipelineContext) {
77 LOGE("prepare card transition failed. pipeline is null.");
78 return;
79 }
80 auto pageDest = (event_ == TransitionEvent::PUSH_START) ? pageDest_.Upgrade() : pageSrc_.Upgrade();
81 if (!pageDest || pageDest->GetCardComposeId().empty()) {
82 return;
83 }
84 auto composeId = pageDest->GetCardComposeId();
85 auto cardComposeElement = pipelineContext->GetComposedElementById(composeId);
86 if (!cardComposeElement) {
87 LOGE("prepare card transition failed. cardComposeElement is null.");
88 return;
89 }
90 auto cardRender = cardComposeElement->GetRenderNode();
91 if (!cardRender) {
92 LOGE("prepare card transition failed. cardRender is null.");
93 return;
94 }
95 auto rrect = GetCardRect(composeId);
96 CreateCardAnimation(cardComposeElement, cardRender->GetGlobalOffset(), rrect);
97 CreateCardListAnimation(cardRender);
98 CreateExternalAnimation(cardRender->GetLayoutSize().Height(), cardRender->GetGlobalOffset().GetY());
99 }
100
CreateCardAnimation(const RefPtr<Element> & cardComposeElement,const Offset & globalOffset,const RRect & rrect)101 void CardTransitionController::CreateCardAnimation(const RefPtr<Element>& cardComposeElement,
102 const Offset& globalOffset, const RRect& rrect)
103 {
104 auto listItemElement = cardComposeElement->GetFirstChild();
105 if (!listItemElement) {
106 LOGE("create card animation failed. listItemElement is null.");
107 return;
108 }
109 auto displayElement = AceType::DynamicCast<DisplayElement>(listItemElement->GetFirstChild());
110 if (!displayElement) {
111 LOGE("create card animation failed. displayElement is null.");
112 return;
113 }
114 auto displayRender = displayElement->GetRenderNode();
115 if (!displayRender) {
116 LOGE("create card animation failed. displayRender is null.");
117 return;
118 }
119 auto transformElement = GetCardTransformElement(cardComposeElement);
120 if (!transformElement) {
121 LOGE("create card animation failed. transformElement is null.");
122 return;
123 }
124 auto transformRender = AceType::DynamicCast<RenderTransform>(transformElement->GetRenderNode());
125 if (!transformRender) {
126 LOGE("create card animation failed. transformRender is null.");
127 return;
128 }
129 auto weakTransform = AceType::WeakClaim(AceType::RawPtr(transformRender));
130 CreateCardScaleAnimation(weakTransform, rrect);
131 CreateCardTranslateAnimation(weakTransform, globalOffset, rrect.GetRect().GetOffset() - globalOffset);
132 CreateCardOpacityAnimation(displayRender);
133 AddPrepareListener(weakTransform);
134 AddStopListener(weakTransform);
135 }
136
CreateCardTranslateAnimation(const WeakPtr<RenderTransform> & weakTransform,const Offset & globalOffset,const Offset & marginOffset)137 void CardTransitionController::CreateCardTranslateAnimation(const WeakPtr<RenderTransform>& weakTransform,
138 const Offset& globalOffset, const Offset& marginOffset)
139 {
140 auto pipelineContext = context_.Upgrade();
141 if (!pipelineContext) {
142 LOGE("create card translate animation failed. pipeline is null.");
143 return;
144 }
145 Offset startOffset = (event_ == TransitionEvent::PUSH_START) ? Offset() : Offset() - (globalOffset + marginOffset);
146 Offset endOffset = (event_ == TransitionEvent::PUSH_START) ? Offset() - (globalOffset + marginOffset) : Offset();
147 auto offsetAnimation = AceType::MakeRefPtr<CurveAnimation<Offset>>(startOffset, endOffset, Curves::FRICTION);
148 offsetAnimation->AddListener([weakTransform](const Offset& offset) {
149 auto transform = weakTransform.Upgrade();
150 if (transform) {
151 transform->Translate(Dimension(offset.GetX()), Dimension(offset.GetY()));
152 }
153 });
154 controller_->AddInterpolator(offsetAnimation);
155 }
156
CreateCardScaleAnimation(const WeakPtr<RenderTransform> & weakTransform,const RRect & rrect)157 void CardTransitionController::CreateCardScaleAnimation(const WeakPtr<RenderTransform>& weakTransform,
158 const RRect& rrect)
159 {
160 auto pipelineContext = context_.Upgrade();
161 if (!pipelineContext) {
162 LOGE("create card scale animation failed. pipeline is null.");
163 return;
164 }
165 auto transform = weakTransform.Upgrade();
166 if (!transform) {
167 LOGE("create card scale animation failed. transform is null.");
168 return;
169 }
170 auto width = rrect.Width();
171 if (NearEqual(width, 0.0)) {
172 LOGE("create card animation failed. width is zero.");
173 return;
174 }
175 transform->SetDisableClickEffect(true);
176 transform->ResetTransform();
177 transform->SetTransformOrigin(Dimension(), Dimension());
178 transform->MarkNeedUpdateOrigin();
179
180 double startScale = (event_ == TransitionEvent::PUSH_START) ? 1.0 : pipelineContext->GetRootWidth() / width;
181 double endScale = (event_ == TransitionEvent::PUSH_START) ? pipelineContext->GetRootWidth() / width : 1.0;
182 auto scaleAnimation = AceType::MakeRefPtr<CurveAnimation<double>>(startScale, endScale, Curves::FRICTION);
183 scaleAnimation->AddListener([weakTransform](double value) {
184 auto transform = weakTransform.Upgrade();
185 if (transform) {
186 transform->Scale(value);
187 }
188 });
189 controller_->AddInterpolator(scaleAnimation);
190 }
191
CreateCardOpacityAnimation(RefPtr<RenderNode> & displayRender)192 void CardTransitionController::CreateCardOpacityAnimation(RefPtr<RenderNode>& displayRender)
193 {
194 auto opacityAnimation = AceType::MakeRefPtr<KeyframeAnimation<uint8_t>>();
195 if (event_ == TransitionEvent::PUSH_START) {
196 auto opacityKeyframe1 = AceType::MakeRefPtr<Keyframe<uint8_t>>(0.0f, 255);
197 auto opacityKeyframe2 = AceType::MakeRefPtr<Keyframe<uint8_t>>(OPACITY_CHANGE_END, 0);
198 opacityKeyframe2->SetCurve(Curves::SHARP);
199 auto opacityKeyframe3 = AceType::MakeRefPtr<Keyframe<uint8_t>>(1.0f, 0);
200 opacityAnimation->AddKeyframe(opacityKeyframe1);
201 opacityAnimation->AddKeyframe(opacityKeyframe2);
202 opacityAnimation->AddKeyframe(opacityKeyframe3);
203 } else {
204 auto opacityKeyframe1 = AceType::MakeRefPtr<Keyframe<uint8_t>>(0.0f, 0);
205 auto opacityKeyframe2 = AceType::MakeRefPtr<Keyframe<uint8_t>>(OPACITY_CHANGE_START, 0);
206 auto opacityKeyframe3 = AceType::MakeRefPtr<Keyframe<uint8_t>>(1.0f, 255);
207 opacityKeyframe3->SetCurve(Curves::SHARP);
208 opacityAnimation->AddKeyframe(opacityKeyframe1);
209 opacityAnimation->AddKeyframe(opacityKeyframe2);
210 opacityAnimation->AddKeyframe(opacityKeyframe3);
211 }
212 opacityAnimation->AddListener([displayWeak = AceType::WeakClaim(AceType::RawPtr(displayRender))](uint8_t value) {
213 auto display = displayWeak.Upgrade();
214 if (display) {
215 display->UpdateOpacity(value);
216 }
217 });
218 controller_->AddInterpolator(opacityAnimation);
219 }
220
CreateCardListAnimation(const RefPtr<RenderNode> & renderNode)221 void CardTransitionController::CreateCardListAnimation(const RefPtr<RenderNode>& renderNode)
222 {
223 auto renderListItem = AceType::DynamicCast<RenderListItem>(renderNode);
224 if (!renderListItem) {
225 return;
226 }
227 auto height = renderListItem->GetLayoutSize().Height();
228 auto start = (event_ == TransitionEvent::PUSH_START) ? 0.0 : height;
229 auto end = (event_ == TransitionEvent::PUSH_START) ? height : 0.0;
230 auto heightAnimation = AceType::MakeRefPtr<CurveAnimation<double>>(start, end, Curves::FRICTION);
231 auto listItemWeak = AceType::WeakClaim(AceType::RawPtr(renderListItem));
232 heightAnimation->AddListener([listItemWeak](double value) {
233 auto renderListItem = listItemWeak.Upgrade();
234 if (renderListItem) {
235 renderListItem->RunCardTransitionAnimation(value);
236 }
237 });
238 controller_->AddInterpolator(heightAnimation);
239 controller_->AddStopListener([listItemWeak] {
240 auto renderListItem = listItemWeak.Upgrade();
241 if (renderListItem) {
242 renderListItem->StopCardTransitionAnimation();
243 }
244 });
245 }
246
CreateExternalAnimation(double height,double cardOffsetY)247 void CardTransitionController::CreateExternalAnimation(double height, double cardOffsetY)
248 {
249 auto pageSrc = (event_ == TransitionEvent::PUSH_START) ? pageSrc_.Upgrade() : pageDest_.Upgrade();
250 if (!pageSrc) {
251 return;
252 }
253 const auto& transformMap = pageSrc->GetCardTransitionMap();
254 for (auto& item : transformMap) {
255 auto& transformWeak = item.second;
256 auto transformElement = transformWeak.Upgrade();
257 if (!transformElement) {
258 continue;
259 }
260 auto transformRender = AceType::DynamicCast<RenderTransform>(transformElement->GetRenderNode());
261 if (!transformRender) {
262 continue;
263 }
264 auto transformOffsetY = transformRender->GetGlobalOffset().GetY();
265 if (NearEqual(transformOffsetY, cardOffsetY)) {
266 continue;
267 }
268 auto shiftHeight = height;
269 if (transformOffsetY < cardOffsetY) {
270 shiftHeight = -height;
271 }
272 auto start = (event_ == TransitionEvent::PUSH_START) ? 0.0 : shiftHeight;
273 auto end = (event_ == TransitionEvent::PUSH_START) ? shiftHeight : 0.0;
274 auto positionAnimation = AceType::MakeRefPtr<CurveAnimation<double>>(start, end, Curves::FRICTION);
275 auto weakTransform = AceType::WeakClaim(AceType::RawPtr(transformRender));
276 positionAnimation->AddListener([weakTransform](double value) {
277 auto transform = weakTransform.Upgrade();
278 if (transform) {
279 transform->ResetTransform();
280 transform->Translate(Dimension(), Dimension(value));
281 }
282 });
283 controller_->AddInterpolator(positionAnimation);
284 controller_->AddStopListener([weakTransform, event = event_] {
285 auto transform = weakTransform.Upgrade();
286 if (transform && event == TransitionEvent::PUSH_START) {
287 transform->ResetTransform();
288 }
289 });
290 }
291 }
292
AddPrepareListener(const WeakPtr<RenderTransform> & weakTransform)293 void CardTransitionController::AddPrepareListener(const WeakPtr<RenderTransform>& weakTransform)
294 {
295 controller_->AddPrepareListener([weakTransform, weakContext = context_, weak = AceType::WeakClaim(this)]() {
296 auto context = weakContext.Upgrade();
297 auto cardTransition = weak.Upgrade();
298 auto transform = weakTransform.Upgrade();
299 if (context && cardTransition && transform) {
300 auto currentTimestamp = context->GetTimeFromExternalTimer();
301 if (cardTransition->currentTimestamp_ != currentTimestamp || cardTransition->currentTimestamp_ == 0) {
302 transform->ResetTransform();
303 cardTransition->currentTimestamp_ = currentTimestamp;
304 }
305 }
306 });
307 }
308
AddStopListener(const WeakPtr<RenderTransform> & weakTransform)309 void CardTransitionController::AddStopListener(const WeakPtr<RenderTransform>& weakTransform)
310 {
311 controller_->AddStopListener([weakTransform] {
312 auto transform = weakTransform.Upgrade();
313 if (transform) {
314 transform->ResetTransform();
315 transform->SetTransformOrigin(Dimension(transform->GetLayoutSize().Width() * 0.5),
316 Dimension(transform->GetLayoutSize().Height() * 0.5));
317 transform->MarkNeedUpdateOrigin();
318 transform->SetDisableClickEffect(false);
319 }
320 });
321 }
322
GetCardTransformElement(const RefPtr<Element> & element)323 RefPtr<Element> CardTransitionController::GetCardTransformElement(const RefPtr<Element>& element)
324 {
325 auto childElement = element->GetFirstChild();
326 while (childElement) {
327 auto composedElement = AceType::DynamicCast<ComposedElement>(childElement);
328 if (composedElement) {
329 return nullptr;
330 }
331 auto transformElement = AceType::DynamicCast<TransformElement>(childElement);
332 if (transformElement) {
333 return transformElement;
334 }
335 childElement = childElement->GetFirstChild();
336 }
337 return nullptr;
338 }
339
GetCardRect(const ComposeId & composeId) const340 RRect CardTransitionController::GetCardRect(const ComposeId& composeId) const
341 {
342 auto pipelineContext = context_.Upgrade();
343 if (!pipelineContext) {
344 LOGE("get card rect failed. pipeline is null.");
345 return RRect();
346 }
347 auto cardComposeElement = pipelineContext->GetComposedElementById(composeId);
348 if (!cardComposeElement) {
349 return RRect();
350 }
351 auto renderListItem = AceType::DynamicCast<RenderListItem>(cardComposeElement->GetRenderNode());
352 if (!renderListItem) {
353 return RRect();
354 }
355 return renderListItem->GetRRect();
356 }
357
358 } // namespace OHOS::Ace
359