Reputation: 157
I've been using this example to build a mobile app in xamarin to interact with my chatbot. The problem I'm having is that the ListView that displays the messages between the bot and user doesn't automatically scroll to the bottom when a new message is displayed in it. Here's the code:
<ContentPage.Content>
<StackLayout Margin="5">
<ListView x:Name="ChatListView"
VerticalOptions="FillAndExpand"
SelectedItem="{Binding SelectedMessage}"
ItemsSource="{Binding BotMessages, Mode=TwoWay}"
BackgroundColor="Azure"
HasUnevenRows="True"
SeparatorVisibility="None"
ItemTemplate="{StaticResource ChatDataTemplateSelector}"/>
<StackLayout Orientation="Horizontal">
<Entry Placeholder="Ask a question.."
Margin="5"
Keyboard="Chat"
Text="{Binding CurrentMessage, Mode=TwoWay}"
HorizontalOptions="FillAndExpand"
Completed="Entry_Completed"/>
<Button Text="Send" Command="{Binding SendCommand}"/>
</StackLayout>
</StackLayout>
</ContentPage.Content>
Is there some way to get the Listview to scroll automatically to the bottom when a new message is recieved?
Upvotes: 1
Views: 2198
Reputation: 25956
This answer has elements from @Julio's comment:
For example, you could subscribe to some viewmodel event or property from the view to know when an item has been added and then manually invoke the call ChatListView.ScrollTo(item, ScrollToPosition.MakeVisible, true).
Combined with the fact that you're ListView has a property binding for SelectedItem
:
SelectedItem="{Binding SelectedMessage}"
In your C# code behind, we can connect to the PropertyChanged event and watch for changes in SelectedMessage
and react to it by calling ScrollTo
directly on our ListView
.
public partial class MainPage : ContentPage
{
MainViewModel viewModel;
public MainPage()
{
InitializeComponent();
viewModel = (MainViewModel) BindingContext;
viewModel.PropertyChanged += ViewModel_PropertyChanged;
}
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(viewModel.SelectedMessage):
MessagesListView.ScrollTo(viewModel.SelectedMessage,
ScrollToPosition.MakeVisible,
false);
break;
}
}
}
Upvotes: 0
Reputation: 2119
To scroll to a certain item, all you have to do is this:
ChatListView.ScrollTo(item, ScrollToPosition.MakeVisible, true);
If you want a list that will scroll automatically to the new/updated items when used with an observable collection, you can try with this extension:
namespace Your.Namespace.For.Custom.Controls
{
public class AutoScrollListView : ListView
{
private INotifyCollectionChanged _previousObservableCollection;
public AutoScrollListView(ListViewCachingStrategy cachingStrategy)
: base(cachingStrategy)
{
}
public AutoScrollListView()
: base()
{
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == nameof(ItemsSource))
{
if (_previousObservableCollection != null)
{
_previousObservableCollection.CollectionChanged -= OnItemsSourceCollectionChanged;
_previousObservableCollection = null;
}
if (ItemsSource is INotifyCollectionChanged newObservableCollection)
{
_previousObservableCollection = newObservableCollection;
newObservableCollection.CollectionChanged += OnItemsSourceCollectionChanged;
}
}
}
private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (var item in e.NewItems)
{
// Scroll to the item that has just been added/updated to make it visible
ScrollTo(item, ScrollToPosition.MakeVisible, true);
}
}
}
}
}
To use it, remember to add its namespace to the XAML file:
<Page
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:custom="clr-namespace:Your.Namespace.For.Custom.Controls"
x:Class="Your.Namespace.YourPage">
...
<ContentPage.Content>
<StackLayout Margin="5">
<custom:AutoScrollListView
x:Name="ChatListView"
VerticalOptions="FillAndExpand"
SelectedItem="{Binding SelectedMessage}"
ItemsSource="{Binding BotMessages, Mode=TwoWay}"
BackgroundColor="Azure"
HasUnevenRows="True"
SeparatorVisibility="None"
ItemTemplate="{StaticResource ChatDataTemplateSelector}" />
<StaLayout Orientation="Horizontal">
<Entry
Placeholder="Ask a question.."
Margin="5"
Keyboard="Chat"
Text="{Binding CurrentMessage, Mode=TwoWay}"
HorizontalOptions="FillAndExpand"
Completed="Entry_Completed"/>
<Button Text="Send" Command="{Binding SendCommand}" />
</StackLayout>
</StackLayout>
</ContentPage.Content>
...
</Page>
Please keep in mind that, as mentioned above, this custom list will only scroll automatically if the property ItemsSource
is bound to an observable collection.
I hope it helps!
Upvotes: 4