Adding a swipe gesture to open SplitView Pane
Asked Answered
M

2

23

I am trying to add a swipe gesture to the SplitView control (aka "hamburger menu") of UWP, similar to the swipe left/right of a Pivot control. How can I set a gesture to change the Display mode of it?

In iOS 8 and later, I can use UISplitViewController and set presentsWithGesture property to do that but there is not a similar thing in WinRT.

Now after reading this blog: http://blogs.msdn.com/b/cdndevs/archive/2015/07/10/uwp-new-controls-part-2-splitview.aspx, I realized that there is the DisplayMode property in SplitView control and I should use VisualStateManager to change the state of it But how can I use vsm to pan the left Pane in and out? I am not aware that this is achievable with vsm.

Any help/hint would be greatly appreciated.

Mendy answered 20/8, 2015 at 1:55 Comment(0)
K
40

Interesting question! :)

I recently created a SwipeableSplitView which extends the SplitView control to enable a swipe from left edge gesture when the DisplayMode is set to Overlay (as I don't see the point to have it in other modes, but feel free to extend it whenever needed).

All I am doing is, inside the control's style, create another layer on top of the PaneRoot layer and handle all the gestures there.

<Grid x:Name="PaneRoot" ManipulationMode="TranslateX" Grid.ColumnSpan="2" HorizontalAlignment="Left" Background="{TemplateBinding PaneBackground}" Width="{Binding TemplateSettings.OpenPaneLength, RelativeSource={RelativeSource Mode=TemplatedParent}}">
    <Grid.Clip>
        <RectangleGeometry x:Name="PaneClipRectangle">
            <RectangleGeometry.Transform>
                <CompositeTransform x:Name="PaneClipRectangleTransform" />
            </RectangleGeometry.Transform>
        </RectangleGeometry>
    </Grid.Clip>
    <Grid.RenderTransform>
        <CompositeTransform x:Name="PaneTransform" TranslateX="{Binding RenderTransform.TranslateX, ElementName=PanArea}" />
    </Grid.RenderTransform>
    <Border Child="{TemplateBinding Pane}" />
    <Rectangle x:Name="HCPaneBorder" Fill="{ThemeResource SystemControlForegroundTransparentBrush}" HorizontalAlignment="Right" Visibility="Collapsed" Width="1" x:DeferLoadStrategy="Lazy" />
</Grid>

<!--a new layer here to handle all the gestures -->
<Grid x:Name="OverlayRoot" Grid.ColumnSpan="2">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="{Binding TemplateSettings.OpenPaneGridLength, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <!--the actual element for panning, manipulations happen here-->
    <Rectangle x:Name="PanArea" Fill="Transparent" ManipulationMode="TranslateX" Width="{Binding PanAreaThreshold, RelativeSource={RelativeSource Mode=TemplatedParent}}" Grid.Column="1">
        <Rectangle.RenderTransform>
            <CompositeTransform TranslateX="{Binding PanAreaInitialTranslateX, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
        </Rectangle.RenderTransform>
    </Rectangle>
    <!--this is used to dismiss this swipeable pane-->
    <Rectangle x:Name="DismissLayer" Fill="Transparent" Grid.Column="2" />
</Grid>

While updating the TranslateX of the new layer's transform object, I am also updating the PaneRoot's to keep their position in sync.

void OnManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
{
    _panAreaTransform = PanArea.RenderTransform as CompositeTransform;
    _paneRootTransform = PaneRoot.RenderTransform as CompositeTransform;

    if (_panAreaTransform == null || _paneRootTransform == null)
    {
        throw new ArgumentException("Make sure you have copied the default style to Generic.xaml!!");
    }
}

void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    var x = _panAreaTransform.TranslateX + e.Delta.Translation.X;

    // keep the pan within the bountry
    if (x < PanAreaInitialTranslateX || x > 0) return;

    // while we are panning the PanArea on X axis, let's sync the PaneRoot's position X too
    _paneRootTransform.TranslateX = _panAreaTransform.TranslateX = x;
}

void OnManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
{
    var x = e.Velocities.Linear.X;

    // ignore a little bit velocity (+/-0.1)
    if (x <= -0.1)
    {
        CloseSwipeablePane();
    }
    else if (x > -0.1 && x < 0.1)
    {
        if (Math.Abs(_panAreaTransform.TranslateX) > Math.Abs(PanAreaInitialTranslateX) / 2)
        {
            CloseSwipeablePane();
        }
        else
        {
            OpenSwipeablePane();
        }
    }
    else
    {
        OpenSwipeablePane();
    }
}

Keep in mind that because the IsPaneOpen property is not virtual, I have to create another one IsSwipeablePaneOpen to wrap the former around. So whenever you feel like using the IsPaneOpen property, use IsSwipeablePaneOpen instead.

This is how it works in a demo app I created in GitHub. You can find the full source code here.

enter image description here


Credits

  • The SplitView template was generated from Koen Zwikstra's awesome Visual Studio UWP templates.
  • Page animations and some other implementations were inspired by this post from Jerry Nixon.
Kliment answered 20/8, 2015 at 6:20 Comment(12)
I have recently updated the GitHub repo with another cool feature - a IsPanSelectorEnabled property which allows you to select a menu item by panning up/down on the bottom area of the pane. I believe this would help users with large phones since they don't need to stretch their fingers to reach the top area anymore! Live demo: youtube.com/watch?v=K47MHJFe4dQKliment
Hey Justin, on stock Splitview control (overlay mode), do you experience any lag during slide-in animation on actual mobile device (build 10152) when you press the hamburger button?Cornwall
@Saurabh3321, I did when I was using a ListView inside the Pane and then I replaced it with a simpler ListBox. Also I tried publishing it in release mode so it got complied with .NET Native and after all that the performance was better.Kliment
I even tried a bunch of Button and RadioButton instead of ListView but it's still there. Tried release mode as well. I think I should wait for the next build before I start to find ways to fix it.Cornwall
Nice. Now that you have done it, though, it's worth asking if this is a good gesture in the first place. No other app will use it, right?Chadwickchae
@JerryNixon-MSFT I guess it depends. It might not be a good idea to use it with a Pivot or horizontal swipe enabled pages (e.g. swipe left/right to delete/complete items). But I think there should at least be an option for devs to choose whatever is suitable for their apps. :)Kliment
Thanks for your wonderful work and sorry for replying late. After checking your project on github, I found it's exactly reusable and extensible, for example, it's possible to disable the swipe gestures when the ManipulationModel of PaneRoot was set to none and the OverlayRoot is collapsed.Mendy
@JustinXL IMHO this whole swipe-to-open should be implemented the way it is on Android - if swipe event comes from outside the screen, or begins in the leftmost few pixel columns (I think on Android, on HD screens this is 12 or 24px), handle it as a gesture of the drawer/pane, otherwise handle it as generic page gesture. This way you avoid the mix-up with swipe-able tabs.Ailyn
@Ailyn this is why in the source code there's a dp called PanAreaThreshold. You can change it to 12 or 24px for example.Kliment
@Jerry: Yeah, definitely. That said, as someone who used this gesture frequently on iOS prior to switching to WP, I'd love to see it implemented across all apps. (On a related note, it's incredibly maddening that VS doesn't even provide a built-in hamburger control to go with SplitView and you had to roll your own in Template10 - is it any wonder that the hamburger control UX is so horribly inconsistent across even the built-in apps?!)Fidelis
Thanks for this. Should the DismissLayer on column 1 instead of 2? Because only 2 columns in Root grid?Reggie
@JustinXL : Thanks for the awesome work. But I was wondering if we could add other elements to the SplitView.Pane? I've tried adding them but the code keeps on crashing at various places. Suppose if I wanted to add an image etc along with the ListBox. It would be really helpful if you guide me how?Homburg
B
1

Well, vsm is used in making Responsive UI in that blog. To add a swipe gesture in SplitView, here's what I did:

  • Detect gesture on your root panel of SplitView's Content, and add some Manipulatioin involved event handler of it.
  • Handle SplitView's IsPaneOpen property in Manipulation event.
Bradbradan answered 20/8, 2015 at 2:35 Comment(2)
This is a demo I made months ago to demonstrate this feature. It's a Windows Phone 8.1 project and of course I didn't use SplitView to implement the feature. Instead, the demo contains a Pivot control with some PivotItems and a Hamburger menu I made.Bradbradan
Hey Jerry! @JerryNixon-MSFT You should check out my answer. :)Kliment

© 2022 - 2024 — McMap. All rights reserved.