Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement Camera app style photo strip on WP7?

I'm running into a number of problems creating an effect very similar to the photo strip in the Camera app.

All I want to do is display a row of grids that each have the same dimensions as the screen (whether in portrait or landscape). Already, I had to do something hacky and create dependency properties that the grids width and height properties bind to to maintain the aspect ratio.

And this works fine. But when I create a StackPanel for my strip and implement my navigation (or just zoom back with the z-index transform) I see that my StackPanel can't display larger than the screen dimensions (it's clipped to the size of just one grid). I thought I found a post describing this issue but I can't find it now - please post if you know which post I'm thinking of or if you know more about this limitation.

The only workaround I've found is to use a ScrollViewer, which is absolutely not the behavior I want, but it allows the StackPanel to be wider than the screen.

My real problem is with the ScrollViewer behavior - because I need to hop from grid to grid (just like the photo strip does) instead of freely scrolling, and as far as I can tell the HorizontalOffset is not an animatable property. I can force it to animate by calling ScrollToHorizontalOffset every 15 milliseconds, basically implementing my own easing effect manually. This seems like a huge hack, and the behavior is very glitchy (either I'm not getting the ManipulationCompleted event every time I expect it - at the end of every swipe action - or the built in inertia physics of ScrollViewer is interfering with my effect).

Does anyone know better workarounds for the issues I've run into, or a completely different way to get the experience of the Camera Photo strip in Silverlight?

I have considered using the Pivot control, but it isn't quite what I want (if I wanted each item to animate out completely before the next one comes in, instead of appearing to be all attached to one strip, there should be less constraining ways to achieve that). More importantly, the strip is just one of many effects I want to be able to do dynamically. I'd like to alternately have a CoolIris-like 3d-tilt, or a FlipPad style page turn. I believe if I could get my current setup working nicely it would be easy to implement these other effects (as themable transitions). Committing to a control like Pivot won't get me any closer to that vision.

Here is my XAML:

<Grid x:Name="LayoutRoot" Background="Transparent" Height="{Binding RealHeight, ElementName=phoneApplicationPage}" Width="{Binding RealWidth, ElementName=phoneApplicationPage}" HorizontalAlignment="Left" VerticalAlignment="Top">
        <ScrollViewer x:Name="SlideScroller" VerticalScrollBarVisibility="Disabled" Height="{Binding RealHeight, ElementName=phoneApplicationPage}" Margin="0,0,0,-31" ScrollViewer.HorizontalScrollBarVisibility="Auto" HorizontalAlignment="Left" VerticalAlignment="Top">
            <StackPanel x:Name="SlidePanel" Orientation="Horizontal" Height="{Binding RealHeight, ElementName=phoneApplicationPage}" VerticalAlignment="Top" HorizontalAlignment="Left">    
                <Grid x:Name="Slide0" Margin="0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="{Binding RealWidth, ElementName=phoneApplicationPage}" Height="{Binding RealHeight, ElementName=phoneApplicationPage}" Background="#FFCCCCCC">
                    <Image x:Name="Photo0" Width="{Binding RealWidth, ElementName=phoneApplicationPage}" Height="{Binding RealHeight, ElementName=phoneApplicationPage}" VerticalAlignment="Top" HorizontalAlignment="Left" Stretch="UniformToFill"/>
                </Grid>
                <Grid x:Name="Slide1" Margin="0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="{Binding RealWidth, ElementName=phoneApplicationPage}" Height="{Binding RealHeight, ElementName=phoneApplicationPage}" Background="#FFCCCCCC">
                    <Image x:Name="Photo1" Width="{Binding RealWidth, ElementName=phoneApplicationPage}" Height="{Binding RealHeight, ElementName=phoneApplicationPage}" VerticalAlignment="Top" HorizontalAlignment="Left" Stretch="UniformToFill"/>
                </Grid>
                <Grid x:Name="Slide2" Margin="0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="{Binding RealWidth, ElementName=phoneApplicationPage}" Height="{Binding RealHeight, ElementName=phoneApplicationPage}" Background="#FFCCCCCC">
                    <Image x:Name="Photo2" Width="{Binding RealWidth, ElementName=phoneApplicationPage}" Height="{Binding RealHeight, ElementName=phoneApplicationPage}" VerticalAlignment="Top" HorizontalAlignment="Left" Stretch="UniformToFill"/>
                </Grid>
            </StackPanel>    
        </ScrollViewer>
    </Grid>
like image 993
Subcreation Avatar asked Jul 16 '11 20:07

Subcreation


1 Answers

It turns out the setup I described works pretty well if I just prevent the ScrollViewer from getting manipulated directly by the user and position it manually. This eliminates the physics effects that were causing most of the glitchiness I mentioned.

XAML

<ScrollViewer x:Name="SlideScroller" VerticalScrollBarVisibility="Disabled" Height="{Binding RealHeight, ElementName=phoneApplicationPage}" ScrollViewer.HorizontalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Visible" HorizontalAlignment="Left" VerticalAlignment="Top">
            <StackPanel x:Name="SlidePanel" Orientation="Horizontal" Height="{Binding RealHeight, ElementName=phoneApplicationPage}" VerticalAlignment="Top" HorizontalAlignment="Left">
            </StackPanel>
        </ScrollViewer>
<Rectangle x:Name="ScrollInterceptRect" Margin="0,0,0,-31" Width="{Binding RealWidth, ElementName=phoneApplicationPage}" Height="{Binding RealHeight, ElementName=phoneApplicationPage}" HorizontalAlignment="Left" VerticalAlignment="Top">

Codebehind

    public MainPage()
    {
        InitializeComponent();

        ScrollInterceptRect.MouseLeftButtonUp += new MouseButtonEventHandler(ScrollInterceptRect_MouseLeftButtonUp);
        ScrollInterceptRect.MouseLeftButtonDown += new MouseButtonEventHandler(ScrollInterceptRect_MouseLeftButtonDown);
        ScrollInterceptRect.MouseMove += new MouseEventHandler(ScrollInterceptRect_MouseMove);
    }
    //...
    NavigationIndices navigationIndices = new NavigationIndices();
    readonly double swipeThreshold = 80.0;
    SwipeDirection swipeDirection;
    bool tapCancelled;
    Point swipeDelta;
    Point swipeStartPosition;
    double startScrollOffsetX;
    void SlideScroller_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        swipeStartPosition = e.GetPosition(this);
        startScrollOffsetX = SlideScroller.HorizontalOffset;
    }
    void ScrollInterceptRect_MouseMove(object sender, MouseEventArgs e)
    {
        Point touchPosition = e.GetPosition(this);
        swipeDelta = new Point() { X = swipeStartPosition.X - touchPosition.X, Y = swipeStartPosition.Y - touchPosition.Y };
        SlideScroller.ScrollToHorizontalOffset(startScrollOffsetX + swipeDelta.X);
        // swipe right
        if (swipeDelta.X > swipeThreshold)
        {
            swipeDirection = SwipeDirection.Left;
            tapCancelled = true;
        }
        // swipe left
        else if (swipeDelta.X < -swipeThreshold)
        {
            swipeDirection = SwipeDirection.Right;
            tapCancelled = true;
        }
    }
    void ScrollInterceptRect_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (swipeDirection == SwipeDirection.Left && navigationIndices.X < photos.Count - 1 && photos[navigationIndices.X] != null)
        {
            navigationIndices.X++;
        }
        // only go back when you aren't already at the beginning
        else if (swipeDirection == SwipeDirection.Right && navigationIndices.X > 0)
        {
            navigationIndices.X--;
        }
        if (!tapCancelled)
        {
            // handle tap
        }
        else
        {
            animateScrollViewerToCurrentPhoto();
        }
    }

This is simplified a bit for clarity (I also use vertical swipe for something in my app and I omitted how I'm animating the ScrollViewer - probably worthy of it's own post).

I'd love to hear any improvements you can offer to this, or suggestions on better ways to implement it altogether. Perhaps extending the Panel Class or as a custom Behavior. Thoughts?

like image 180
Subcreation Avatar answered Nov 16 '22 20:11

Subcreation