How to make a swipe selector

I’m trying to make a selector that has multiple entries in it, and I can swipe through to select these entries, like the native iOS selector.

I found the perfect example: https://dribbble.com/shots/2378825-Select-Weight

I thought of using linear navigation but that would stop at the exact entry that the users finger is lifted, instead, if the swipe is fast enough it should keep going.

Also, thinking about this, in a selector for soemthing like weight (like the one above), there can easily be 100 - 1000 entries, is there a correct way to do this without wearing down performance?

Cheers

1 Like

Hey!

I think you could achieve this using a ScrollView + ScrollingAnimation as long as the length of the list is static.

Otherwise i think you might be better off trying to implement this with some Uno code.

I think that you in this case want something in between a scroller and a pagecontrol effect, where you can flick it to move accross multiple element but still snap in the end?

I hope this puts you on the right track :slight_smile: Let me know it not.

I gave this a try and got pretty close to the desired effect, however, my limited experience with Uno doesn’t allow for fiddling around with Visuals and their position.

The main problem is that there is no point of reference in JS, which would tell us exactly how far the ScrollView has travelled - a relative ScrollPosition in % would be the closest to what we need.

In addition to that, I can see how having this point of reference would have the potential to have a negative impact on app performance - having a realtime-updated variable as one scrolls must be expensive.

Here’s to hoping @mortoray is getting ready with ScrollView-related positioning features :slight_smile:

UX:

<App>
    <JavaScript>
    var Observable = require('FuseJS/Observable');
    var position = Observable(0);
    function scrolled(args) {
        console.log('scrolling motion done');
    };

    module.exports = {
        'position': position,
        'scrolled': scrolled
    };
    </JavaScript>

    <ClientPanel Background="#eee">
        <StackPanel Alignment="VerticalCenter" Margin="10,0,10,0">

            <WhileFalse ux:Name="IsScrolling" Value="false">
                <Callback Handler="{scrolled}" />
            </WhileFalse>

            <Text Value="{position}" FontSize="22" TextColor="#333" Alignment="Center" Margin="20" />

            <ScrollView ux:Name="_scrollView" AllowedScrollDirections="Horizontal">

                <ScrollingAnimation>
                    <Move X="0.95" RelativeNode="_scrollView" RelativeTo="Size" Target="_scrollIndicator" />
                </ScrollingAnimation>

                <WhileScrolling>
                    <Change IsScrolling.Value="true" />
                </WhileScrolling>

                <StackPanel Orientation="Horizontal">
                    <Each Count="30">
                        <StackPanel Orientation="Horizontal" Height="100">
                            <Panel Background="#aaa" Width="2" Height="80" Alignment="Bottom" Margin="5,0,5,0" />
                            <StackPanel Orientation="Horizontal">
                                <Each Count="4">
                                    <Panel Background="#ddd" Width="2" Height="50" Alignment="Bottom" Margin="5,0,5,10" />
                                </Each>
                            </StackPanel>
                        </StackPanel>
                    </Each>
                </StackPanel>

            </ScrollView>

            <Panel>
                <Rectangle Width="5%" Height="8" Margin="0,2,0,2" CornerRadius="10" Alignment="TopLeft" ux:Name="_scrollIndicator">
                    <SolidColor Color="#44444488" />
                </Rectangle>
            </Panel>

        </StackPanel>
    </ClientPanel>
</App>

.unoproj:

{
  "Packages": [
    "Fuse",
    "FuseJS",
    "Fuse.Scripting"
  ],
  "Includes": [
    "*"
  ]
}

and WhileScrolling.uno (taken from here: https://www.fusetools.com/community/forums/permalink/e2c6d45a-d8b7-4e74-beed-7dc4e5ab4e46):

using Uno;
using Fuse;
using Fuse.Triggers;
using Fuse.Controls;

public class WhileScrolling : WhileTrigger
{

    ScrollView _scrollView;

    protected override void OnRooted()
    {
        //base.OnRooted(Parent);
        if (Parent is ScrollView)
        {
            _scrollView = (ScrollView)Parent;
            _scrollView.ScrollPositionChanged += OnScrollPositionChanged;
        }
    }

    protected override void OnUnrooted()
    {
        //base.OnUnrooted(Parent);
        if (_scrollView != null)
        {
            _scrollView.ScrollPositionChanged -= OnScrollPositionChanged;
            _scrollView = null;
        }
    }

    bool _isActive = false;
    int _prevFrameIndex = 0;

    void OnScrollPositionChanged(object sender, EventArgs args)
    {
        if (!_isActive)
        {
            _isActive = true;
            SetActive(true);
            UpdateManager.AddAction(OnUpdate);
        }
        _prevFrameIndex = UpdateManager.FrameIndex;
    }

    void OnUpdate()
    {
        if (_prevFrameIndex < UpdateManager.FrameIndex)
        {
            SetActive(false);
            _isActive = false;
            UpdateManager.RemoveAction(OnUpdate);
        }
    }

}

Indeed. A ScrollingAnimation going through JS is not really an option. We do have a bunch of tickets for improving the ScrollView with more Animators and such, but i’m not sure when they will be implemented yet.

I’m also hoping well be able to fill in the uno documentation with more details so these kinds of tasks become easier for the community to be a part of.

Building on Uldis’ example:

WhileScrolling.uno

using Uno;
using Fuse;
using Fuse.Triggers;
using Fuse.Controls;

public class WhileScrolling : WhileTrigger
{

    ScrollView _scrollView;

    protected override void OnRooted()
    {
        //base.OnRooted(Parent);
        if (Parent is ScrollView)
        {
            _scrollView = (ScrollView)Parent;
            _scrollView.ScrollPositionChanged += OnScrollPositionChanged;
        }
    }

    protected override void OnUnrooted()
    {
        //base.OnUnrooted(Parent);
        if (_scrollView != null)
        {
            _scrollView.ScrollPositionChanged -= OnScrollPositionChanged;
            _scrollView = null;
        }
    }

    bool _isActive = false;
    int _prevFrameIndex = 0;

    void OnScrollPositionChanged(object sender, EventArgs args)
    {
        if (!_isActive)
        {
            _isActive = true;
            SetActive(true);
            UpdateManager.AddAction(OnUpdate);
        }
        _prevFrameIndex = UpdateManager.FrameIndex;
    }

    void OnUpdate()
    {
        if (_prevFrameIndex < UpdateManager.FrameIndex)
        {
            SetActive(false);
            _isActive = false;
            UpdateManager.RemoveAction(OnUpdate);
        }
    }

}

MainView.ux

<App>
    <JavaScript>
    var Observable = require('FuseJS/Observable');
    var position = Observable(0);
    var relativePosition = Observable(0);

    function scrolled(args) {
        console.log('');
        console.log('Scrolling motion stopped!');
        console.log('');
        console.log('Position:');
        console.log(position.value);
        console.log('Relative Position:');
        console.log(relativePosition.value);
    };

    function scrollPositionChanged(data) {
	    console.log('data');
	    console.log(JSON.stringify(data));
	    relativePosition.value = data.relativePosition[0];
	    position.value = data.value[0];
    }

    module.exports = {
        'relativePosition': relativePosition,
        'position': position,
        'scrolled': scrolled,
        'scrollPositionChanged': scrollPositionChanged
    };
    </JavaScript>

    <ClientPanel Background="#eee">
        <StackPanel Alignment="VerticalCenter" Margin="10,0,10,0">

            <WhileFalse ux:Name="IsScrolling" Value="false">
                <Callback Handler="{scrolled}" />
            </WhileFalse>

            <Text Value="{relativePosition}" FontSize="22" TextColor="#333" Alignment="Center" Margin="20" />

            <Text Value="{position}" FontSize="22" TextColor="#333" Alignment="Center" Margin="20" />

            <Panel>
            	<Panel Background="#1188ff" Width="2" Height="100" Alignment="Bottom" Margin="0" />

	            <ScrollView ux:Name="_scrollView" AllowedScrollDirections="Horizontal" ScrollPositionChanged="{scrollPositionChanged}">

	                <!-- <ScrollingAnimation>
	                    <Move X="0.95" RelativeNode="_scrollView" RelativeTo="Size" Target="_scrollIndicator" />
	                </ScrollingAnimation> -->

	                <WhileScrolling>
	                    <Change IsScrolling.Value="true" />
	                </WhileScrolling>

	                <StackPanel Orientation="Horizontal">
	                    <Each Count="30">
	                        <StackPanel Orientation="Horizontal" Height="100">
	                            <Panel Background="#aaa" Width="2" Height="80" Alignment="Bottom" Margin="5,0,5,0" />
	                            <StackPanel Orientation="Horizontal">
	                                <Each Count="4">
	                                    <Panel Background="#ddd" Width="2" Height="50" Alignment="Bottom" Margin="5,0,5,10" />
	                                </Each>
	                            </StackPanel>
	                        </StackPanel>
	                    </Each>
	                </StackPanel>

	            </ScrollView>
	        </Panel>

            <!-- <Panel>
                <Rectangle Width="5%" Height="8" Margin="0,2,0,2" CornerRadius="10" Alignment="TopLeft" ux:Name="_scrollIndicator">
                    <SolidColor Color="#44444488" />
                </Rectangle>
            </Panel> -->

        </StackPanel>
    </ClientPanel>
</App>

1 Like

That’s perfect, thank you.
I also added:

<WhileFalse ux:Name="IsScrolling" Value="false">
    <Callback Handler="{scrolled}" />
    <Set _scrollView.ScrollPosition="round({position}/12)*12" Delay="0.2" />
</WhileFalse>

This rounds to the nearest 12 (5 left margin 5 right margin and 2 width of the strokes in Uldis’ example) to make it snap to the nearest “correct” position.

1 Like