Determine If component is visible in the screen - nativescript

I use Nativescript + Angular and this is my code:
<ScrollView class="body" id="scroll" #scroll (scroll)="scrollEvent($event);">
<StackLayout #stackScroll>
<ng-template ngFor let-card [ngForOf]="allList">
<StackLayout [card]="card">
<my-custom-component [aCard]="card"></my-custom-component>
</StackLayout>
</ng-template>
</StackLayout>
</ScrollView>
I have used this snippet of code and it works great:
https://discourse.nativescript.org/t/how-to-detect-if-component-is-in-screen-view-is-visible/1148/4
I can change the background colour of the "StackLayout" inside the "ng-template".
But I can't access to my custom component variables to modify his behaviour.
For example, if "my-custom-component" is shown, I want to change the property "isShown" in the "card" object passed in the "aCard" attribute.
Thanks to all :)
EDIT1:
"isShown" is a custom variable that I have used for this test. My idea is to calculate in the afterScroll function what is the cards visible and pass to aCard the parameter to change his behaviour.

You could find the location of each child component inside ScrollView upon scroll event, comparing the same with the vertical offset will let you know whether the component is really visible on screen.
Here is a Playground example. As you scroll down / up, the background color of visible components will turn green, red otherwise.
onScroll(event: EventData) {
const scrollView = <ScrollView>event.object,
verticalOffset = scrollView.verticalOffset,
height = scrollView.getActualSize().height,
visibleRange = verticalOffset + height,
container = <StackLayout>this.container.nativeElement;
let index = 0;
container.eachLayoutChild((childView) => {
const locationY = childView.getLocationRelativeTo(container).y;
this.cards[index].isShown = locationY >= verticalOffset && locationY <= visibleRange
index += 1;
});
}

You need to update your allList object as NgForOf is bindable, it will update the card and that will reflect in [acard] of your my-custom-component
In the scroll event where you must be playing with relative height you take a unique variable to identify the component that is shown and change the property for that index in allList.
I have created a sample playgrod here where I am changing the text of the custom component Label to isShown if scroll height is greater than 300. The way I am changing the label name, you can have a boolean variable in allList in change that where you have your logic to change the background color of stackLayout. Let me know if you want to update the playgrond.

Related

Xamarin.Forms - How can I dynamically change the height of my CarouselView?

I have a CarouselView in which I call an object with individual and completely different Views.
The only elements my ContentPage has, are the CarouselView itself, and a bottombar with a gradient above it (notice gradient in following image).
I have done this in a way in which this gradient dissapears when the page's scrolling space becomes 0 (when I have scrolled to the end of the page).
The problem is that when I swipe between items in the CarouselView, the CarouselView always maintains the height of the very first View that is called in.
This means that, in a View with MORE height than the 1st one, when scrolling up (after being at the very bottom, and therefore not showing a gradient) the gradient will only show again once it hits the height value of the 1st page.
In a View with LESS height than the 1st one, the page will allow me to scroll down until I reach the height value of the 1st page, even if there are not enough elements on the page to even need a scroll.
Essentially, what I am asking for, is if there is a way in which I can, in some way, "refresh" the height of the Page every time a scroll is complete to another View in the CarouselView, resolving my height issues in smaller views, and my gradient issues in larger views.
Main ContentPage Code Behind (Gradient)
public double ScrollingSpace
{
get
{
return MainScrollView.ContentSize.Height - MainScrollView.Height;
}
set { }
}
// Removes gradient when scroll is complete
private void OnScrolled(object sender, ScrolledEventArgs e)
{
if (ScrollingSpace <= e.ScrollY) // Touched bottom
EndPageGradient.SetValue(IsVisibleProperty, false); // the view is GONE, not invisible
else
EndPageGradient.SetValue(IsVisibleProperty, true);
}
// Removes gradient if page is not large enough to need scroll
protected override void OnAppearing()
{
if (ScrollingSpace <= 0)
EndPageGradient.SetValue(IsVisibleProperty, false); // the view is GONE, not invisible
}
Main ContentPage CarouselView XAML
<CarouselView
ItemsSource="{Binding ViewList}"
Loop="False">
<CarouselView.ItemTemplate>
<DataTemplate>
<ContentView Content="{Binding .}" />
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
Main ContentPage ViewModel (List with Views for CarouselView)
ViewList = new List<ContentView>()
{
new Step1(),
new Step2(),
new Step3(),
new Step4(),
new Step5(),
new Step6(),
new Step7(),
new Step8()
};
Thanks in advance!

Dynamically size Nativescript ContentView within ScrollView based on device height

I have a Nativescript Vue component template defined as:
<ScrollView>
<StackLayout>
<ContentView ref="mapContainer" height="500" width="100%">
<Mapbox
accessToken="my access key"
mapStyle="outdoors-v9"
hideCompass="true"
zoomLevel="10.2" ,
latitude="51.949266"
longitude="-12.183571"
showUserLocation="true"
disableZoom="true"
disableRotation="true"
disableScroll="true"
disableTilt="true"
#mapReady="onMapReady($event)">
</Mapbox>
</ContentView>
<Label text="A test label"/>
</StackLayout>
</ScrollView>
When the component is mounted, I want to set the height of mapContainer to roughly 75% of the screen height. To do so, I have:
export default {
name: "MyPage",
mounted () {
this.$refs.mapContainer.height = platform.screen.mainScreen.heightDIPs
}
...
}
But this does nothing, and the ContentView remains at 500dp tall.
height is meant to be a setter, but I figure I'm missing a redraw (?) to get this change to take effect, but not sure how?
In order to access the ContentView via refs, you must use .nativeView property.
this.$refs.mapContainer.nativeView.height = platform.screen.mainScreen.heightDIPs
Also you don't have to calculate the height but simply set the height in percentage (70%) instead of fixed value (500) Or use a GridLayout with 7 partition (7*).

Xamarin Forms Add Custom Toolbar at bottom

I have a page created using Xamarin Forms. I want to add a toolbar at the bottom of the screen like an overlay but doesnt change the actual layout of the screen. It should just overlay above everything and always be at the bottom.
It should showup only when there is a particular event. SO it will be added dynamically.
Any ideas - or if you can send point me in the right direction. I do not want to use any nuget packages.
Have you tried to use AbsoluteLayout/Grid?
<Grid>
<StackLayout><Label Text="Your content"/></StackLayout>
<Button x:Name="toolbar" VerticalOptions="End" Text="Your toolbar" />
</Grid>
Show/hide the control based on your event.
If you could show the code of displayToolbar it would be easier to write a code that suits your needs, but it should be pretty easy even without the code :)
You could do this with a RelativeLayout, AbsoluteLayout or a Grid but I recommend doing it with a Grid because is much lighter than RelativeLayout and has a lot more functions than AbsoluteLayout.
So, make the root of every page that you want to use displayToolbar a Grid with one row and one column. Add the actual content of the page to that Grid as a child view.
Now comes the part that would be easier with your code. When you want to display the toolbar, add the toolbar view as a child of the root Grid with VerticalOptions set to LayoutOptions.End.
That's it. The toolbar will be added in front of every view of the page and if you want to dynamically remove the toolbar, remove the root Grid's last child.
The layout would be something like this:
Grid root;
internal SomePageConstructor()
{
root = new Grid
{
ColumnDefinitions =
{
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }
},
RowDefinitions =
{
new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }
}
};
root.Children.Add(actualPageContent, 0, 0);
Content = root;
}
void DisplayToolbar()
{
var toolbar = new StackLayout { VerticalOptions = LayoutOptions.End };
root.Children.Add(toolbar, 0, 0);
}
void RemoveToolbar()
{
root.Children.Remove(root.Children[1]);
}
Note: I did this directly on StackOverflow, code could need some corrections.
From this sample, you could do an animation of the toolbar coming from below the page or something like it. Just change the DisplayToolbar and the RemoveToolvar methods.
Obs. Method names on C# are PascalCased (every word is capitalized) so your displayToolbar is DisplayToolbar.
Hope it helps! :)

How to add TapGestureRecognizer to outer StackLayout in Xamarin Forms

I have two nested StackLayouts:
<StackLayout>
<StackLayout>
</StackLayout>
</StackLayout>
I would like to add click event on outer StackLayout that will not be triggered when inner StackLayout is clicked. Is it possible in Xamarin Forms? If I can filter click event (with if block) I would also be happy.
I think you can take a look to Rg Plugin popup.
it has this property
CloseWhenBackgroundIsClicked: Close pop-up when click on the background
You can add what you want to this popup because you can add a ContentPage
// Use these methods in PopupNavigation globally or Navigation in your pages
// Open new PopupPage
Task PushAsync(PopupPage page, bool animate = true) // Navigation.PushPopupAsync
// Hide last PopupPage
Task PopAsync(bool animate = true) // Navigation.PopPopupAsync
// Hide all PopupPage with animations
Task PopAllAsync(bool animate = true) // Navigation.PopAllPopupAsync
// Remove one popup page in stack
Task RemovePageAsync(PopupPage page, bool animate = true) // Navigation.RemovePopupPageAsync

How can I prevent Scrollviewer to scroll back when scrollbarvisibility is set to disabled? wp7

I have created a Scrollviewer in WP7, which harbors 3 usercontrol, each one of which hold as their content XAML created UserControls. This works fine. This scrollviewer should be able to scroll between these items, but make this not possible for the user to scroll. So when an item in one of these contents are clicked upon, the scrollviewer slides left or right depending on the item selected, and bring into view one of the other usercontrols. I use a mediator to accomplish this:
<Grid.Resources>
<Storyboard x:Name="ItemAnimation">
<DoubleAnimation x:Name="ItemAnimationContent"
Storyboard.TargetName="Mediator"
Storyboard.TargetProperty="ScrollableWidthMultiplier"/>
</Storyboard>
</Grid.Resources>
<ScrollViewer Name="ScrollableItemPanel"
Grid.Row="2"
Grid.RowSpan="3"
Grid.ColumnSpan="3"
VerticalScrollBarVisibility="Disabled"
HorizontalScrollBarVisibility="Disabled">
<StackPanel Orientation="Horizontal">
<UserControl Name="NewsListBoxControl" Width="480" />
<UserControl Name="DetailedItemControl" Width="480"/>
<UserControl Name="ExternalBrowserItemControl" Width="480"/>
</StackPanel>
</ScrollViewer>
<local:ScrollableItemAnimationMediator x:Name="Mediator"
ScrollViewer="{Binding ElementName=ScrollableItemPanel}"/>
In basic, this works fine too, I can navigate between the items, and load upon them the content as usercontrols. But the problem lies in granting the user the abillity to scroll. Before the item scrolls, I set the hittestvisibilty to true, and the horizontalscrollbarvisibility to visible. After the animation is done, I want to grant back the hittestvisibility and set the horizontalscrollbarvisibility to Disabled again. This latter is where the problem is: when I set the horizontalscrollbarvisibility to Disabled, the scrollviewer automatically brings back into view the first of three items in the stackpanel. How can I stop this? This is the code I use to scroll the mediator:
private void CreateDetailedArticleItem( Dictionary<string, string> itemQuery )
{
_articleDetailPage.ItemQuery = itemQuery;
DetailedItemControl.Content = _articleDetailPage as UserControl;
Animate( _articleDetailPage, 0.0f, 0.5f, 250 );
}
private void Animate( IContentControl control, float from, float to, double milliseconds )
{
//this eventhandler will fire when the animation has completed
EventHandler handler = null;
//we take away the User Input just for the moment, so that we can animate without the user interfering. Also, we make horizontalScroll Visible
IsUserEnabled = false;
//we then set the content of the animation. Where from will it move, towards where and in what duration?
ItemAnimationContent.From = from;
ItemAnimationContent.To = to;
ItemAnimationContent.Duration = TimeSpan.FromMilliseconds( milliseconds );
//we start the animation
ItemAnimation.Begin( );
//we tell the new control that it will appear soon, so it can load its main content
control.ViewWillAppear( );
//also, we tell the currentcontrol that it will disappear soon, so it can unload its content and eventhandlers and so on
CurrentControl.ViewWillDisAppear( );
//the handler is a delegate. This way, it becomes rather easy and clean to fire the completed event, without creating a strong reference ( well, actually,
//we do create a strong reference, but as soon as it is fired, we remove it again, shhhh! ).
handler = delegate( object sender, EventArgs e )
{
//as stated, we remove the eventlistener again, so it won't keep firing all the time
ItemAnimation.Completed -= handler;
//after the animation, we tell the new control that it is now in screen, and can start downloading its data
control.ViewDidAppear( );
//at the same time, the "current" control has fully moved out of view, so it can now fully unload all its content.
CurrentControl.ViewDidDisAppear( );
//now, all we have to do is to make sure that the next time an item is being loaded, the new content is spoken to, not the old one
CurrentControl = control;
//and finally, enable the users input again, and remove the horizontal scrollbarvisibility
IsUserEnabled = true;
};
ItemAnimation.Completed += handler;
}
private bool IsUserEnabled
{
set
{
//when the user can control the scrollviewer, then the horizontal scrollvisibility is disabled, so that the user cannot move horizontally,
//otherwise, so we only make it visible when the program needs to animate.
ScrollableItemPanel.IsHitTestVisible = value;
ScrollableItemPanel.HorizontalScrollBarVisibility = value ? ScrollBarVisibility.Disabled : ScrollBarVisibility.Visible;
}
}
I had already asked this question, then regarded it as answered, as I thought it to be answered, namely using ScrollbarVisibility.Hidden instead of ScrollbarVisibility.Disabled, only the scrollbarvisibility stays visible this way, and the user can still scroll. Is there a native way to deal with this problem?
Any help would be greatly appreciated. Greetz
Rather than fight the behavior of the native control it may be easier to just manipulate the position of items yourself using a custom control (wrapping your other controls) which animates between different visual states (adjust the translate transform) depending on the "selected" item.

Resources