Xamarin.Forms icon indicating copy to clipboard operation
Xamarin.Forms copied to clipboard

[Bug] [Android] CollectionView has incorrect VerticalOffset after using ScrollTo method

Open InquisitorJax opened this issue 4 years ago • 7 comments

Description

After calling ItemsCollectionView.ScrollTo(0, animate: false) on Android, the collectionview will report the incorrect VerticalOffset on the scrolled event

Steps to Reproduce

  1. Create new Xamarin project from Shell Template
  2. Add Items to MockDataStore to make the landing page scrollable
  3. Add a button to the UI which scrolls the CollectionView: ItemsCollectionView.ScrollTo(0, animate: false)
  4. Add a Scrolled event handler for the collection view to debug the VerticalOffset property
  5. 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

image

InquisitorJax avatar Jun 05 '20 09:06 InquisitorJax

@InquisitorJax What version of Xamarin.Forms are you using?, could you attach the reproduction sample?

jsuarezruiz avatar Jun 05 '20 10:06 jsuarezruiz

@InquisitorJax What version of Xamarin.Forms are you using?, could you attach the reproduction sample?

latest

InquisitorJax avatar Jun 05 '20 10:06 InquisitorJax

CVBugs.zip

InquisitorJax avatar Jun 05 '20 10:06 InquisitorJax

Same here. Any workarounds?

SangI762 avatar Aug 16 '22 19:08 SangI762

Have the same problem

sokrog avatar Dec 16 '22 16:12 sokrog

Same here. Any workarounds?

valentasm1 avatar Mar 23 '24 14:03 valentasm1

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;
        }
    }
}

valentasm1 avatar Apr 02 '24 09:04 valentasm1