Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

[WP7] Create a panoramic view using Silverlight

You’ve probably already seen MIX10 videos of Windows Phone, showcasing panoramic effects where each page appears as part of a bigger image, and showing on the right of the screen a small piece of the next page as a teaser.

 

img_203332_win_mob_7_1

 

This is a pretty nice effect, but unfortunately the current version of the WP7 SDK doesn’t provide out of box the necessary controls to reproduce this effect. These controls will probably be added in the next releases, but trying to create them ourselves would prove to be a nice training.

 

Window layout

 

First step : define the layout of the main window. We’re going to use the following design:

Layout

 

We will not put the whole panoramic scene in the page: while easier, this would lead to obvious performance problems (never forget, we’re dealing with a cell phone here). Instead, we’re going to use a simple grid with three columns. Each column will be as wide as the screen, minus a few pixels to show a bit of the next column. The standard WP7 screen resolution being 800*480, the columns will be 400 pixels wide.

The grid’s XAML code should looks like:

   1: <Grid x:Name="PanoramicGrid" Grid.Row="1" Grid.Column="0" >
   2:             <Grid.RenderTransform>
   3:                 <TranslateTransform x:Name="PanoramaContentTranslate" X="-400" Y="0" />
   4:             </Grid.RenderTransform>
   5:             <Grid.RowDefinitions>
   6:                 <RowDefinition />
   7:             </Grid.RowDefinitions>
   8:  
   9:             <Grid.ColumnDefinitions>
  10:                 <ColumnDefinition Width="400" />
  11:                 <ColumnDefinition Width="400" />
  12:                 <ColumnDefinition Width="400" />
  13:             </Grid.ColumnDefinitions>
  14:  
  15:         </Grid>

We’re using a TranslateTransform rather than margins to center the grid. This way, it will be easier to ‘slide’ it when switching page.

We also add a few usercontrols to the solution. Each of them is 400 pixel wide, and will act as a page of the panorama. For this example I’ve added three of them, called WindowsPhoneControl1, WindowsPhoneControl2, and WindowsPhoneControl3. I’m not showing their code since there is nothing of interest here.

 

Controls initialization

Now that the grid is created, we still have to put some content in it. We’ll start by adding a few properties and initialize our controls in the code-behind file :

   1: /// <summary>
   2: /// Size of the pages
   3: /// </summary>
   4: public const int PageWidth = 400;
   5:  
   6: public MainPage()
   7: {
   8:     this.PageList = new List<UserControl>() 
   9:         { 
  10:             new WindowsPhoneControl1() { IsEnabled = false }, 
  11:             new WindowsPhoneControl2() { IsEnabled = false }, 
  12:             new WindowsPhoneControl3() { IsEnabled = false } 
  13:         };
  14:  
  15:     this.CurrentPageIndex = 0;
  16:  
  17:     InitializeComponent();
  18:  
  19:     SupportedOrientations = SupportedPageOrientation.Portrait;
  20: }
  21:  
  22: /// <summary>
  23: /// Ordered list of the panorama pages
  24: /// </summary>
  25: protected List<UserControl> PageList { get; set; }
  26:  
  27: /// <summary>
  28: /// Index of the page currently displayed
  29: /// </summary>
  30: protected int CurrentPageIndex { get; set; }

PageWidth is the size of the grid columns. This way, it will be easier to customize it with minimal impact on the code.
PageList contains the list of the usercontrols, acting as the pages of the panorama, dans l’ordre d’affichage de gauche à droite. The IsEnabled property is set to false, to avoid user interaction.

For the sake of simplicity, only the portrait orientation will be supported..

Now we add a few more lines of code in the Loaded event:

   1: private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
   2: {
   3:     var frame = (PhoneApplicationFrame)Application.Current.RootVisual;
   4:     frame.Width = PageWidth * 3;
   5:  
   6:     this.LoadPages();
   7: }

 

The objective is to increase the size of the PhoneApplicationFrames, so the whole grid is rendered even if some parts are out of the screen area. It’s necessary to avoid blank areas when sliding from one page to another.

Last but not least, we define a method LoadPage, which put the active pages’ usercontrols in the grid:

   1: private void LoadPages()
   2: {
   3:     this.PanoramicGrid.Children.Clear();
   4:  
   5:     var currentPage = this.PageList[this.CurrentPageIndex];
   6:     currentPage.IsEnabled = true;
   7:  
   8:     this.PanoramicGrid.Children.Add(currentPage);
   9:     Grid.SetColumn(currentPage, 1);
  10:     Grid.SetRow(currentPage, 1);
  11:  
  12:     if (this.PageList.Count > this.CurrentPageIndex + 1)
  13:     {
  14:         var nextPage = this.PageList[this.CurrentPageIndex + 1];
  15:         nextPage.IsEnabled = false;
  16:  
  17:         this.PanoramicGrid.Children.Add(nextPage);
  18:  
  19:         Grid.SetColumn(nextPage, 2);
  20:         Grid.SetRow(nextPage, 1);
  21:     }
  22:  
  23:     if (this.CurrentPageIndex > 0)
  24:     {
  25:         var previousPage = this.PageList[this.CurrentPageIndex - 1];
  26:         previousPage.IsEnabled = false;
  27:  
  28:         this.PanoramicGrid.Children.Add(previousPage);
  29:  
  30:         Grid.SetColumn(previousPage, 0);
  31:         Grid.SetRow(previousPage, 1);
  32:     }
  33: }

All the controls are removed from the grid, then those that should be visible from the users are added. The main column will always contain the usercontrol correspoding to the current page. The left and right columns will contain respectively the previous and the next page, if any.

If you try to launch the application at this point, the first page is displayed and you should see a bit of the next page on the right of the screen. Still, there is no way to switch pages.

sample1

 

Switching pages with transitions

Now let’s define what the application is supposed to do: if the user let his finger slide on the screen from left to right, the whole interface should follow his movement, revealing the next page. When the user get his finger off the screen, we start a transition to the next page is the movement has been wide enough. Otherwise, we put everything back to its original position.

Needless to say, the application will have the exact opposite behaviour if the user slides his finger from right to left.

Let’s go back to XAML, to add the transition animation, and to define the manipulation events needed to detect user’s movements:

   1: <UserControl.Resources>
   5:         <Storyboard x:Name="PageChangeAnimation">
   6:             <DoubleAnimation To="-400.0" SpeedRatio="4" Storyboard.TargetName="PanoramaContentTranslate" Storyboard.TargetProperty="X" />
   8:         </Storyboard>
   9:     </UserControl.Resources>
  10:         </StackPanel.RenderTransform>
  11:     </StackPanel>
  12:     
  13:     <Grid x:Name="PanoramicGrid" Grid.Row="1" Grid.Column="0"
  14:                     ManipulationDelta="PhoneApplicationPage_ManipulationDelta"
  15:                     ManipulationCompleted="PhoneApplicationPage_ManipulationCompleted" >
  16:         <Grid.RenderTransform>
  17:             <TranslateTransform x:Name="PanoramaContentTranslate" X="-400" Y="0" />
  18:         </Grid.RenderTransform>
  19:         <Grid.RowDefinitions>
  20:             <RowDefinition />
  21:         </Grid.RowDefinitions>
  22:  
  23:         <Grid.ColumnDefinitions>
  24:             <ColumnDefinition Width="400" />
  25:             <ColumnDefinition Width="400" />
  26:             <ColumnDefinition Width="400" />
  27:         </Grid.ColumnDefinitions>
  28:  
  29:     </Grid>

 

The ManipulationDelta event is triggered multiple times while the user is dragging his finger on the screen. We will use it to move the interface accordingly. The ManipulationCompleted event is triggered when the user puts his finger off the screen.

Let’s start with the ManipulationDelta event:

   1: private void PhoneApplicationPage_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
   2:         {
   3:             if (e.OriginalSource is Panel)
   4:             {
   5:                 this.PanoramaContentTranslate.X = e.CumulativeManipulation.Translation.X - PageWidth;
   8:             }
   9:         }

 

e.CumulativeManipulation.Translation contains the magnitude of the finger’s movement. We will simply move the grid from the same amount of pixels, using the previously defined TranslateTransform. Don’t forget to substract the PageWidth value, that we used to center the grid.

Note that we do handle only the events coming from a container control (inheriting Panel). This way, we ensure that the whole interface won’t be moving while the user is trying to move a slider or select some text.

 

Now the ManipulationCompleted event:

   1: private void PhoneApplicationPage_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
   2:         {
   3:             if (e.OriginalSource is Panel)
   4:             {
   5:                 if (e.TotalManipulation.Translation.X < 0)
   6:                 {
   7:                     if (e.TotalManipulation.Translation.X > -180 || this.CurrentPageIndex >= this.PageList.Count - 1)
   8:                     {
   9:                         this.PageChangeAnimation.Begin();
  10:                     }
  11:                     else
  12:                     {
  13:                         this.ChangePage(1);
  14:                     }
  15:                 }
  16:                 else if (e.TotalManipulation.Translation.X > 0)
  17:                 {
  18:                     if (e.TotalManipulation.Translation.X < 180 || this.CurrentPageIndex <= 0)
  19:                     {
  20:                         this.PageChangeAnimation.Begin();
  21:                     }
  22:                     else
  23:                     {
  24:                         this.ChangePage(-1);
  25:                     }
  26:                 }
  27:             }
  28:         }

We start by determining the movement orientation (right of left) with e.TotalManipulation.Translation.X. From here, we check is the movement is wider than 180 pixels (arbitrary value). If it is, we call the ChangePage method which will change the active page. Otherwise, we consider is as a manipulation error from the user, and we start the PageChangeAnimation to put back the controls to their original position.
Don’t forget, before changing the active page, to check whether there is a next or previous page.

Now let’s focus on the ChangePage method:

   1: private void ChangePage(int step)
   2: {
   3:     this.CurrentPageIndex += step;
   4:  
   5:     this.LoadPages();
   6:  
   7:     this.PanoramaContentTranslate.X += PageWidth * step;
   8:  
   9:     this.PageChangeAnimation.Begin();
  10: }

This one is a bit tricky. The grid having only three columns, we have to make sure that the active page is always in the main column, so we can make transitions to the right or the left. This means we’ll have at some time to put the next page in the main column, so she can replace the active page.

To do that, we start by changing the current page index, and calling the LoadPages method. This method will put the next page in the main column, the old active page in the left column, and a new page in the right column. Now we have a problem: since we moved the grid in the ManipulationDelta event, the user is now facing the new next page instead of the new active page. To correct that, we move the grid 400 pixels to the right. Now the user is facing the right page, and we can start the PageChangeAnimation to finish the transition.

Up to this point, the application can be used: the panorama’s pages are displayed, and the user can switch the active page. But we are still missing something from the WP7 videos.

 

The remaining part : the title

The WP7 videos show a title spanning on multiple pages, and sliding at a lower pace than the page. The panorama effect isn’t as pretty without it, so we are going to add it.

Since we can’t split the title in several controls, we have to change the layout slightly. We keep our grid with the three columns, but we put it in a parent grid. The parent grid have a line on the top, spanning across the whole panorama (not only the three grid columns), and this line will contain the title:

Layout2

The corresponding XAML :

   1: <UserControl.Resources>
   2:     <Storyboard x:Name="PageChangeAnimation">
   3:         <DoubleAnimation To="-400.0" SpeedRatio="4" Storyboard.TargetName="PanoramaContentTranslate" Storyboard.TargetProperty="X" />
   4:         <DoubleAnimation x:Name="SlideTitleDoubleAnimation" SpeedRatio="4" Storyboard.TargetName="TitleTranslate" Storyboard.TargetProperty="X" />
   5:     </Storyboard>
   6: </UserControl.Resources>
   7:  
   8: <Grid x:Name="LayoutRoot" Background="{StaticResource PhoneBackgroundBrush}">
   9:     <Grid.RowDefinitions>
  10:         <RowDefinition Height="140" />
  11:         <RowDefinition Height="*"/>
  12:     </Grid.RowDefinitions>         
  13:     
  14:     <StackPanel Grid.Row="0" Grid.Column="0" x:Name="TitlePanel">
  15:         <StackPanel.RenderTransform>
  16:             <TranslateTransform x:Name="TitleTranslate" />
  17:         </StackPanel.RenderTransform>
  18:     </StackPanel>
  19:     
  20:     <Grid x:Name="PanoramicGrid" Grid.Row="1" Grid.Column="0"
  21:                     ManipulationDelta="PhoneApplicationPage_ManipulationDelta"
  22:       ManipulationCompleted="PhoneApplicationPage_ManipulationCompleted" >
  23:         <Grid.RenderTransform>
  24:             <TranslateTransform x:Name="PanoramaContentTranslate" X="-400" Y="0" />
  25:         </Grid.RenderTransform>
  26:         <Grid.RowDefinitions>
  27:             <RowDefinition />
  28:         </Grid.RowDefinitions>
  29:  
  30:         <Grid.ColumnDefinitions>
  31:             <ColumnDefinition Width="400" />
  32:             <ColumnDefinition Width="400" />
  33:             <ColumnDefinition Width="400" />
  34:         </Grid.ColumnDefinitions>
  35:  
  36:     </Grid>
  37: </Grid>

A panel is put in the parent grid top line, and will contain the title. We also put a TranslateTransform and we add a DoubleAnimation to the PageChangeAnimation storyboard to move it.

To finish, we create a usercontrol called “PanoramicTitle”, with the same size as the panorama, and we draw the title in it.

We initialize it in the Loaded event :

   1: private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
   2: {
   3:     var frame = (PhoneApplicationFrame)Application.Current.RootVisual;
   4:     frame.Width = PageWidth * 3;
   5:  
   6:     var title = new PanoramicTitle();
   7:  
   8:     this.TitlePanel.Children.Add(title);
   9:  
  10:     this.LoadPages();
  11: }

To make the differential scrolling effect, we’re going to move the title as a pace twice as slow as the pages’. Now we just have to tweak the manipulation event to move the title:

   1: private void PhoneApplicationPage_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
   2: {
   3:     if (e.OriginalSource is Panel)
   4:     {
   5:         if (e.TotalManipulation.Translation.X < 0)
   6:         {
   7:             if (e.TotalManipulation.Translation.X > -180 || this.CurrentPageIndex >= this.PageList.Count - 1)
   8:             {
   9:                 this.SlideTitleDoubleAnimation.To = this.CurrentPageIndex * PageWidth / 2 * -1;
  10:                 this.PageChangeAnimation.Begin();
  11:             }
  12:             else
  13:             {
  14:                 this.ChangePage(1);
  15:             }
  16:         }
  17:         else if (e.TotalManipulation.Translation.X > 0)
  18:         {
  19:             if (e.TotalManipulation.Translation.X < 180 || this.CurrentPageIndex <= 0)
  20:             {
  21:                 this.SlideTitleDoubleAnimation.To = this.CurrentPageIndex * PageWidth / 2 * -1;
  22:                 this.PageChangeAnimation.Begin();
  23:             }
  24:             else
  25:             {
  26:                 this.ChangePage(-1);
  27:             }
  28:         }
  29:     }
  30: }
  31:  
  32: private void ChangePage(int step)
  33: {
  34:     this.CurrentPageIndex += step;
  35:  
  36:     this.LoadPages();
  37:  
  38:     this.PanoramaContentTranslate.X += PageWidth * step;
  39:  
  40:     this.SlideTitleDoubleAnimation.To = this.CurrentPageIndex * PageWidth / 2 * -1;
  41:  
  42:     this.PageChangeAnimation.Begin();
  43: }
  44:  
  45: private void PhoneApplicationPage_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
  46: {
  47:     if (e.OriginalSource is Panel)
  48:     {
  49:         this.PanoramaContentTranslate.X = e.CumulativeManipulation.Translation.X - PageWidth;
  50:  
  51:         this.TitleTranslate.X = e.CumulativeManipulation.Translation.X /2 - (this.CurrentPageIndex * PageWidth / 2);
  52:     }
  53: }

Build the solution, launch it in the emulator, and here is the final product:

 

You can download the sample solution by following this link.

kick it on DotNetKicks.com
Publié mardi 23 mars 2010 20:58 par KooKiz
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires

Pas de commentaires
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Office 365: Comparaison des composants pour préparer votre migration de SharePoint 2007 vers Office 365 par Blog Technique de Romelard Fabrice le il y a 4 heures et 47 minutes

- Créer un périphérique Windows To Go 10 ! par Blog de Jérémy Jeanson le 11-21-2014, 04:54

- RDV à Genève le 12 décembre pour l’évènement “SharePoint–Office 365 : des pratiques pour une meilleure productivité !” par Le blog de Patrick [MVP Office 365] le 11-19-2014, 10:40

- [IIS] Erreurs web personnalisées par Blog de Jérémy Jeanson le 11-19-2014, 00:00

- BDD/TDD + Javascript par Fathi Bellahcene le 11-16-2014, 16:57

- Sécuriser sans stocker de mots de passe par Blog de Jérémy Jeanson le 11-15-2014, 08:58

- Où télécharger la preview de Visual Studio 2015 ? par Blog de Jérémy Jeanson le 11-13-2014, 21:33

- Les cartes sont partout ! par Le blog de Patrick [MVP Office 365] le 11-13-2014, 17:26

- [ #Office365 ] Courrier basse priorité ! par Le blog de Patrick [MVP Office 365] le 11-12-2014, 08:56

- [Oracle] Fichier oranfsodm12.dll absent du package client par Blog de Jérémy Jeanson le 11-10-2014, 20:44