Xamarin Forms ScrollView Jitters When Descendents are Added - c#

I'm new to Xamarin, so hopefully this is an easy fix. The problem is simple: I have a ScrollView containing a StackLayout, and adding new children to the StackLayout briefly causes the existing children to squish together causing a jittery visual disturbance. In the application I'm writing, children are being added to the StackLayout as the user scrolls down, so this jitter is also causing some more complicated issues with my scroll events.
The isolated issue is easy to reproduce:
<ScrollView>
<StackLayout>
<Button Clicked="Button_Clicked" Text="Click Me" HeightRequest="80"/>
<StackLayout x:Name="Column1"/>
</StackLayout>
</ScrollView>
private void Button_Clicked(object sender, EventArgs e)
{
for (int i = 0; i < 5; i++)
Column1.Children.Add(new Frame() { BackgroundColor = Xamarin.Forms.Color.Blue, HeightRequest = 100 });
}
Is there any way to create a scrollable view and add content dynamically without disrupting existing content?
Any help would be much appreciated!

It would be much more appropriate to use a view such as a CollectionView or ListView to add children to a scrolling view on the fly. They are both more efficient in terms of only loading what is visible on the screen, and already have built in logic for adding/removing children such that the layout behaves nicely.
With CollectionView/ListView you can still have children that look differently, by using a TemplatSelector.

Related

Xamarin Forms Tap Gesture Click Effect on Stack layout

Please i will like to know how to achieve a click effect on a stacklayout. I have my design, i use stacklayout and tap gesture to navigate to other pages. However, when i click it feels static and i need to see a click effect.
Below is my design
I want the effect to show when i click any of these options (falut, outage etc) just like when i click on a button control.
Thanks
There are many solutions which can implement it . For example ,you could set the BackgroundColor of the StackLayout in tap directly .
private async void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
var stack = sender as StackLayout;
stack.BackgroundColor = Color.Black;
await Task.Delay(100); // delay 0.1s
stack.BackgroundColor = Color.LightBlue; // set it to the default color that you define in xaml
//do something you want
}
Solution 2
You could use the plugin XamEffects from nuget .
Usage
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamEffects.Sample"
xmlns:xe="clr-namespace:XamEffects;assembly=XamEffects"
x:Class="xxx.MainPage">
<StackLayout HorizontalOptions="Center"
VerticalOptions="Center"
HeightRequest="100"
WidthRequest="200"
xe:TouchEffect.Color="Red">
//put content of StackLayout here
</StackLayout >
</ContentPage>

Unable to Programmatically Navigate Dynamic ListView

<ListView x:Name="myListView" ItemsSource="{x:Bind PageViewModel.myCollectionOfThings, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewmodels:ThingViewModel>
<TextBlock Text="{x:Bind Name, Mode=OneWay}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Name="TestButton" Tapped="TestButton_Tapped"/>
private void TestButton_Tapped(object sender, TappedRoutedEventArgs e)
{
PageViewModel.myCollectionOfThings.Add(newItem);
myListView.SelectedIndex++;
}
I'm trying to create a carousel control**. Using the code above I can dynamically add an item to the end of the collection each time its selected index increases. It works as one would expect except one problem. Even though the UI is reflecting the new items added to the end of the ListView, if I iterate past the last index of the original collection size, the UI jumps back to the beginning of the list and the selected index becomes 0. I've tried many variations of the above code and tried different collection types. I've also tried re-assigning the ListView's ItemSource each iteration which didn't do anything. Any help would be appreciated.
**I know there's something called Carousel in the UWP Community Toolkit but it isn't actually a carousel. A carousel can scroll endlessly as its collection will loop, which that control does not do.
If you want Carousel that can scroll endlessly then take a look at the Carousel control in the Windows AppStudio NuGet package. Download Windows App Studio UWP Samples to learn about the control.
Here an image of this Carousel Control
It looks like the listview is rebinding with binding source when the collection is changed. Try to re-set the index manually after adding the item.
int currentIndex = myListView.SelectedIndex;
PageViewModel.myCollectionOfThings.Add(newItem);
myListView.SelectedIndex = currentIndex++;
// If above doesn't work try setting selectedItem property to newItem.

UWP GridView Image Stretch bug

I have a GridView control bound to a collection of objects having a BitmapImage property. GridView item template's Image control has a fixed size, while actual pictures may vary in size, can be smaller or bigger than the Image. So i use Stretch=Uniform for bigger pictures, and Stretch=None for smaller. I set the Stretch property on Image_Loaded event:
private void img_ImageOpened(object sender, RoutedEventArgs e)
{
var img = sender as Image;
if (img.Width > (img.Source as BitmapImage).PixelWidth)
{
img.Stretch = Stretch.None;
}
else
{
img.Stretch = Stretch.Uniform;
}
}
So the pics are fitted pretty well:
But if i clear the bound collection and fill it again, things get really messy:
I have spent pretty much time trying to resolve this issue. Image_Loaded isn't called for the second time, so i thought it's something with item caching. I have tried to set CacheMode to null, but that didn't help. Tried to handle various events but with no success either.
Please help!
Thanks
Download my project - i have removed anything not related to the problem, there are only 90 lines of code.
PS i have found the right event to subscribe, it's Image_DataContextChanges. It seems GridView items are reused, and on updates objects and particular grid items can be confused. Image_Loaded isn't called, so an object gets into a random grid item with arbitrary stretching. DataContextChanges fires each time instead so it can be used to change stretching method on the fly.
And while it works i think the Clemens' solution below is just better. Will use it next time.
Instead of adjusting the Image's Stretch property in code behind, you can put the Image control in a Viewbox, which in addition to Stretch also has a StretchDirection property. If you set that to DownOnly, the images will only be stretched to smaller sizes.
<DataTemplate x:DataType="local:Thing">
<Border BorderBrush="Black" BorderThickness="1" Background="Black">
<Viewbox Width="48" Height="48" Stretch="Uniform" StretchDirection="DownOnly">
<Image Stretch="None" Source="{x:Bind Image}" />
</Viewbox>
</Border>
</DataTemplate>
I fixed your code by making sure that you clean up the Stuff collection entirely when hiding the grid
Stuff = null
This means that in the Show_Click handler, you reinitialize the collection.
Stuff = new ObservableCollection<Thing>();
If you will continue to use bindings, you'll need to raise a PropertyChanged notification when you do that (recommended). If you don't want to use bindings, just re-set the ItemsSource to the new instance of Stuff (see below).
private void Show_Click(object sender, RoutedEventArgs e)
{
Stuff = new ObservableCollection<Thing>();
foreach (var s in source.Except(Stuff)) Stuff.Add(s);
gv.ItemsSource = Stuff;
}
private void Hide_Click(object sender, RoutedEventArgs e)
{
Stuff.Clear();
Stuff = null;
}

Xamarin Forms image in ListView gives app poor performance

I have simple listview in Xamarin.Forms portable project. On Android Listview it is very slow if I put simple image in ViewCell and bind image with path. Image size is not grater than 10Kb. Application poor performance are visible on scrolling and also when I'm sliding menu (open/close). Situation is very same even on device as it is on emulator.
Here is my XAML code of listview
<ListView x:Name="newsList" CachingStrategy="RecycleElement" RowHeight="70" SeparatorVisibility="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Image HeightRequest="50" Aspect="AspectFill" Source="{Binding Image}"></Image>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
Application has simple Master Detail Page menu for navigation. I am binding 3-4 (testing) items to ListView and it is very very slow. I am using 2.2.0.31 version of Forms.
This is very strange because I don't have big dana in my ListVies.
Backend Code is very simply for testing:
public partial class News : ContentPage
{
public List<Model.NewsData> NewsListData;
public News()
{
InitializeComponent();
NewsListData = new List<Model.NewsData>();
TestData();
}
public void TestData()
{
NewsListData.Add(new Model.NewsData()
{
Image = "placeholdercircle.png",
Title = "Some Stuff Here"
});
newsList.ItemsSource = NewsListData;
}
}
Only thing I have on my News.xaml page is ListView. I'm using RoundedImage library but not in this list, and not on this view.
What am I doing wrong here, and why is my ListView so slow?
On Android with Xamarin.Forms specifically, I had terrible performance problems with images. It was caused by only having one size of image in the Resources/drawable folder of the Droid project whilst I was developing.
As soon as I put appropriately sized images into drawable-hdpi,-mdpi,-xhdpi etc., there was a huge performance increase.

Programmatically adding content to scrollviewer, scrollbar stops working

Ok, so I am not that versed in the mighty WPF, but I attempted an interesting project to jump into it. I have made a simple RSS/ATOM feed viewer that pulls the HTML out of and RRS or ATOM feed and sticks it in a Browser control which is added to a stack panel... which is the content of a ScrollViewer. Whew. Anyways the problem is, I am doing this all in the code behind and have found that the ScrollViewer doesn't work, or isn't recognizing the size of the content, so there is no scrolling. I have tried setting the size of the viewer and the content, as well as attempted the min and max sizes.
What am I missing here? The content is there, and if I load this before the WPF is loaded it works but once I try to change, or "Clear" children from a control, the scrollviewer stops working right.
<Window x:Class="Heine.Syndication.xkcd.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Feed Viewer" Height="600" Width="800">
<StackPanel>
<ToolBarPanel >
<ToolBar>
<ComboBox Margin="0" Width="100" Name="cbController">
<MenuItem Header="xkcd" Name="xkcdMI"/>
<MenuItem Header="9Gag" Name="nineGagMI"/>
<MenuItem Header="reddit" Name="redditMI"/>
</ComboBox>
</ToolBar>
</ToolBarPanel>
<Grid Name="svMain">
</Grid>
</StackPanel>
</Window>
public MainWindow()
{
InitializeComponent();
cbController.SelectedIndex = 0;
xkcdMI.Click += xkcdMI_Click;
nineGagMI.Click += nineGagMI_Click;
redditMI.Click += redditMI_Click;
Load("http://xkcd.com/atom.xml");
}
private void Load(string feedUrl)
{
var reader = XmlReader.Create(feedUrl);
var feed = SyndicationFeed.Load<SyndicationFeed>(reader);
svMain.Children.Clear();
var tmpStack = new StackPanel();
foreach (var item in feed.Items)
{
var browser = new WebBrowser();
GetHTML(ref browser, item);
tmpStack.Children.Add(browser);
}
svMain.Children.Add(new ScrollViewer()
{
Content = tmpStack,
Height = svMain.Height
});
}
Okay, so I am unfortunately answering my own question, without going crazy and rewriting a bunch of stuff. So in my research, it turns out that in .NET 4.0 and 4.5, StackPanel is great with ScrollView... so long as you know what you are doing! I agree with the comments left that MVVM is what is happening in the background, and my code actually reflects what I had to change it to to try and get it working, even when I had proper models, views, listeners/handlers etc (which are all built into the framework).
So the answer to my question, given the above, and this link I found that setting the size of my Grid, which contained the scrollview and other such fun made it work as advertised. The problem is evidently that the Grid was reporting to the scrollview that it was indefinably big, and so the scrollviewer could be too. So... for my code above, I need to handle when the whole form is resized and set the height of my grid accordingly.
<Grid Name="svMain" Height="550">
</Grid>
How can I get ScrollViewer to work inside a StackPanel?

Categories

Resources