How to fill the blank space of a ScrollView in a ClientPanel

Hello!
I recently implemented a PullToReload in my app and using the ScrollView for its operation has created layout issues.

The original layout looked similar to the following, I have a ClientPanel (to take care of status and bottom bars) with a DockPanel inside. The DockPanel has 3 elements: the first docked “Top”, the next docked “Bottom” and the third is expected to take up all remaining space (in the middle). Similar to the following:

<App>
    <ClientPanel ux:Name="screen">
            <DockPanel>
                <Rectangle ux:Name="rec1" Dock="Top" Color="#aaa">
                    <Text Value="I'm at the top!" Alignment="Center" FontSize="32"/>
                </Rectangle>
                
                <Rectangle ux:Name="rec2" Dock="Bottom" Color="#aaa">
                    <Text Value="I'm at the bottom!" Alignment="Center" FontSize="32"/>
                </Rectangle>

                <Rectangle ux:Name="fill" Color="#aaf">
                    <Text Value="I'm the fill!" Alignment="Center" FontSize="32"/>
                </Rectangle>
            </DockPanel>
    </ClientPanel>
</App>

Adding the ScrollView collapses the “fill” section and the only way I’ve found to try and get it back is to use height() expressions as follows:

<App>
    <ClientPanel ux:Name="screen">
        <ScrollView SnapMaxTransform="false" SnapMinTransform="false">
            <DockPanel>
                <Rectangle ux:Name="rec1" Dock="Top" Color="#aaa">
                    <Text Value="I'm at the top!" Alignment="Center" FontSize="32"/>
                </Rectangle>
                
                <Rectangle ux:Name="rec2" Dock="Bottom" Color="#aaa">
                    <Text Value="I'm at the bottom!" Alignment="Center" FontSize="32"/>
                </Rectangle>

                <Rectangle ux:Name="fill" Color="#aaf" Height="height(screen)-height(rec1)-height(rec2)-72">
                    <Text Value="I'm the fill!" Alignment="Center" FontSize="32"/>
                </Rectangle>
            </DockPanel>
        </ScrollView>
    </ClientPanel>
</App>

This seems a little hacky to me (especially the magic number [72] to account for the status/bottom bars). And, while it looks ok using the Android Preview App (on PixelXL with Android 8.1.0), it doesn’t look great in the Fuse Studio preview (all the white space at the bottom) Is there a better or recommended way to handle this situation?

Info:

  • Fuse version 1.5.0 (build 15046)

  • Uno version 1.4.3 (build 6115) Windows 10 x64 83d8656

Product information

  • Product.Commit 83d8656035e71a42151ce35b76e3b59cf7725e08

  • Product.Version 1.4.3.6115

Runtime environment

  • Environment.OSVersion Microsoft Windows NT 6.2.9200.0

  • Environment.Version 4.0.30319.42000

Hey Darrin,

You should put the ScrollView in a different place. From your code, it looks like you want to create scrollable middle part and have a few locked/docked elements. If that is the case, you should only wrap that Rectangle which represents your scrollable content. This way the parent DockPanel will dock the respective elements to top and bottom, and the remaining available space will be filled by ScrollView. You should spend some time reading about Layout in Fuse.

I changed your code a little, so now you can scroll the content and your rectangles dock at the top and bottom, as they should. Oh, and a ClientPanel is a DockPanel, so you don’t necessarily need both.

<App>
  <ClientPanel>
    <Rectangle Dock="Top" Color="#aaa">
        <Text Value="I'm at the top!" Alignment="Center" FontSize="32"/>
    </Rectangle>
    <Rectangle Dock="Bottom" Color="#aaa">
        <Text Value="I'm at the bottom!" Alignment="Center" FontSize="32"/>
    </Rectangle>
    <ScrollView SnapMaxTransform="false" SnapMinTransform="false">
      <Rectangle Color="#aaf" Height="1000">
          <Text Value="I'm the fill!" Alignment="Center" FontSize="32"/>
      </Rectangle>
    </ScrollView>
  </ClientPanel>
</App>

Hope this helps!

Hello @Arturs! Thanks for the reply – but that’s not really what I’m after.

The ScrollView isn’t for scrolling data, it is used to act as a grabber for the PullToReload so I want the ScrollView to occupy the entire space – not just the fill space. This is why I have it wrapping the whole content – because I need to be able to interact with other elements on the whole page (the fill is actually a PageControl in my code that displays multiple graph elements - for a weather application. There is no data that needs to be scrolled, only a PullToReload action, which is what the ScrollView is for.

I can relocate the ScrollView outside of the display elements and of course the docked and fill elements display correctly, however, since it is then outside of the other layout elements, it either blocks (if it’s on top) interaction with them, or isn’t interactable itself (if it’s at the bottom of the stack – unless I set it to Overlay which then blocks the other elements).

I wasn’t aware of the ClientPanel essentially being a DockPanel, so thanks for that info! However, I have read, watched, and experimented pretty extensively with layout options in Fuse, which is why I’m confronted with this quandry – this is the only way I’ve been able to make it work. If I could propagate touch interaction through the ScrollView, I could move it to a sibling location that would not affect the layout. Maybe that’s possible?

Here’s a more verbose implementation to demonstrate what I ultimately want to accomplish:

<App Background="Black">
    <JavaScript>
        module.exports = {
            doSomething: (args) => { console.log(args.data.name + " doing something..."); }
        }
    </JavaScript>

    <ClientPanel ux:Name="screen">
        <!-- the ScrollView is only for the PullToReload action -->
        <ScrollView SnapMaxTransform="false" SnapMinTransform="false">
            <ScrollingAnimation Range="SnapMin">
                <Change pulledPanel.Opacity="1.5" />
                <Change pulledPanel.Height="50" />
                <Change filler.Opacity="0" />
                <Change rec2.Opacity=".2" />
            </ScrollingAnimation>

            <PullToReload ux:Name="puller" IsLoading="false" ReloadHandler="{doSomething}" >
                <State ux:Binding="PulledPastThreshold">
                    <Change pulledText.Value="- RELEASE TO RELOAD -" />
                    <Change pulledPanel.Color="#48f8" Duration=".2" />
                </State>
                <State ux:Binding="Loading">
                    <Change puller.IsLoading.Value="false" />
                </State>
            </PullToReload>

            <DockPanel>
                <!-- top docked panel -->
                <StackPanel Orientation="Vertical" Dock="Top">
                    <Rectangle ux:Name="rec1" Color="#aaa">
                        <Text Value="I am at the top!" Alignment="Center" FontSize="32"/>
                    </Rectangle>
                    <!-- this is the pulled panel indicator -->
                    <Rectangle ux:Name="pulledPanel" Height="0" MinHeight="0" CornerRadius="0,0,20,20"
                            Layer="Overlay" Color="#48f6" Opacity="0" 
                            Offset="0,height(pulledText)" Anchor="50%,0%">
                        <Text ux:Name="pulledText" Padding="3"
                            Alignment="BottomCenter" FontSize="14" Color="#aaa" 
                            TextWrapping="Wrap" TextAlignment="Center"
                            Value="" />
                    </Rectangle>
                </StackPanel>
            
                <!-- bottom docked panel -->
                <Rectangle ux:Name="rec2" Dock="Bottom" Color="#555">
                    <Text Value="I am at the bottom displaying some data!" TextWrapping="Wrap" TextAlignment="Center" FontSize="32" Color="#aaa" />
                    <Clicked Handler="{doSomething}" />
                </Rectangle>

                <!-- without the height math below, the filler panel is collapsed -->

                <!-- <PageControl ux:Name="filler"> -->
                <PageControl ux:Name="filler" Height="height(screen) - height(rec1) - height(rec2) - 72">
                    <PageDemo />
                    <PageDemo Color="#afa" />
                    <PageDemo Color="#faa" />
                </PageControl>
                
            </DockPanel>
        </ScrollView>
    </ClientPanel>

    <Rectangle ux:Class="PageDemo" Color="#aaf">
        <Text Value="I am one page of the fill!" Alignment="Center" FontSize="32"/>
    </Rectangle>
</App>

Ultimately, the question is, what is the appropriate way to handle the “filler” area height to be responsive to any screen and inclusive of handling the status/bottom bars?

Thanks for such a detailed description. At least it’s clear now that you understand how the layout works in Fuse just fine. Sorry for assuming otherwise.

However, you have chosen the wrong tools for the task. Only because you need a “pull to reload” behavior does not justify the use of a ScrollView if there is not scrollable content.

It is true that the PullToReload trigger only works inside of ScrollView, but you don’t necessarily need to use it. It would be much less painful if you just went ahead and made your own “pull to reload” by using SwipeGesture, SwipingAnimation and Swiped.

Here’s my take on it:

<App Background="Black">
    <JavaScript>
        function reload() {
            console.log("reload was called");
        }
        module.exports = {
            reload: reload
        }
    </JavaScript>
    <ClientPanel>
        <!-- top docked panel -->
        <Panel Dock="Top" Height="56" Color="#aaa">
            <Text Value="I am at the top!" Alignment="Center" FontSize="24" />
        </Panel>
        <!-- bottom docked panel -->
        <Panel Dock="Bottom" Color="#555">
            <Text Value="I am at the bottom displaying some data!" TextWrapping="Wrap" TextAlignment="Center" FontSize="24" Color="#aaa" />
            <Clicked Handler="{reload}" />
        </Panel>
        <PageControl>
            <PageDemo />
            <PageDemo Color="#afa" />
            <PageDemo Color="#faa" />
        </PageControl>
    </ClientPanel>
    <Panel ux:Class="PageDemo" Color="#aaf">
        <!-- this is the pulled panel indicator -->
        <Rectangle ux:Name="pulledPanel" CornerRadius="0,0,20,20" Color="#48f6" Opacity="0" Alignment="Top">
            <Text ux:Name="pulledText" Margin="0,8" Alignment="Center" FontSize="14" Color="#fff" TextWrapping="Wrap" TextAlignment="Center" Value="" />
        </Rectangle>
        <SwipeGesture ux:Name="swipe" Direction="Down" Length="200" />
        <SwipingAnimation Source="swipe">
            <Change pulledPanel.Opacity="1" Duration=".8" />
            <Change pulledPanel.Height="60" Duration="1" />
            <Change pulledText.Value="- RELEASE TO RELOAD -" Duration=".8" />
        </SwipingAnimation>
        <Swiped Source="swipe">
            <Callback Handler="{reload}" />
        </Swiped>
        <Text Value="I am one page of the fill!" Alignment="Center" FontSize="24" />
    </Panel>
</App>

Next steps: you could use Busy API to trigger a busy state in UX so that the pulled panel stays open and animates while data loading is happening.

Hope this helps!

Hello again @Arturs! I do have Busy implemented in the app already, so am familiar with it.

PullToRelead did seem like overkill for what I wanted, but the built-in states and handler were nice – even though I found myself having to work around it more than with it. Technically, I am only using the ‘pulled past threshold’ state anyway – so should be easy to get away from.

I will dissect your example and give it a try! I’ll let you know how it goes and thanks again!

Hello @Arturs! I believe the implementation you submitted above will work fine for my needs. I’ve shuffled around a few things but definitely success in the overall goal – thank you!

I have a couple of issues with the SwipeGesture that I’ll open in a separate thread.

BTW, here’s the implementation I came up with – I mimicked the pull-to-reload from Aqua Mail and then modified it a bit.

<App Background="Black">
    <JavaScript>
        function reload() {
            console.log("reload was called");
        }
        module.exports = {
            reload: reload
        }
    </JavaScript>
    <ClientPanel>
        <SwipeGesture ux:Name="swipe" Direction="Down" Length="200" />
        <SwipingAnimation Source="swipe">
            <Change pulledAnimator.Width="width(pulledPanel)" />
            <Change pulledAnimator.Height="height(pulledPanel)"/>
        </SwipingAnimation>
        <Swiped Source="swipe">
            <Callback Handler="{reload}" />
        </Swiped>
        <WhileSwipeActive Source="swipe">
            <Change pulledText.Opacity="1" Duration=".2" />
            <Change pulledText.Value="- RELEASE TO RELOAD -" />
        </WhileSwipeActive>
        <WhileSwiping Source="swipe">
            <Change pulledPanel.Height="40" Duration=".3" Easing="CubicOut" EasingBack="CubicOut" />
            <Change pulledPanel.Opacity=".8" Duration=".3" />
            <Change pulledText.Opacity=".8" Duration=".3" />
        </WhileSwiping>
        
        <!-- top docked panel -->
        <Panel Dock="Top" Color="#aaa">
            <Text Value="I am at the top!" Alignment="Center" FontSize="24" />
        </Panel>
        <!-- this is the pulled panel indicator -->
        <Panel Dock="Top">
            <StackPanel Orientation="Vertical">
                <Rectangle ux:Name="pulledPanel" Layer="Overlay" Height="0" Color="#48f6" Opacity="0" Anchor="50%,0%">
                    <Text ux:Name="pulledText" Padding="0,0,0,12" Alignment="Bottom" FontSize="14" Color="#fff" TextWrapping="Wrap" TextAlignment="Center" Opacity="0" Value="Pull to Reload" />
                    <Rectangle ux:Name="pulledAnimator" Height="0" Width="0" Color="#48fd" Alignment="Center" CornerRadius="5" />
                </Rectangle>
            </StackPanel>
        </Panel>

        <!-- bottom docked panel -->
        <Panel Dock="Bottom" Color="#555">
            <Text Value="I am at the bottom displaying some data!" TextWrapping="Wrap" TextAlignment="Center" FontSize="24" Color="#aaa" />
            <Clicked Handler="{reload}" />
        </Panel>
        <PageControl>
            <PageDemo2 />
            <PageDemo2 Color="#afa" />
            <PageDemo2 Color="#faa" />
        </PageControl>
    </ClientPanel>
    <Panel ux:Class="PageDemo2" Color="#aaf">
        <Text Value="I am one page of the fill!" Alignment="Center" FontSize="24" />
    </Panel>
</App>