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/shared_transition_effect.h"
17
18 #include "core/components/common/properties/page_transition_option.h"
19 #include "core/components/overlay/overlay_element.h"
20 #include "core/components/positioned/positioned_component.h"
21 #include "core/components/shared_transition/shared_transition_element.h"
22 #include "core/components/tween/tween_component.h"
23
24 namespace OHOS::Ace {
25
SharedTransitionEffect(const ShareId & shareId,SharedTransitionEffectType type)26 SharedTransitionEffect::SharedTransitionEffect(const ShareId& shareId, SharedTransitionEffectType type)
27 : shareId_(shareId), type_(type)
28 {
29 controller_ = CREATE_ANIMATOR();
30 }
31
CheckIn(TransitionEvent event,WeakPtr<SharedTransitionElement> & sharedWeak,Offset & ticket)32 bool SharedTransitionEffect::CheckIn(
33 TransitionEvent event, WeakPtr<SharedTransitionElement>& sharedWeak, Offset& ticket)
34 {
35 if (!controller_) {
36 LOGE("Check In failed. controller is null. share id: %{public}s", shareId_.c_str());
37 return false;
38 }
39 auto shared = sharedWeak.Upgrade();
40 if (!shared) {
41 LOGE("Check In failed. passenger element is null. share id: %{public}s", shareId_.c_str());
42 return false;
43 }
44 // Check-in
45 if (!shared->AboardShuttle(ticket)) {
46 LOGE("Check In failed. aboard shuttle failed. share id: %{public}s.", shareId_.c_str());
47 return false;
48 }
49 // Arrange Return Shuttle
50 controller_->AddStopListener([sharedWeak, shareId = shareId_]() {
51 auto shared = sharedWeak.Upgrade();
52 if (!shared) {
53 return;
54 }
55 shared->GetOffShuttle();
56 });
57 controller_->AddStopListener([destWeak = dest_, srcWeak = src_]() {
58 auto dest = destWeak.Upgrade();
59 if (dest) {
60 dest->SetSizeModified(nullptr);
61 }
62 auto src = srcWeak.Upgrade();
63 if (src) {
64 src->SetSizeModified(nullptr);
65 }
66 });
67 return true;
68 }
69
TakeOff(TransitionEvent event,RefPtr<OverlayElement> & overlay,WeakPtr<SharedTransitionElement> & sharedWeak,const Offset & ticket,TweenOption & option)70 bool SharedTransitionEffect::TakeOff(TransitionEvent event, RefPtr<OverlayElement>& overlay,
71 WeakPtr<SharedTransitionElement>& sharedWeak, const Offset& ticket, TweenOption& option)
72 {
73 if (!controller_) {
74 LOGE("TakeOff failed. controller is null. share id: %{public}s", shareId_.c_str());
75 return false;
76 }
77 if (!overlay) {
78 LOGE("TakeOff failed. overlay is null. event: %{public}d, share id: %{public}s", event, shareId_.c_str());
79 return false;
80 }
81 auto shared = sharedWeak.Upgrade();
82 if (!shared) {
83 LOGE("TakeOff failed. shared is null. event: %{public}d, share id: %{public}s", event, shareId_.c_str());
84 return false;
85 }
86 auto passengerComponent = shared->GetPassengerComponent();
87 auto passengerElement = shared->GetPassengerElement();
88 if (!passengerComponent || !passengerElement) {
89 LOGE("TakeOff failed. passenger not found. event: %{public}d, share id: %{public}s.", event, shareId_.c_str());
90 return false;
91 }
92 auto tweenSeat = AceType::MakeRefPtr<TweenComponent>("TweenSeat");
93 // Find Seat.
94 auto seat = AceType::MakeRefPtr<PositionedComponent>(tweenSeat);
95 Component::MergeRSNode(seat);
96 seat->SetLeft(Dimension(ticket.GetX(), DimensionUnit::PX));
97 seat->SetTop(Dimension(ticket.GetY(), DimensionUnit::PX));
98 // set zIndex
99 auto zIndex = shared->GetZIndex();
100 if (zIndex != 0) {
101 seat->SetZIndex(zIndex);
102 }
103 // Take Off,
104 overlay->PushInstant(seat);
105 auto seatElement = AceType::DynamicCast<PositionedElement>(overlay->GetLastChild());
106 if (!seatElement) {
107 LOGE("TakeOff failed. seat not found. event: %{public}d, share id: %{public}s.", event, shareId_.c_str());
108 return false;
109 }
110 // make overlay un-focusable.
111 seatElement->SetFocusable(false);
112 auto seatRender = seatElement->GetRenderNode();
113 if (!seatRender) {
114 LOGE("TakeOff failed, render is nullptr");
115 return false;
116 }
117 seatRender->SetDisableTouchEvent(true);
118 return TakeOffTween(seatElement->GetFirstChild(), passengerComponent, passengerElement, option);
119 }
120
TakeOffTween(const RefPtr<Element> & tweenElement,const RefPtr<Component> & passengerComponent,const RefPtr<Element> & passengerElement,TweenOption & option)121 bool SharedTransitionEffect::TakeOffTween(const RefPtr<Element>& tweenElement,
122 const RefPtr<Component>& passengerComponent, const RefPtr<Element>& passengerElement, TweenOption& option)
123 {
124 auto tweenSeatElement = AceType::DynamicCast<TweenElement>(tweenElement);
125 if (!tweenSeatElement) {
126 LOGE("TakeOff failed. tween not found. share id: %{public}s.", shareId_.c_str());
127 return false;
128 }
129 auto contentParent = tweenSeatElement->GetContentParent();
130 if (!contentParent) {
131 LOGE("TakeOff failed. content parent not found. share id: %{public}s.", shareId_.c_str());
132 return false;
133 }
134 passengerElement->SetNewComponent(passengerComponent);
135 passengerElement->Mount(contentParent);
136 auto passengerRender = passengerElement->GetRenderNode();
137 auto parentRender = contentParent->GetRenderNode();
138 if (passengerRender && parentRender) {
139 // Follow parent's hidden status.
140 passengerRender->SetHidden(parentRender->GetHidden());
141 }
142 tweenSeatElement->SetController(controller_);
143 tweenSeatElement->SetOption(option);
144 tweenSeatElement->ApplyKeyframes();
145 tweenSeatElement->ApplyOptions();
146 tweenSeatElement_ = tweenSeatElement;
147 return true;
148 }
149
GetSharedTransitionEffect(SharedTransitionEffectType effect,const ShareId & shareId)150 RefPtr<SharedTransitionEffect> SharedTransitionEffect::GetSharedTransitionEffect(
151 SharedTransitionEffectType effect, const ShareId& shareId)
152 {
153 switch (effect) {
154 case SharedTransitionEffectType::SHARED_EFFECT_EXCHANGE: {
155 return AceType::MakeRefPtr<SharedTransitionExchange>(shareId);
156 }
157 case SharedTransitionEffectType::SHARED_EFFECT_STATIC: {
158 return AceType::MakeRefPtr<SharedTransitionStatic>(shareId);
159 }
160 default: {
161 LOGE("Unknown effect. effect: %{public}d, share id: %{public}s", effect, shareId.c_str());
162 return nullptr;
163 }
164 }
165 }
166
ApplyAnimation(RefPtr<OverlayElement> & overlay,RefPtr<Animator> & controller,TweenOption & option,TransitionEvent event)167 bool SharedTransitionEffect::ApplyAnimation(
168 RefPtr<OverlayElement>& overlay, RefPtr<Animator>& controller, TweenOption& option, TransitionEvent event)
169 {
170 controller_->ClearAllListeners();
171 controller_->ClearInterpolators();
172 return true;
173 }
174
Allow(TransitionEvent event)175 bool SharedTransitionExchange::Allow(TransitionEvent event)
176 {
177 auto dest = dest_.Upgrade();
178 auto src = src_.Upgrade();
179 if (!dest || !src) {
180 return false;
181 }
182 bool allow = false;
183 if (event == TransitionEvent::PUSH_START) {
184 // In Push Scene, dest means Enter and Source means Exit
185 allow = dest->IsEnablePushEnter() && src->IsEnablePushExit();
186 } else if (event == TransitionEvent::POP_START) {
187 // In Pop Scene, dest means Enter and Source means Exit
188 allow = dest->IsEnablePopEnter() && src->IsEnablePopExit();
189 }
190 return allow;
191 }
192
AddLazyLoadCallback(TransitionEvent event)193 void SharedTransitionExchange::AddLazyLoadCallback(TransitionEvent event)
194 {
195 auto src = src_.Upgrade();
196 auto dest = dest_.Upgrade();
197 if (!dest || !src) {
198 LOGE("Add Lazy callback failed. Dest or src is null. event: %{public}d, share id: %{public}s", event,
199 shareId_.c_str());
200 return;
201 }
202 // Lazy load child size. make width and height animation later.
203 auto effectWeak = AceType::WeakClaim(this);
204 dest->SetSizeModified([effectWeak, event, shareId = shareId_]() {
205 auto effect = effectWeak.Upgrade();
206 if (!effect) {
207 LOGE("Create Lazy load animation failed. effect is null.");
208 return;
209 }
210 auto tweenSeatElement = effect->tweenSeatElement_.Upgrade();
211 if (!tweenSeatElement) {
212 LOGE("Create Lazy load animation failed. tween Seat Element is null.");
213 return;
214 }
215 TweenOption option = tweenSeatElement->GetOption();
216 option.ClearListeners();
217 if (!effect->CreateAnimation(option, event, true)) {
218 LOGE("Create animation failed. event: %{public}d, share id: %{public}s", event, shareId.c_str());
219 return;
220 }
221
222 tweenSeatElement->SetOption(option);
223 effect->controller_->ClearInterpolators();
224 tweenSeatElement->ApplyKeyframes();
225 });
226 }
227
CreateTranslateAnimation(TweenOption & option,TransitionEvent event,bool calledByLazyLoad)228 bool SharedTransitionExchange::CreateTranslateAnimation(
229 TweenOption& option, TransitionEvent event, bool calledByLazyLoad)
230 {
231 auto src = src_.Upgrade();
232 auto dest = dest_.Upgrade();
233 if (!dest || !src) {
234 LOGE("Create exchange animation failed. dest or src is null. event: %{public}d, share id: %{public}s", event,
235 shareId_.c_str());
236 return false;
237 }
238 auto& translateMap = option.GetTranslateAnimations();
239 auto translateIter = translateMap.find(AnimationType::TRANSLATE);
240 if ((calledByLazyLoad && autoTranslate_) || (translateIter == translateMap.end())) {
241 // if no custom translate animation, add exchange translate animation.
242 auto destOffset = dest->GetGlobalOffset();
243 auto srcOffset = src->GetGlobalOffset();
244 if (destOffset != srcOffset) {
245 auto translateAnimation = AceType::MakeRefPtr<CurveAnimation<DimensionOffset>>(
246 Offset(0, 0), destOffset - srcOffset, Curves::FRICTION);
247 const auto& motionPathOption = option.GetMotionPathOption();
248 if (motionPathOption.IsValid()) {
249 auto motionPathEvaluator =
250 AceType::MakeRefPtr<MotionPathEvaluator>(motionPathOption, Offset(0, 0), destOffset - srcOffset);
251 translateAnimation->SetEvaluator(motionPathEvaluator->CreateDimensionOffsetEvaluator());
252 if (motionPathOption.GetRotate()) {
253 auto rotateAnimation = AceType::MakeRefPtr<CurveAnimation<float>>(0.0f, 1.0f, option.GetCurve());
254 rotateAnimation->SetEvaluator(motionPathEvaluator->CreateRotateEvaluator());
255 option.SetTransformFloatAnimation(AnimationType::ROTATE_Z, rotateAnimation);
256 }
257 }
258 option.SetTranslateAnimations(AnimationType::TRANSLATE, translateAnimation);
259 autoTranslate_ = true;
260 }
261 }
262 return true;
263 }
264
CreateSizeAnimation(TweenOption & option,TransitionEvent event,bool isLazy)265 bool SharedTransitionExchange::CreateSizeAnimation(TweenOption& option, TransitionEvent event, bool isLazy)
266 {
267 auto src = src_.Upgrade();
268 auto dest = dest_.Upgrade();
269 if (!dest || !src) {
270 LOGE("Create exchange animation failed. dest or src is null. event: %{public}d, share id: %{public}s", event,
271 shareId_.c_str());
272 return false;
273 }
274 auto destSize = dest->GetSuitSize();
275 auto srcSize = src->GetSuitSize();
276
277 // add width shared transition
278 auto& propertyMap = option.GetFloatPropertyAnimation();
279 auto widthIter = propertyMap.find(PropertyAnimatableType::PROPERTY_WIDTH);
280 if (((isLazy && autoWidth_) || (widthIter == propertyMap.end())) && !NearEqual(destSize.Width(), srcSize.Width())) {
281 auto widthAnimation =
282 AceType::MakeRefPtr<CurveAnimation<float>>(srcSize.Width(), destSize.Width(), Curves::FRICTION);
283 option.SetPropertyAnimationFloat(PropertyAnimatableType::PROPERTY_WIDTH, widthAnimation);
284 autoWidth_ = true;
285 }
286
287 // add scaleY shared transition
288 auto heightIter = propertyMap.find(PropertyAnimatableType::PROPERTY_HEIGHT);
289 if (((isLazy && autoHeight_) || (heightIter == propertyMap.end())) &&
290 !NearEqual(destSize.Height(), srcSize.Height())) {
291 auto heightAnimation =
292 AceType::MakeRefPtr<CurveAnimation<float>>(srcSize.Height(), destSize.Height(), Curves::FRICTION);
293 option.SetPropertyAnimationFloat(PropertyAnimatableType::PROPERTY_HEIGHT, heightAnimation);
294 autoHeight_ = true;
295 }
296 return true;
297 }
298
CreateOpacityAnimation(TweenOption & option,TransitionEvent event,bool isLazy)299 bool SharedTransitionExchange::CreateOpacityAnimation(TweenOption& option, TransitionEvent event, bool isLazy)
300 {
301 auto src = src_.Upgrade();
302 auto dest = dest_.Upgrade();
303 if (!dest || !src) {
304 LOGE("Create exchange animation failed. dest or src is null. event: %{public}d, share id: %{public}s", event,
305 shareId_.c_str());
306 return false;
307 }
308 auto destOpacity = dest->GetOpacity();
309 auto srcOpacity = src->GetOpacity();
310
311 if (!NearEqual(destOpacity, srcOpacity) && !option.GetOpacityAnimation()) {
312 auto opacityAnimation = AceType::MakeRefPtr<CurveAnimation<float>>(srcOpacity, destOpacity, Curves::FRICTION);
313 option.SetOpacityAnimation(opacityAnimation);
314 }
315 return true;
316 }
317
CreateAnimation(TweenOption & option,TransitionEvent event,bool isLazy)318 bool SharedTransitionExchange::CreateAnimation(TweenOption& option, TransitionEvent event, bool isLazy)
319 {
320 auto src = src_.Upgrade();
321 auto dest = dest_.Upgrade();
322 if (!dest || !src) {
323 LOGE("Create exchange animation failed. dest or src is null. event: %{public}d, share id: %{public}s", event,
324 shareId_.c_str());
325 return false;
326 }
327 if (!isLazy) {
328 autoTranslate_ = false;
329 autoWidth_ = false;
330 autoHeight_ = false;
331 }
332
333 // add translate shared transition
334 if (!CreateTranslateAnimation(option, event, isLazy)) {
335 return false;
336 }
337 if (!CreateSizeAnimation(option, event, isLazy)) {
338 return false;
339 }
340 if (!CreateOpacityAnimation(option, event, isLazy)) {
341 return false;
342 }
343 AddLazyLoadCallback(event);
344 return true;
345 }
346
ApplyAnimation(RefPtr<OverlayElement> & overlay,RefPtr<Animator> & controller,TweenOption & option,TransitionEvent event)347 bool SharedTransitionExchange::ApplyAnimation(RefPtr<OverlayElement>& overlay, RefPtr<Animator>& controller,
348 TweenOption& option, TransitionEvent event)
349 {
350 if (!SharedTransitionEffect::ApplyAnimation(overlay, controller, option, event)) {
351 return false;
352 }
353 Offset ticket;
354 if (!CheckIn(event, src_, ticket)) {
355 LOGE(
356 "Apply exchange failed. check in failed. event: %{public}d, share id: %{public}s", event, shareId_.c_str());
357 return false;
358 }
359 return TakeOff(event, overlay, src_, ticket, option);
360 }
361
Allow(TransitionEvent event)362 bool SharedTransitionStatic::Allow(TransitionEvent event)
363 {
364 auto current = GetCurrentSharedElement().Upgrade();
365 if (!current) {
366 return false;
367 }
368 bool allow = false;
369 if (event == TransitionEvent::PUSH_START) {
370 // In Push Scene, dest means Enter and Source means Exit
371 allow = current->IsEnablePushEnter();
372 } else if (event == TransitionEvent::POP_START) {
373 // In Pop Scene, dest means Enter and Source means Exit
374 allow = current->IsEnablePopEnter();
375 }
376 return allow;
377 }
378
CreateAnimation(TweenOption & option,TransitionEvent event,bool isLazy)379 bool SharedTransitionStatic::CreateAnimation(TweenOption& option, TransitionEvent event, bool isLazy)
380 {
381 if (src_.Invalid()) {
382 // anchor appearing, opacity 0 to 1
383 auto opacityAnimation = option.GetOpacityAnimation();
384 if (!opacityAnimation) {
385 TransitionTweenOptionFactory::CreateSharedTweenOption(
386 SharedTransitionEffectType::SHARED_EFFECT_STATIC, option);
387 }
388 } else {
389 // anchor disappearing, opacity 1 to 0
390 if (!option.GetOpacityAnimation()) {
391 auto animation = AceType::MakeRefPtr<CurveAnimation<float>>(1.0f, 0.0f, option.GetCurve());
392 option.SetOpacityAnimation(animation);
393 }
394 }
395 return true;
396 }
397
ApplyAnimation(RefPtr<OverlayElement> & overlay,RefPtr<Animator> & controller,TweenOption & option,TransitionEvent event)398 bool SharedTransitionStatic::ApplyAnimation(
399 RefPtr<OverlayElement>& overlay, RefPtr<Animator>& controller, TweenOption& option, TransitionEvent event)
400 {
401 if (!SharedTransitionEffect::ApplyAnimation(overlay, controller, option, event)) {
402 return false;
403 }
404 Offset ticket;
405 // the dest page and source page elements are in effect
406 auto current = GetCurrentSharedElement();
407 if (!CheckIn(event, current, ticket)) {
408 LOGE("Apply static fail. check in failed. event: %{public}d, share id: %{public}s", event, shareId_.c_str());
409 return false;
410 }
411 AddLazyLoadCallback();
412 return TakeOff(event, overlay, current, ticket, option);
413 }
414
AddLazyLoadCallback()415 void SharedTransitionStatic::AddLazyLoadCallback()
416 {
417 auto current = GetCurrentSharedElement().Upgrade();
418 if (!current) {
419 LOGE("Add Lazy load Callback failed. current is null.");
420 return;
421 }
422 current->SetSizeModified([effectWeak = WeakClaim(this)]() {
423 auto effect = effectWeak.Upgrade();
424 if (!effect) {
425 LOGE("Fix static shared element position failed. effect is null");
426 return;
427 }
428 auto tweenSeatElement = effect->tweenSeatElement_.Upgrade();
429 if (!tweenSeatElement) {
430 LOGE("Fix static shared element position failed. tween element is null");
431 return;
432 }
433 auto positionedElement =
434 AceType::DynamicCast<PositionedElement>(tweenSeatElement->GetElementParent().Upgrade());
435 if (!positionedElement) {
436 LOGE("Fix static shared element position failed. positioned element is null");
437 return;
438 }
439 auto positionedRender = AceType::DynamicCast<RenderPositioned>(positionedElement->GetRenderNode());
440 if (!positionedRender) {
441 LOGE("Fix static shared element position failed. positioned render is null");
442 return;
443 }
444 auto dest = effect->currentWorking_.Upgrade();
445 if (!dest) {
446 return;
447 }
448 auto offset = dest->GetGlobalOffset();
449 positionedRender->SetTop(Dimension(offset.GetY(), DimensionUnit::PX));
450 positionedRender->SetLeft(Dimension(offset.GetX(), DimensionUnit::PX));
451 });
452 }
453
454 } // namespace OHOS::Ace
455