Dynamically size Nativescript ContentView within ScrollView based on device height - nativescript

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*).

Related

CollectionView renders on top of the Border

I'm trying to mount a view using VerticalStackLayout, but the second (Border) disappears when I add another CollectionView sample element, how can I resolve this, if I comment out the first element(CollectionView) the second appears pasted at the top.
Yes, that is because the CollectionView automatically full fill the page. So you couldn't see the Border.
Some workaround here:
1.You could set the HeightRequest for the CollectionView:
<CollectionView x:Name="CollectionViewTransactions" ... HeightRequest="300">
2.if there is more item in CollectionView, you could put it in a ScrollView:
<ScrollView Orientation="Vertical">
<CollectionView x:Name="CollectionViewTransactions" ... HeightRequest="300">
</ScrollView>
3.You may also customize the HeightRequest for CollectionView depending on the count of item. The following code is just for example.
In xaml fileļ¼Œset binding for HeightRequest
<CollectionView x:Name="CollectionViewTransactions" HeightRequest="{Binding Height}" ...>
In ViewModel, define a property for height and update it when adding a new item:
public int Height
{
get
{
return height;
}
set
{
height = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Height)));
}
}
public void SetNewHeight()
{
Height = 70 * ItemCollection.Count; //
}
All of the above three ways can show border below the CollectionView. You could have a try based on your design
For more info, you could refer to CollectionView.
Hope it works for you.

How to make ActivityIndicator overlay full screen?

I have a StackLayout and a number of elements inside (buttons, texts etc).
I want the ActivityIndicator to overlay the entire screen and make it not able to do anything to those elements.
I have put ActivityIndicator inside the StackLayout but wrapped it with AbsoluteLayout thinking that AbsoluteLayout can easitly overlap everything:
<StackLayout>
<AbsoluteLayout>
<ActivityIndicator ... />
</AbsoluteLayout>
<...other elements...>
</StackLayout>
Instead activity indicator is displayed at the top of the StackLayout and other elements are available for affecting. I'm new in Xamarin and layouts, what am I doing wrong? All samples in the Internet have single ActivityIndicator per page...
It is better said that an AbsoluteLayout's children can easily overlap each other. Just as a StackLayout lets you stack controls inside , vertically or horizontally, an AbsoluteLayout lets you position controls inside using absolute or proportional values, thus if two controls have the same absolute positioning set, they will overlap 100%.
Therefore, you want to wrap your StackLayout and another StackLayout that has your ActivityIndicator inside an AbsoluteLayout using proportional sizing, e.g:
<AbsoluteLayout>
<StackLayout
x:Name="mainLayout"
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All" >
<Label Text="Welcome to Xamarin.Forms!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Button Text="Do Something"
Clicked="DoSomethingBtn_Clicked" />
</StackLayout>
<StackLayout
x:Name="aiLayout"
IsVisible="False"
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All"
BackgroundColor="Gray" Opacity="0.5">
<ActivityIndicator
x:Name="ai"
IsRunning="False"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
Color="Black"/>
</StackLayout>
</AbsoluteLayout>
The above sets the two StackLayouts to both take up the full size of the parent container of the AbsoluteLayout, which is presumably a Page. The StackLayout that has the indicator is initially hidden. IN the page code behind for the above example, I show the second StackLayout and start the activity indicator and show it for 2 seconds, and then hide it again:
private async void DoSomethingBtn_Clicked(object sender, EventArgs e)
{
ai.IsRunning = true;
aiLayout.IsVisible = true;
await Task.Delay(2000);
aiLayout.IsVisible = false;
ai.IsRunning = false;
}
Here is what it looks like:
And since the second StackLayout completely covers the first, none of the controls in the first StackLayout are clickable.
Might be worth going over the docs for the AbsoluteLayout to understand the AbsoluteLayout.LayoutBounds and AbsoluteLayout.LayoutFlags:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/layouts/absolute-layout
If you want to "overlap", you need to be outside of the StackLayout. A Grid is the most common control for this:
<Grid>
<StackLayout>
<...other elements...>
</StackLayout>
<ActivityIndicator ... />
</Grid>
Here's a hacked-up control for making things full-screen via the horribly-named RelativeLayout (tested in Android only)
[ContentProperty("ContentInner")]
public class FullScreenLayout : ContentView
{
public View ContentInner
{
get => ((RelativeLayout) Content).Children[0];
set
{
var display = DeviceDisplay.MainDisplayInfo;
var screenWidth = display.Width / display.Density;
var screenHeight = display.Height / display.Density;
var wrapper = new RelativeLayout();
wrapper.Children.Add(value, () => new Rectangle(0, 0, screenWidth, screenHeight));
Content = wrapper;
}
}
}
It can be used like this:
<controls:FullScreenLayout>
<!-- Anything you want fullscreen here -->
</controls:FullScreenLayout>
Unfortunately, if you use NavigationPage, this won't overlap the navigation bar. Every other solution currently on this page has the same issue. According to this question, it's not possible to solve this without using platform-specific customer renderers. Ugh.
If you don't mind the page being dimmed, you can use Rg.Plugins.Popup which implements the custom renderers needed.
I ended up solving my similar problem (dimming most of the screen) by implementing a custom renderer for the navigation page itself.

Determine If component is visible in the screen

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.

NativeScript Angular 2 Get more data when Scroll to Bottom (endless scrolling)

HI for example below is my view
<ScrollView>
<StackLayout>
<StackLayout *ngFor="let kid of kids" id="kidList">
<StackLayout orientation="horizontal" class="some-class">
<Lable text="{{ kid.fname }} {{ kid.lname }}"></Lable>
<Lable text="{{ kid.age }} years ago"></Lable>
</StackLayout>
</StackLayout>
</StackLayout>
</ScrollView>
I want to append the more data getting from server to 'kidList' when scroll reaches to bottom of screen in {N} aAngular2.
It's very hard to me build the layouts and adding childs in 'js' like below(KidInformation has more data).
let stackLayout = new StackLayout();
stackLayout.addChild('something newly constructed with JS')
Is there a way we can do in My Component just by adding child as view by passing local parameters to view , I mean like in below way
let kidListStacklayout = view.getViewById(this.page, 'kidList');
kidListStacklayout.addChild('views/kid/kid-item.html')
and kid-item.html will look like
<StackLayout orientation="horizontal" class="some-class">
<Lable text="{{ kid.fname }} {{ kid.lname }}"></Lable>
<Lable text="{{ kid.age }} years ago"></Lable>
</StackLayout>
The stock list view supports what you want. Don't use a scrollview and adding more layouts to the screen. This will cause lag and other issues. A listview recycles UI view components to reduce overhead of the layout growing in size. You want to use the loadMore event on the list view. https://docs.nativescript.org/cookbook/ui/list-view
Of course as the comment above ^^^ the free UI suite from telerik provides the RadListView which also supports infinite scrolling with a loadMore event.
Find out how to do it ( using Angular & TypeScript) :
import { ScrollView, ScrollEventData } from "ui/scroll-view";
#Component({...})
class ComponentClass implements OnInit, AfterViewInit {
#ViewChild("scrollid") scrollView: ElementRef;
ngOnInit(){
let scrollv : ScrollView = <ScrollView>this.scrollView.nativeElement;
scrollv.on(ScrollView.scrollEvent, function (args: ScrollEventData) {
if(scrollv.scrollableHeight === args.scrollY){
console.log("load more items here !!! ");
}
});
}
}
The scrollv.scrollableHeight gets updated by itself.
Tested on android emulator only. Must work on both Plateforms.

How to add two full-sized page in scrollview(vertical) on Xamarin.forms?

I'm making app using with Xamarin.forms.
What I'm doing is really hard. Can't figure out so far.
I want to add two view(or page) into scrollview(vertical) and size of each view is equal with screen size. So If I scroll down, second view show up and first view will be hidden.
How to make it with using xaml?
I tried stacklayout, grid, relative, absolute.
Nothing works. (I believe there is some way to do it)
Thanks.
The solution I used for this was, to create a StackLayout with vertical orientation inside a ScrollView. Inside it I have a two StackLayout.
<ScrollView>
<StackLayout Orientation="Vertical" Spacing="0">
<StackLayout x:Name="FirstStack" BackgroundColor="Red">
</StackLayout>
<StackLayout x:Name="SecondStack" BackgroundColor="Blue">
</StackLayout>
</StackLayout>
</ScrollView>
After this I setted programmatically the Height for each page.
To get the screen Height i did it the simpler way (just to test it) but you should do it in a better way.
On iOS inside FinishedLaunching:
App.ScreenHeight = (int)UIScreen.MainScreen.Bounds.Height;
On Android inside MainActivity (need to be tweaked + 0.07f):
App.ScreenHeight = (int)(Resources.DisplayMetrics.HeightPixels / (Resources.DisplayMetrics.Density + 0.07f));
In you Application:
public partial class App : Application
{
public static int ScreenHeight;
And finally in the Page:
public ScrollVerticalPage()
{
InitializeComponent();
FirstStack.HeightRequest = App.ScreenHeight;
SecondStack.HeightRequest = App.ScreenHeight;
}
Final result here.

Resources