[NUI] Add bindable properties to Components.
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / Navigation / Navigator.cs
1 /*
2  * Copyright(c) 2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 using System;
19 using System.Collections.Generic;
20 using System.ComponentModel;
21 using Tizen.NUI.BaseComponents;
22 using Tizen.NUI.Binding;
23
24 namespace Tizen.NUI.Components
25 {
26     /// <summary>
27     /// PoppedEventArgs is a class to record <see cref="Navigator.Popped"/> event arguments which will be sent to user.
28     /// </summary>
29     /// <since_tizen> 9 </since_tizen>
30     public class PoppedEventArgs : EventArgs
31     {
32         /// <summary>
33         /// Page popped by Navigator.
34         /// </summary>
35         /// <since_tizen> 9 </since_tizen>
36         public Page Page { get; internal set; }
37     }
38
39     /// <summary>
40     /// The Navigator is a class which navigates pages with stack methods such as Push and Pop.
41     /// </summary>
42     /// <remarks>
43     /// With Transition class, Navigator supports smooth transition of View pair between two Pages
44     /// by using <see cref="PushWithTransition(Page)"/> and <see cref="PopWithTransition()"/> methods.
45     /// If current top Page and next top Page have <see cref="View"/>s those have same TransitionTag,
46     /// Navigator creates smooth transition motion for them.
47     /// Navigator.Transition property can be used to set properties of the Transition such as TimePeriod and AlphaFunction.
48     /// When all transitions are finished, Navigator calls a callback methods those connected on the "TransitionFinished" event.
49     /// </remarks>
50     /// <example>
51     /// <code>
52     /// Navigator navigator = new Navigator()
53     /// {
54     ///     TimePeriod = new TimePeriod(500),
55     ///     AlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseInOutSine)
56     /// };
57     ///
58     /// View view = new View()
59     /// {
60     ///     TransitionOptions = new TransitionOptions()
61     ///     {
62     ///         /* Set properties for the transition of this View */
63     ///     }
64     /// };
65     ///
66     /// ContentPage newPage = new ContentPage()
67     /// {
68     ///     Content = view,
69     /// };
70     ///
71     /// Navigator.PushWithTransition(newPage);
72     /// </code>
73     /// </example>
74     /// <since_tizen> 9 </since_tizen>
75     public class Navigator : Control
76     {
77         /// <summary>
78         /// TransitionProperty
79         /// </summary>
80         [EditorBrowsable(EditorBrowsableState.Never)]
81         public static readonly BindableProperty TransitionProperty = BindableProperty.Create(nameof(Transition), typeof(Transition), typeof(Navigator), null, propertyChanged: (bindable, oldValue, newValue) =>
82         {
83             var instance = (Navigator)bindable;
84             if (newValue != null)
85             {
86                 instance.InternalTransition = newValue as Transition;
87             }
88         },
89         defaultValueCreator: (bindable) =>
90         {
91             var instance = (Navigator)bindable;
92             return instance.InternalTransition;
93         });
94
95         private const int DefaultTransitionDuration = 500;
96
97         //This will be replaced with view transition class instance.
98         private Animation curAnimation = null;
99
100         //This will be replaced with view transition class instance.
101         private Animation newAnimation = null;
102
103         private TransitionSet transitionSet = null;
104
105         private Transition transition = new Transition()
106         {
107             TimePeriod = new TimePeriod(DefaultTransitionDuration),
108             AlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Default),
109         };
110
111         private bool transitionFinished = true;
112
113         //TODO: Needs to consider how to remove disposed window from dictionary.
114         //Two dictionaries are required to remove disposed navigator from dictionary.
115         private static Dictionary<Window, Navigator> windowNavigator = new Dictionary<Window, Navigator>();
116         private static Dictionary<Navigator, Window> navigatorWindow = new Dictionary<Navigator, Window>();
117
118         private List<Page> navigationPages = new List<Page>();
119
120         /// <summary>
121         /// Creates a new instance of a Navigator.
122         /// </summary>
123         /// <since_tizen> 9 </since_tizen>
124         public Navigator() : base()
125         {
126             Layout = new AbsoluteLayout();
127         }
128
129         /// <inheritdoc/>
130         [EditorBrowsable(EditorBrowsableState.Never)]
131         public override void OnInitialize()
132         {
133             base.OnInitialize();
134
135             SetAccessibilityConstructor(Role.PageTabList);
136         }
137
138         /// <summary>
139         /// An event fired when Transition has been finished.
140         /// </summary>
141         /// <since_tizen> 9 </since_tizen>
142         public event EventHandler<EventArgs> TransitionFinished;
143
144         /// <summary>
145         /// An event fired when Pop of a page has been finished.
146         /// </summary>
147         /// <remarks>
148         /// When you free resources in the Popped event handler, please make sure if the popped page is the page you find.
149         /// </remarks>
150         /// <since_tizen> 9 </since_tizen>
151         public event EventHandler<PoppedEventArgs> Popped;
152
153         /// <summary>
154         /// Returns the count of pages in Navigator.
155         /// </summary>
156         /// <since_tizen> 9 </since_tizen>
157         public int PageCount => navigationPages.Count;
158
159         /// <summary>
160         /// Transition properties for the transition of View pair having same transition tag.
161         /// </summary>
162         /// <since_tizen> 9 </since_tizen>
163         public Transition Transition
164         {
165             get
166             {
167                 return GetValue(TransitionProperty) as Transition;
168             }
169             set
170             {
171                 SetValue(TransitionProperty, value);
172                 NotifyPropertyChanged();
173             }
174         }
175         private Transition InternalTransition
176         {
177             set
178             {
179                 transition = value;
180             }
181             get
182             {
183                 return transition;
184             }
185         }
186
187         /// <summary>
188         /// Pushes a page to Navigator.
189         /// If the page is already in Navigator, then it is not pushed.
190         /// </summary>
191         /// <param name="page">The page to push to Navigator.</param>
192         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
193         /// <since_tizen> 9 </since_tizen>
194         public void PushWithTransition(Page page)
195         {
196             if (!transitionFinished)
197             {
198                 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
199                 return;
200             }
201
202             if (page == null)
203             {
204                 throw new ArgumentNullException(nameof(page), "page should not be null.");
205             }
206
207             //Duplicate page is not pushed.
208             if (navigationPages.Contains(page)) return;
209
210             var topPage = Peek();
211
212             if (!topPage)
213             {
214                 Insert(0, page);
215                 return;
216             }
217
218             navigationPages.Add(page);
219             Add(page);
220             page.Navigator = this;
221
222             //Invoke Page events
223             page.InvokeAppearing();
224             topPage.InvokeDisappearing();
225
226             transitionSet = CreateTransitions(topPage, page, true);
227             transitionSet.Finished += (object sender, EventArgs e) =>
228             {
229                 if (page is DialogPage == false)
230                 {
231                    topPage.SetVisible(false);
232                 }
233
234                 //Invoke Page events
235                 page.InvokeAppeared();
236                 topPage.InvokeDisappeared();
237                 NotifyAccessibilityStatesChangeOfPages(topPage, page);
238             };
239             transitionFinished = false;
240         }
241
242         /// <summary>
243         /// Pops the top page from Navigator.
244         /// </summary>
245         /// <returns>The popped page.</returns>
246         /// <exception cref="InvalidOperationException">Thrown when there is no page in Navigator.</exception>
247         /// <since_tizen> 9 </since_tizen>
248         public Page PopWithTransition()
249         {
250             if (!transitionFinished)
251             {
252                 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
253                 return null;
254             }
255
256             if (navigationPages.Count == 0)
257             {
258                 throw new InvalidOperationException("There is no page in Navigator.");
259             }
260
261             var topPage = Peek();
262
263             if (navigationPages.Count == 1)
264             {
265                 Remove(topPage);
266
267                 //Invoke Popped event
268                 Popped?.Invoke(this, new PoppedEventArgs() { Page = topPage });
269
270                 return topPage;
271             }
272             var newTopPage = navigationPages[navigationPages.Count - 2];
273
274             //Invoke Page events
275             newTopPage.InvokeAppearing();
276             topPage.InvokeDisappearing();
277
278             transitionSet = CreateTransitions(topPage, newTopPage, false);
279             transitionSet.Finished += (object sender, EventArgs e) =>
280             {
281                 Remove(topPage);
282                 topPage.SetVisible(true);
283
284                 //Invoke Page events
285                 newTopPage.InvokeAppeared();
286                 topPage.InvokeDisappeared();
287                 NotifyAccessibilityStatesChangeOfPages(topPage, newTopPage);
288
289                 //Invoke Popped event
290                 Popped?.Invoke(this, new PoppedEventArgs() { Page = topPage });
291             };
292             transitionFinished = false;
293
294             return topPage;
295         }
296
297         /// <summary>
298         /// Pushes a page to Navigator.
299         /// If the page is already in Navigator, then it is not pushed.
300         /// </summary>
301         /// <param name="page">The page to push to Navigator.</param>
302         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
303         /// <since_tizen> 9 </since_tizen>
304         public void Push(Page page)
305         {
306             if (!transitionFinished)
307             {
308                 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
309                 return;
310             }
311
312             if (page == null)
313             {
314                 throw new ArgumentNullException(nameof(page), "page should not be null.");
315             }
316
317             //Duplicate page is not pushed.
318             if (navigationPages.Contains(page)) return;
319
320             var curTop = Peek();
321
322             if (!curTop)
323             {
324                 Insert(0, page);
325                 return;
326             }
327
328             navigationPages.Add(page);
329             Add(page);
330             page.Navigator = this;
331
332             //Invoke Page events
333             page.InvokeAppearing();
334             curTop.InvokeDisappearing();
335
336             //TODO: The following transition codes will be replaced with view transition.
337             InitializeAnimation();
338
339             if (page is DialogPage == false)
340             {
341                 curAnimation = new Animation(1000);
342                 curAnimation.AnimateTo(curTop, "Opacity", 1.0f, 0, 1000);
343                 curAnimation.EndAction = Animation.EndActions.StopFinal;
344                 curAnimation.Finished += (object sender, EventArgs args) =>
345                 {
346                     curTop.SetVisible(false);
347
348                     //Invoke Page events
349                     curTop.InvokeDisappeared();
350                 };
351                 curAnimation.Play();
352
353                 page.Opacity = 0.0f;
354                 page.SetVisible(true);
355                 newAnimation = new Animation(1000);
356                 newAnimation.AnimateTo(page, "Opacity", 1.0f, 0, 1000);
357                 newAnimation.EndAction = Animation.EndActions.StopFinal;
358                 newAnimation.Finished += (object sender, EventArgs e) =>
359                 {
360                     //Invoke Page events
361                     page.InvokeAppeared();
362                     NotifyAccessibilityStatesChangeOfPages(curTop, page);
363                 };
364                 newAnimation.Play();
365             }
366         }
367
368         /// <summary>
369         /// Pops the top page from Navigator.
370         /// </summary>
371         /// <returns>The popped page.</returns>
372         /// <exception cref="InvalidOperationException">Thrown when there is no page in Navigator.</exception>
373         /// <since_tizen> 9 </since_tizen>
374         public Page Pop()
375         {
376             if (!transitionFinished)
377             {
378                 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
379                 return null;
380             }
381
382             if (navigationPages.Count == 0)
383             {
384                 throw new InvalidOperationException("There is no page in Navigator.");
385             }
386
387             var curTop = Peek();
388
389             if (navigationPages.Count == 1)
390             {
391                 Remove(curTop);
392
393                 //Invoke Popped event
394                 Popped?.Invoke(this, new PoppedEventArgs() { Page = curTop });
395
396                 return curTop;
397             }
398
399             var newTop = navigationPages[navigationPages.Count - 2];
400
401             //Invoke Page events
402             newTop.InvokeAppearing();
403             curTop.InvokeDisappearing();
404
405             //TODO: The following transition codes will be replaced with view transition.
406             InitializeAnimation();
407
408             if (curTop is DialogPage == false)
409             {
410                 curAnimation = new Animation(1000);
411                 curAnimation.AnimateTo(curTop, "Opacity", 0.0f, 0, 1000);
412                 curAnimation.EndAction = Animation.EndActions.StopFinal;
413                 curAnimation.Finished += (object sender, EventArgs e) =>
414                 {
415                     //Removes the current top page after transition is finished.
416                     Remove(curTop);
417                     curTop.Opacity = 1.0f;
418
419                     //Invoke Page events
420                     curTop.InvokeDisappeared();
421
422                     //Invoke Popped event
423                     Popped?.Invoke(this, new PoppedEventArgs() { Page = curTop });
424                 };
425                 curAnimation.Play();
426
427                 newTop.Opacity = 1.0f;
428                 newTop.SetVisible(true);
429                 newAnimation = new Animation(1000);
430                 newAnimation.AnimateTo(newTop, "Opacity", 1.0f, 0, 1000);
431                 newAnimation.EndAction = Animation.EndActions.StopFinal;
432                 newAnimation.Finished += (object sender, EventArgs e) =>
433                 {
434                     //Invoke Page events
435                     newTop.InvokeAppeared();
436                     NotifyAccessibilityStatesChangeOfPages(curTop, newTop);
437                 };
438                 newAnimation.Play();
439             }
440             else
441             {
442                 Remove(curTop);
443             }
444
445             return curTop;
446         }
447
448         /// <summary>
449         /// Returns the page of the given index in Navigator.
450         /// The indices of pages in Navigator are basically the order of pushing or inserting to Navigator.
451         /// So a page's index in Navigator can be changed whenever push/insert or pop/remove occurs.
452         /// </summary>
453         /// <param name="index">The index of a page in Navigator.</param>
454         /// <returns>The page of the given index in Navigator.</returns>
455         /// <exception cref="ArgumentOutOfRangeException">Thrown when the argument index is less than 0, or greater than the number of pages.</exception>
456         public Page GetPage(int index)
457         {
458             if ((index < 0) || (index > navigationPages.Count))
459             {
460                 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than or equal to the number of pages.");
461             }
462
463             return navigationPages[index];
464         }
465
466         /// <summary>
467         /// Returns the current index of the given page in Navigator.
468         /// The indices of pages in Navigator are basically the order of pushing or inserting to Navigator.
469         /// So a page's index in Navigator can be changed whenever push/insert or pop/remove occurs.
470         /// </summary>
471         /// <param name="page">The page in Navigator.</param>
472         /// <returns>The index of the given page in Navigator. If the given page is not in the Navigator, then -1 is returned.</returns>
473         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
474         /// <since_tizen> 9 </since_tizen>
475         public int IndexOf(Page page)
476         {
477             if (page == null)
478             {
479                 throw new ArgumentNullException(nameof(page), "page should not be null.");
480             }
481
482             for (int i = 0; i < navigationPages.Count; i++)
483             {
484                 if (navigationPages[i] == page)
485                 {
486                     return i;
487                 }
488             }
489
490             return -1;
491         }
492
493         /// <summary>
494         /// Inserts a page at the specified index of Navigator.
495         /// The indices of pages in Navigator are basically the order of pushing or inserting to Navigator.
496         /// So a page's index in Navigator can be changed whenever push/insert or pop/remove occurs.
497         /// To find the current index of a page in Navigator, please use IndexOf(page).
498         /// If the page is already in Navigator, then it is not inserted.
499         /// </summary>
500         /// <param name="index">The index of a page in Navigator where the page will be inserted.</param>
501         /// <param name="page">The page to insert to Navigator.</param>
502         /// <exception cref="ArgumentOutOfRangeException">Thrown when the argument index is less than 0, or greater than the number of pages.</exception>
503         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
504         /// <since_tizen> 9 </since_tizen>
505         public void Insert(int index, Page page)
506         {
507             if ((index < 0) || (index > navigationPages.Count))
508             {
509                 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than or equal to the number of pages.");
510             }
511
512             if (page == null)
513             {
514                 throw new ArgumentNullException(nameof(page), "page should not be null.");
515             }
516
517             //Duplicate page is not pushed.
518             if (navigationPages.Contains(page)) return;
519
520             //TODO: The following transition codes will be replaced with view transition.
521             InitializeAnimation();
522
523             if (index == PageCount)
524             {
525                 page.Opacity = 1.0f;
526                 page.SetVisible(true);
527             }
528             else
529             {
530                 page.SetVisible(false);
531                 page.Opacity = 0.0f;
532             }
533
534             navigationPages.Insert(index, page);
535             Add(page);
536             page.Navigator = this;
537         }
538
539         /// <summary>
540         /// Inserts a page to Navigator before an existing page.
541         /// If the page is already in Navigator, then it is not inserted.
542         /// </summary>
543         /// <param name="before">The existing page, before which a page will be inserted.</param>
544         /// <param name="page">The page to insert to Navigator.</param>
545         /// <exception cref="ArgumentNullException">Thrown when the argument before is null.</exception>
546         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
547         /// <exception cref="ArgumentException">Thrown when the argument before does not exist in Navigator.</exception>
548         /// <since_tizen> 9 </since_tizen>
549         public void InsertBefore(Page before, Page page)
550         {
551             if (before == null)
552             {
553                 throw new ArgumentNullException(nameof(before), "before should not be null.");
554             }
555
556             if (page == null)
557             {
558                 throw new ArgumentNullException(nameof(page), "page should not be null.");
559             }
560
561             //Find the index of before page.
562             int beforeIndex = navigationPages.FindIndex(x => x == before);
563
564             //before does not exist in Navigator.
565             if (beforeIndex == -1)
566             {
567                 throw new ArgumentException("before does not exist in Navigator.", nameof(before));
568             }
569
570             Insert(beforeIndex, page);
571         }
572
573         /// <summary>
574         /// Removes a page from Navigator.
575         /// </summary>
576         /// <param name="page">The page to remove from Navigator.</param>
577         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
578         /// <since_tizen> 9 </since_tizen>
579         public void Remove(Page page)
580         {
581             if (page == null)
582             {
583                 throw new ArgumentNullException(nameof(page), "page should not be null.");
584             }
585
586             //TODO: The following transition codes will be replaced with view transition.
587             InitializeAnimation();
588
589             if ((page == Peek()) && (PageCount >= 2))
590             {
591                 navigationPages[PageCount - 2].Opacity = 1.0f;
592                 navigationPages[PageCount - 2].SetVisible(true);
593             }
594
595             page.Navigator = null;
596             navigationPages.Remove(page);
597             base.Remove(page);
598         }
599
600         /// <summary>
601         /// Removes a page at the specified index of Navigator.
602         /// The indices of pages in Navigator are basically the order of pushing or inserting to Navigator.
603         /// So a page's index in Navigator can be changed whenever push/insert or pop/remove occurs.
604         /// To find the current index of a page in Navigator, please use IndexOf(page).
605         /// </summary>
606         /// <param name="index">The index of a page in Navigator where the page will be removed.</param>
607         /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is less than 0, or greater than or equal to the number of pages.</exception>
608         /// <since_tizen> 9 </since_tizen>
609         public void RemoveAt(int index)
610         {
611             if ((index < 0) || (index >= navigationPages.Count))
612             {
613                 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than the number of pages.");
614             }
615
616             Remove(navigationPages[index]);
617         }
618
619         /// <summary>
620         /// Returns the page at the top of Navigator.
621         /// </summary>
622         /// <returns>The page at the top of Navigator.</returns>
623         /// <since_tizen> 9 </since_tizen>
624         public Page Peek()
625         {
626             if (navigationPages.Count == 0) return null;
627
628             return navigationPages[navigationPages.Count - 1];
629         }
630
631         /// <summary>
632         /// Disposes Navigator and all children on it.
633         /// </summary>
634         /// <param name="type">Dispose type.</param>
635         [EditorBrowsable(EditorBrowsableState.Never)]
636         protected override void Dispose(DisposeTypes type)
637         {
638             if (disposed)
639             {
640                 return;
641             }
642
643             if (type == DisposeTypes.Explicit)
644             {
645                 foreach (Page page in navigationPages)
646                 {
647                     Utility.Dispose(page);
648                 }
649                 navigationPages.Clear();
650
651                 Window window;
652
653                 if (navigatorWindow.TryGetValue(this, out window) == true)
654                 {
655                     navigatorWindow.Remove(this);
656                     windowNavigator.Remove(window);
657                 }
658             }
659
660             base.Dispose(type);
661         }
662
663         /// <summary>
664         /// Returns the default navigator of the given window.
665         /// </summary>
666         /// <returns>The default navigator of the given window.</returns>
667         /// <exception cref="ArgumentNullException">Thrown when the argument window is null.</exception>
668         /// <since_tizen> 9 </since_tizen>
669         public static Navigator GetDefaultNavigator(Window window)
670         {
671             if (window == null)
672             {
673                 throw new ArgumentNullException(nameof(window), "window should not be null.");
674             }
675
676             if (windowNavigator.ContainsKey(window) == true)
677             {
678                 return windowNavigator[window];
679             }
680
681             var defaultNavigator = new Navigator();
682             defaultNavigator.WidthResizePolicy = ResizePolicyType.FillToParent;
683             defaultNavigator.HeightResizePolicy = ResizePolicyType.FillToParent;
684             window.Add(defaultNavigator);
685             windowNavigator.Add(window, defaultNavigator);
686             navigatorWindow.Add(defaultNavigator, window);
687
688             return defaultNavigator;
689         }
690
691         /// <summary>
692         /// Create Transitions between currentTopPage and newTopPage
693         /// </summary>
694         /// <param name="currentTopPage">The top page of Navigator.</param>
695         /// <param name="newTopPage">The new top page after transition.</param>
696         /// <param name="pushTransition">True if this transition is for push new page</param>
697         private TransitionSet CreateTransitions(Page currentTopPage, Page newTopPage, bool pushTransition)
698         {
699             currentTopPage.SetVisible(true);
700             newTopPage.SetVisible(true);
701
702             List<View> taggedViewsInNewTopPage = new List<View>();
703             RetrieveTaggedViews(taggedViewsInNewTopPage, newTopPage, true);
704             List<View> taggedViewsInCurrentTopPage = new List<View>();
705             RetrieveTaggedViews(taggedViewsInCurrentTopPage, currentTopPage, true);
706
707             List<KeyValuePair<View, View>> sameTaggedViewPair = new List<KeyValuePair<View, View>>();
708             foreach(View currentTopPageView in taggedViewsInCurrentTopPage)
709             {
710                 bool findPair = false;
711                 foreach(View newTopPageView in taggedViewsInNewTopPage)
712                 {
713                     if((currentTopPageView.TransitionOptions != null) && (newTopPageView.TransitionOptions != null) &&
714                         currentTopPageView.TransitionOptions?.TransitionTag == newTopPageView.TransitionOptions?.TransitionTag)
715                     {
716                         sameTaggedViewPair.Add(new KeyValuePair<View, View>(currentTopPageView, newTopPageView));
717                         findPair = true;
718                         break;
719                     }
720                 }
721                 if(findPair)
722                 {
723                     taggedViewsInNewTopPage.Remove(sameTaggedViewPair[sameTaggedViewPair.Count - 1].Value);
724                 }
725             }
726             foreach(KeyValuePair<View, View> pair in sameTaggedViewPair)
727             {
728                 taggedViewsInCurrentTopPage.Remove(pair.Key);
729             }
730
731             TransitionSet newTransitionSet = new TransitionSet();
732             foreach(KeyValuePair<View, View> pair in sameTaggedViewPair)
733             {
734                 TransitionItem pairTransition = transition.CreateTransition(pair.Key, pair.Value, pushTransition);
735                 if(pair.Value.TransitionOptions?.TransitionWithChild ?? false)
736                 {
737                     pairTransition.TransitionWithChild = true;
738                 }
739                 newTransitionSet.AddTransition(pairTransition);
740             }
741
742             newTransitionSet.Finished += (object sender, EventArgs e) =>
743             {
744                 if(newTopPage.Layout != null)
745                 {
746                     newTopPage.Layout.RequestLayout();
747                 }
748                 if(currentTopPage.Layout != null)
749                 {
750                     currentTopPage.Layout.RequestLayout();
751                 }
752                 transitionFinished = true;
753                 InvokeTransitionFinished();
754                 transitionSet.Dispose();
755                 currentTopPage.Opacity = 1.0f;
756             };
757
758             if (!pushTransition || newTopPage is DialogPage == false)
759             {
760                 View transitionView = (currentTopPage is ContentPage) ? (currentTopPage as ContentPage).Content : (currentTopPage as DialogPage).Content;
761                 if (currentTopPage.DisappearingTransition != null && transitionView != null)
762                 {
763                     TransitionItemBase disappearingTransition = currentTopPage.DisappearingTransition.CreateTransition(transitionView, false);
764                     disappearingTransition.TransitionWithChild = true;
765                     newTransitionSet.AddTransition(disappearingTransition);
766                 }
767                 else
768                 {
769                     currentTopPage.SetVisible(false);
770                 }
771             }
772             if (pushTransition || currentTopPage is DialogPage == false)
773             {
774                 View transitionView = (newTopPage is ContentPage) ? (newTopPage as ContentPage).Content : (newTopPage as DialogPage).Content;
775                 if (newTopPage.AppearingTransition != null && transitionView != null)
776                 {
777                     TransitionItemBase appearingTransition = newTopPage.AppearingTransition.CreateTransition(transitionView, true);
778                     appearingTransition.TransitionWithChild = true;
779                     newTransitionSet.AddTransition(appearingTransition);
780                 }
781             }
782
783             newTransitionSet.Play();
784
785             return newTransitionSet;
786         }
787
788         /// <summary>
789         /// Retrieve Tagged Views in the view tree.
790         /// </summary>
791         /// <param name="taggedViews">Returned tagged view list..</param>
792         /// <param name="view">Root View to get tagged child View.</param>
793         /// <param name="isRoot">Flag to check current View is page or not</param>
794         private void RetrieveTaggedViews(List<View> taggedViews, View view, bool isRoot)
795         {
796             if (!isRoot && view.TransitionOptions != null)
797             {
798                 if (!string.IsNullOrEmpty(view.TransitionOptions?.TransitionTag))
799                 {
800                     taggedViews.Add((view as View));
801                     if (view.TransitionOptions.TransitionWithChild)
802                     {
803                         return;
804                     }
805                 }
806
807             }
808
809             foreach (View child in view.Children)
810             {
811                 RetrieveTaggedViews(taggedViews, child, false);
812             }
813         }
814
815         /// <summary>
816         /// Notify accessibility states change of pages.
817         /// </summary>
818         /// <param name="disappearedPage">Disappeared page</param>
819         /// <param name="appearedPage">Appeared page</param>
820         private void NotifyAccessibilityStatesChangeOfPages(Page disappearedPage, Page appearedPage)
821         {
822             if (disappearedPage != null)
823             {
824                 //We can call disappearedPage.NotifyAccessibilityStatesChange
825                 //To reduce accessibility events, we are using currently highlighted view instead
826                 View curHighlightedView = Accessibility.Accessibility.Instance.GetCurrentlyHighlightedView();
827                 if (curHighlightedView != null)
828                 {
829                     curHighlightedView.NotifyAccessibilityStatesChange(AccessibilityStates.Visible | AccessibilityStates.Showing, false);
830                 }
831             }
832
833             if (appearedPage != null)
834             {
835                 appearedPage.NotifyAccessibilityStatesChange(AccessibilityStates.Visible | AccessibilityStates.Showing, false);
836             }
837         }
838
839         internal void InvokeTransitionFinished()
840         {
841             TransitionFinished?.Invoke(this, new EventArgs());
842         }
843
844         //TODO: The following transition codes will be replaced with view transition.
845         private void InitializeAnimation()
846         {
847             if (curAnimation != null)
848             {
849                 curAnimation.Stop();
850                 curAnimation.Clear();
851                 curAnimation = null;
852             }
853
854             if (newAnimation != null)
855             {
856                 newAnimation.Stop();
857                 newAnimation.Clear();
858                 newAnimation = null;
859             }
860         }
861     }
862 }