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