• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_controller.h"
17 
18 #include "core/components/common/properties/tween_option.h"
19 #include "core/components/overlay/overlay_element.h"
20 #include "core/components/page/page_element.h"
21 #include "core/components/page_transition/page_transition_element.h"
22 
23 namespace OHOS::Ace {
24 namespace {
25 
GetSharedEffect(const ShareId & shareId,const WeakPtr<SharedTransitionElement> & destWeak,const WeakPtr<SharedTransitionElement> & srcWeak)26 RefPtr<SharedTransitionEffect> GetSharedEffect(const ShareId& shareId, const WeakPtr<SharedTransitionElement>& destWeak,
27     const WeakPtr<SharedTransitionElement>& srcWeak)
28 {
29     auto dest = destWeak.Upgrade();
30     auto src = srcWeak.Upgrade();
31     if ((!src) && (!dest)) {
32         LOGD("No Shared element found. share id: %{public}s", shareId.c_str());
33         return nullptr;
34     }
35     RefPtr<SharedTransitionEffect> effect = dest ? dest->GetEffect() : nullptr;
36     if (!effect) {
37         effect = src ? src->GetEffect() : nullptr;
38     }
39     if (!effect) {
40         // use default effect.
41         effect = SharedTransitionEffect::GetSharedTransitionEffect(
42             SharedTransitionEffectType::SHARED_EFFECT_EXCHANGE, shareId);
43     }
44     return effect;
45 }
46 
47 } // namespace
48 
SharedTransitionController(const WeakPtr<PipelineContext> & context)49 SharedTransitionController::SharedTransitionController(const WeakPtr<PipelineContext>& context) : context_(context)
50 {
51 };
52 
RegisterTransitionListener()53 void SharedTransitionController::RegisterTransitionListener()
54 {
55     auto pipelineContext = context_.Upgrade();
56     if (!pipelineContext) {
57         LOGE("Register Transition listener to stage failed. pipeline context is null.");
58         return;
59     }
60     auto weak = AceType::WeakClaim(this);
61     pipelineContext->AddPageTransitionListener(
62         [weak](const TransitionEvent& event, const WeakPtr<PageElement>& in, const WeakPtr<PageElement>& out) {
63             if ((event != TransitionEvent::POP_START) && (event != TransitionEvent::PUSH_START)) {
64                 LOGD("handle event: %{public}d failed. unknown event.", event);
65                 return;
66             }
67             auto controller = weak.Upgrade();
68             if (!controller) {
69                 LOGE("handle event: %{public}d failed. controller is null.", event);
70                 return;
71             }
72             auto context = controller->context_.Upgrade();
73             if (!context) {
74                 LOGE("Add shared transition to pipeline failed. context is null.");
75                 return;
76             }
77             if (event == TransitionEvent::PUSH_START) {
78                 controller->pageDest_ = in;
79                 controller->pageSrc_ = out;
80             } else {
81                 controller->pageDest_ = out;
82                 controller->pageSrc_ = in;
83             }
84             controller->event_ = event;
85             LOGD("Add shared transition controller to pipeline. event: %{public}d.", event);
86 
87             // when page pushed in, new pushed page's layout parameters is valid after perform layout.
88             // And shared transition needs new pushed page's layout parameters.
89             // So start shared transition in prepare animation.
90             controller->StartSharedTransition();
91         });
92 }
93 
StartSharedTransition()94 void SharedTransitionController::StartSharedTransition()
95 {
96     // finish previous transition
97     for (const auto& controller : controllers_) {
98         if (controller) {
99             controller->Finish();
100             controller->ClearAllListeners();
101             controller->ClearInterpolators();
102         }
103     }
104     controllers_.clear();
105     stopControllerCount_ = 0;
106 
107     auto pipelineContext = context_.Upgrade();
108     if (!pipelineContext) {
109         LOGE("Start shared transition failed. pipeline is null.");
110         return;
111     }
112     auto overlay = pipelineContext->GetOverlayElement();
113     if (!overlay) {
114         LOGE("Start shared transition failed. overlay is null.");
115         return;
116     }
117     KickoffSharedTransition(event_, overlay);
118 }
119 
KickoffSharedTransition(TransitionEvent event,RefPtr<OverlayElement> & overlay)120 void SharedTransitionController::KickoffSharedTransition(TransitionEvent event, RefPtr<OverlayElement>& overlay)
121 {
122     auto pageDest = pageDest_.Upgrade();
123     if (!pageDest) {
124         LOGD("Kickoff shared transition failed. page dest is null. event: %{public}d", event);
125         return;
126     }
127     auto destTransition = PageTransitionElement::GetTransitionElement(pageDest);
128     auto pageId = pageDest->GetPageId();
129     if (!destTransition) {
130         LOGE("Kickoff shared transition failed. page transition is null. page id: %{public}d", pageId);
131         return;
132     }
133     hasSharedTransition_ = PrepareTransition(overlay);
134     if (!hasSharedTransition_) {
135         LOGD("No shared elements found. event: %{public}d. dest page id: %{public}d", event_, pageId);
136         return;
137     }
138 
139     if (!controllers_.empty()) {
140         controllers_.front()->AddStartListener([overlayWeak = WeakClaim(RawPtr(overlay))]() {
141             auto overlay = overlayWeak.Upgrade();
142             if (overlay) {
143                 auto overlayRender = overlay->GetRenderNode();
144                 if (overlayRender) {
145                     overlayRender->SetVisible(true);
146                 }
147             }
148         });
149     }
150 
151     for (const auto& controller : controllers_) {
152         if (controller) {
153             controller->SetFillMode(FillMode::FORWARDS);
154             controller->SetAllowRunningAsynchronously(true);
155             controller->AddStopListener([effectWeak = WeakClaim(this), overlayWeak = WeakClaim(RawPtr(overlay))]() {
156                 auto effect = effectWeak.Upgrade();
157                 if (!effect) {
158                     LOGE("effect is null.");
159                     return;
160                 }
161                 effect->stopControllerCount_++;
162                 if (static_cast<uint32_t>(effect->stopControllerCount_) >= effect->controllers_.size()) {
163                     auto overlay = overlayWeak.Upgrade();
164                     if (overlay) {
165                         // shared element will be removed when get off shuttle, just make sure no shared left on the
166                         // overlay
167                         overlay->Clear();
168                         auto overlayRender = overlay->GetRenderNode();
169                         if (overlayRender) {
170                             overlayRender->SetVisible(false);
171                         }
172                     }
173                 }
174             });
175         }
176     }
177 }
178 
CheckAndCreateTransition(std::vector<RefPtr<SharedTransitionEffect>> & effects,RefPtr<OverlayElement> & overlay)179 bool SharedTransitionController::CheckAndCreateTransition(
180     std::vector<RefPtr<SharedTransitionEffect>>& effects, RefPtr<OverlayElement>& overlay)
181 {
182     bool hasShared = false;
183     for (auto& effect : effects) {
184         const auto& shareId = effect->GetShareId();
185         if (!effect->Allow(event_)) {
186             LOGE("Shared transition not allowed, event: %{public}d, share id: %{public}s", event_, shareId.c_str());
187             continue;
188         }
189         if (!PrepareEachTransition(shareId, effect, overlay)) {
190             LOGE("Prepare shared transition failed. share id: %{public}s", shareId.c_str());
191             continue;
192         }
193         hasShared = true;
194     }
195     return hasShared;
196 }
197 
PrepareTransition(RefPtr<OverlayElement> overlay,bool preCheck)198 bool SharedTransitionController::PrepareTransition(RefPtr<OverlayElement> overlay, bool preCheck)
199 {
200     auto pageDest = pageDest_.Upgrade();
201     auto pageSrc = pageSrc_.Upgrade();
202     if ((!pageSrc) || (!pageDest)) {
203         LOGD("Prepare shared transition failed. page src / dest is null. event: %{public}d", event_);
204         return false;
205     }
206     LOGD("Kickoff shared transition. event: %{public}d. src page id: %{public}d, dest page id: %{public}d", event_,
207         pageSrc->GetPageId(), pageDest->GetPageId());
208     const auto& srcMap = pageSrc->GetSharedTransitionMap();
209     const auto& destMap = pageDest->GetSharedTransitionMap();
210     bool hasShared = false;
211     std::vector<RefPtr<SharedTransitionEffect>> effects;
212     std::vector<RefPtr<SharedTransitionEffect>> anchorEffects;
213 
214     // find out all exchange effect or static effect in dest page
215     for (auto& item : destMap) {
216         auto shareId = item.first;
217         auto& destWeak = item.second;
218         auto srcSharedIter = srcMap.find(shareId);
219         WeakPtr<SharedTransitionElement> srcWeak;
220         if (srcSharedIter == srcMap.end()) {
221             LOGD("Shared src not found, maybe the effect do not need it. share id: %{public}s", shareId.c_str());
222         } else {
223             srcWeak = srcSharedIter->second;
224         }
225         RefPtr<SharedTransitionEffect> effect = GetSharedEffect(shareId, destWeak, srcWeak);
226         if (!effect) {
227             LOGD("Shared effect is null, maybe no shared element at all. share id: %{public}s", shareId.c_str());
228             continue;
229         }
230         if (preCheck) {
231             // Return true, when find the first shared transition.
232             return true;
233         }
234         effect->SetSharedElement(srcWeak, destWeak);
235         effect->setCurrentSharedElement(destWeak);
236         if (effect->GetType() == SharedTransitionEffectType::SHARED_EFFECT_STATIC) {
237             anchorEffects.push_back(effect);
238         } else {
239             effects.push_back(effect);
240         }
241     }
242 
243     // find out all static effect in source page only in ace declarative
244     auto context = context_.Upgrade();
245     if (context && context->GetIsDeclarative()) {
246         for (auto& item : srcMap) {
247             auto sharedId = item.first;
248             auto& sourceWeak = item.second;
249             RefPtr<SharedTransitionEffect> effect = GetSharedEffect(sharedId, nullptr, sourceWeak);
250             if (!effect || effect->GetType() != SharedTransitionEffectType::SHARED_EFFECT_STATIC) {
251                 LOGE(
252                     "Shared effect is null or type is not static, maybe no shared element at all. share id: %{public}s",
253                     sharedId.c_str());
254                 continue;
255             }
256             if (preCheck) {
257                 // Return true, when find the first shared transition.
258                 return true;
259             }
260             effect->SetSharedElement(sourceWeak, nullptr);
261             effect->setCurrentSharedElement(sourceWeak);
262             if (effect->GetType() == SharedTransitionEffectType::SHARED_EFFECT_STATIC) {
263                 anchorEffects.push_back(effect);
264             } else {
265                 effects.push_back(effect);
266             }
267         }
268     }
269 
270     // prepare each sharedTransition effect
271     hasShared = CheckAndCreateTransition(effects, overlay);
272     bool needsAnchor = hasShared || (context && !context->GetIsDeclarative());
273     if (needsAnchor) {
274         // anchor effects only available when other effects are available
275         hasShared = CheckAndCreateTransition(anchorEffects, overlay) || hasShared;
276     }
277 
278     if (!hasShared) {
279         LOGD("No shared elements found. event: %{public}d. Shared size: dest: %{public}zu, src: %{public}zu", event_,
280             destMap.size(), srcMap.size());
281     }
282     return hasShared;
283 }
284 
PrepareEachTransition(const ShareId & shareId,RefPtr<SharedTransitionEffect> & effect,RefPtr<OverlayElement> & overlay)285 bool SharedTransitionController::PrepareEachTransition(
286     const ShareId& shareId, RefPtr<SharedTransitionEffect>& effect, RefPtr<OverlayElement>& overlay)
287 {
288     auto currentWeak = effect->GetCurrentSharedElement();
289     auto current = currentWeak.Upgrade();
290     if (!current) {
291         LOGE("Prepare each transition failed. dest is null. share id: %{public}s", shareId.c_str());
292         return false;
293     }
294     auto option = current->GetOption();
295     if (!effect->CreateAnimation(option, event_, false)) {
296         LOGE("Create animation failed. event: %{public}d, share id: %{public}s", event_, shareId.c_str());
297         return false;
298     }
299     auto tmp = AceType::MakeRefPtr<Animator>();
300     if (!effect->ApplyAnimation(overlay, tmp, option, event_)) {
301         LOGE("Apply animation failed. event: %{public}d, share id: %{public}s", event_, shareId.c_str());
302         return false;
303     }
304     LOGD("Prepare Shared Transition. event: %{public}d, share id: %{public}s", event_, shareId.c_str());
305 
306     auto animator = effect->GetAnimator();
307     if (!animator) {
308         LOGE("GetAnimator failed. event: %{public}d, share id: %{public}s", event_, shareId.c_str());
309         return false;
310     }
311     controllers_.push_back(animator);
312     current->SetVisible(false);
313     animator->AddStopListener([currentWeak, shareId, event = event_]() {
314         auto current = currentWeak.Upgrade();
315         if (!current) {
316             LOGE("Stop shared element failed. shared in element is null. event: %{public}d, shareId: %{public}s", event,
317                 shareId.c_str());
318             return;
319         }
320         current->SetVisible(true);
321     });
322     return true;
323 }
324 
HasSharedTransition(TransitionEvent event)325 bool SharedTransitionController::HasSharedTransition(TransitionEvent event)
326 {
327     if (event_ == event) {
328         return hasSharedTransition_;
329     } else {
330         auto originEvent = event_;
331         event_ = event;
332         bool hasSharedTransition = PrepareTransition(nullptr, true);
333         event_ = originEvent;
334         return hasSharedTransition;
335     }
336 }
337 
338 } // namespace OHOS::Ace
339