Xamarin.Forms
Xamarin.Forms copied to clipboard
[Bug] [Android] CollectionView has incorrect VerticalOffset after using ScrollTo method
Description
After calling ItemsCollectionView.ScrollTo(0, animate: false) on Android, the collectionview will report the incorrect VerticalOffset on the scrolled event
Steps to Reproduce
- Create new Xamarin project from Shell Template
- Add Items to MockDataStore to make the landing page scrollable
- Add a button to the UI which scrolls the CollectionView: ItemsCollectionView.ScrollTo(0, animate: false)
- Add a Scrolled event handler for the collection view to debug the VerticalOffset property
- Run the app: Scroll to the bottom of the list, and then press the button (which programmatically scrolls the collectionview to the top) - now continue to scroll -> the collection view is not reporting incorrect VerticalOffset values (ie. when back to the first position, verticalOffset is no longer 0)
This works as expected on iOS.
Expected Behavior
- VerticalOffSet should report 0 value when collectionview is at first item after calling ScrollTo method.
Actual Behavior
- The collectionview is not resetting the verticalOffset value after calling ScrollTo
Basic Information
- Version with issue: latest
- Last known good version: ??
- IDE: latest
- Platform Target Frameworks: Tested on API 29 device
Screenshots
@InquisitorJax What version of Xamarin.Forms are you using?, could you attach the reproduction sample?
@InquisitorJax What version of Xamarin.Forms are you using?, could you attach the reproduction sample?
latest
Same here. Any workarounds?
Have the same problem
Same here. Any workarounds?
This solution solved issue for me. At least for now. Need more testing. Create custom collection view rendered and create custom RecyclerViewScrollListener
[assembly: ExportRenderer(typeof(CollectionView), typeof(AppCollectionViewRenderer))]
namespace Mobile.Droid.Renderers
{
public class AppCollectionViewRenderer : CollectionViewRenderer
{
public AppCollectionViewRenderer(Context context) : base(context)
{
}
protected override RecyclerViewScrollListener<GroupableItemsView, IGroupableItemsViewSource> CreateScrollListener()
{
return new CustomAppRecyclerViewScrollListener<GroupableItemsView, IGroupableItemsViewSource>(ItemsView, ItemsViewAdapter);
}
}
}
All code is identical to xamarin side except i removed call to base and always calculate _verticalOffset from recyclerView
public class CustomAppRecyclerViewScrollListener<TItemsView, TItemsViewSource> : RecyclerViewScrollListener<TItemsView, TItemsViewSource>
where TItemsView : ItemsView
where TItemsViewSource : IItemsViewSource
{
private int _horizontalOffset, _verticalOffset;
private readonly TItemsView _itemsView;
public CustomAppRecyclerViewScrollListener(TItemsView itemsView, ItemsViewAdapter<TItemsView, TItemsViewSource> itemsViewAdapter, bool getCenteredItemOnXAndY = false) : base(itemsView, itemsViewAdapter, getCenteredItemOnXAndY)
{
_itemsView = itemsView;
ItemsViewAdapter = itemsViewAdapter;
}
public override void OnScrolled(RecyclerView recyclerView, int dx, int dy)
{
//Sending to based trigger event twice with wrong _verticalOffset value
//Not sending could be a problem for some cases
//base.OnScrolled(recyclerView, dx, dy);
// TODO: These offsets will be incorrect upon row size or count change.
// They are currently provided in place of LayoutManager's default offset calculation
// because it does not report accurate values in the presence of uneven rows.
// See https://stackoverflow.com/questions/27507715/android-how-to-get-the-current-x-offset-of-recyclerview
_horizontalOffset += dx;
_verticalOffset += dy;
int offset = recyclerView.ComputeVerticalScrollOffset();
_verticalOffset = offset;
var (first, center, last) = GetVisibleItemsIndex(recyclerView);
var context = recyclerView.Context;
var itemsViewScrolledEventArgs = new ItemsViewScrolledEventArgs
{
HorizontalDelta = context.FromPixels(dx),
VerticalDelta = context.FromPixels(dy),
HorizontalOffset = context.FromPixels(_horizontalOffset),
//Old code aka original from xamarin github
//VerticalOffset = context.FromPixels(_verticalOffset),
VerticalOffset = _verticalOffset,
FirstVisibleItemIndex = first,
CenterItemIndex = center,
LastVisibleItemIndex = last
};
_itemsView.SendScrolled(itemsViewScrolledEventArgs);
// Don't send RemainingItemsThresholdReached event for non-linear layout managers
// This can also happen if a layout pass has not happened yet
if (last == -1)
{
return;
}
switch (_itemsView.RemainingItemsThreshold)
{
case -1:
return;
case 0:
if (last == ItemsViewAdapter.ItemCount - 1)
{
_itemsView.SendRemainingItemsThresholdReached();
}
break;
default:
if (ItemsViewAdapter.ItemCount - 1 - last <= _itemsView.RemainingItemsThreshold)
{
_itemsView.SendRemainingItemsThresholdReached();
}
break;
}
}
}