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