question

njsokalski avatar image
0 Votes"
njsokalski asked RobCaplan edited

Scrolling to Offscreen & Recycled Items

I have a ViewHolder that contains a RecyclerView. I need to synchronize the scrolling of these nested RecyclerView(s). I have managed to synchronize manual scrolling using an OnScrollListener, but I am having trouble with programmatic scrolling. I will start by mentioning that the last item in many of the nested RecyclerView(s) will often be partially offscreen, and it may even be too large to be completely visible all at once. This is by design. When using SmoothScrollToPosition, it does not always seem to work. There are also times when all of one of the RecyclerView's items are scrolled offscreen after synchronizing with the others, therefore making all items return null. How do I scroll to items that are scrolled offscreen or have been recycled, and what is the best way to synchronize the scroll positions when programmatically scrolling to items that do not yet exist?

dotnet-xamarin
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

1 Answer

JarvanZhang-MSFT avatar image
0 Votes"
JarvanZhang-MSFT answered JarvanZhang-MSFT commented

Hello,​

Welcome to our Microsoft Q&A platform!

I created a basic demo about the nested recyclerView. Customize the OnScrollListener interface to override the OnScrolled event and achieve the logic in it. Because scrolling programmatically doesn't trigger the scrolled event, please execute the scroll-to-position command for each recyclerView.

Here is the related sample code, please check:

layout.xml

<LinearLayout ...
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="match_parent">
        <Button
            android:id="@+id/btn_1"
            android:text="button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/test_recyclerView1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/btn_2"
            android:text="button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/test_recyclerView2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</LinearLayout>

Activity class

public class MainActivity : AppCompatActivity
{
    public static MainActivity Instance;
    public static RecyclerView recyclerView1;
    public static RecyclerView recyclerView2;
    public static OnScrollListener1 scrollListener1;
    public static OnScrollListener2 scrollListener2;

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        Xamarin.Essentials.Platform.Init(this, savedInstanceState);
        // Set our view from the "main" layout resource
        SetContentView(Resource.Layout.activity_main);

        Instance = this;
        scrollListener1 = new OnScrollListener1();
        scrollListener2 = new OnScrollListener2();

        recyclerView1 = FindViewById<RecyclerView>(Resource.Id.test_recyclerView1);
        var layoutManager = new LinearLayoutManager(this);
        var data = new ObservableCollection<TestModel>();
        //add the data...
        TestAdapter testAdapter = new TestAdapter(data, this);
        recyclerView1.SetLayoutManager(layoutManager);
        recyclerView1.SetAdapter(testAdapter);

        
        recyclerView2 = FindViewById<RecyclerView>(Resource.Id.test_recyclerView2);
        var data2 = new ObservableCollection<TestModel>();
        //add the data...
        var layoutManager2 = new LinearLayoutManager(this);
        TestAdapter testAdapter2 = new TestAdapter(data2, this);
        recyclerView2.SetLayoutManager(layoutManager2);
        recyclerView2.SetAdapter(testAdapter2);

        recyclerView1.AddOnScrollListener(scrollListener1);
        recyclerView2.AddOnScrollListener(scrollListener2);

        FindViewById<Button>(Resource.Id.btn_1).Click += MainActivity_Click;
    }

    //scroll the recyclerView1 programmatically
    private void MainActivity_Click(object sender, EventArgs e)
    {
        Action runnable = new Action(() =>
        {
            recyclerView1.SmoothScrollToPosition(0);
            recyclerView2.SmoothScrollToPosition(0);
        });

        recyclerView1.Post(runnable);
    }
}

Custom scrollListener classes

public class OnScrollListener1 : RecyclerView.OnScrollListener
{
    public OnScrollListener1()
    {
    }
    public OnScrollListener1(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
    {
    }
    public override void OnScrolled(RecyclerView recyclerView, int dx, int dy)
    {
        base.OnScrolled(recyclerView, dx, dy);
        var recyclerView2 = MainActivity.recyclerView2;
        recyclerView2.RemoveOnScrollListener(MainActivity.scrollListener2);
        recyclerView2.ScrollBy(dx, dy);
        recyclerView2.AddOnScrollListener(MainActivity.scrollListener2);
    }
}

public class OnScrollListener2 : RecyclerView.OnScrollListener
{
    public OnScrollListener2()
    {
    }
    public OnScrollListener2(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
    {
    }
    public override void OnScrolled(RecyclerView recyclerView, int dx, int dy)
    {
        base.OnScrolled(recyclerView, dx, dy);
        var recyclerView1 = MainActivity.recyclerView1;
        recyclerView1.RemoveOnScrollListener(MainActivity.scrollListener1);
        recyclerView1.ScrollBy(dx, dy);
        recyclerView1.AddOnScrollListener(MainActivity.scrollListener1);
    }
}


Best Regards,

Jarvan Zhang


If the response is helpful, please click "Accept Answer" and upvote it.

Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi, @njsokalski
May I know whether your issue has been solved or not? If not, please share it in here. We can work together to figure it out.

0 Votes 0 ·