Hello again!
Ever used ViewPagers where users are able to swipe through your app kind of like how users swipe through Tinder profiles? Yes, those kinds that fill up the full screen.
But okay, with Tinder, the data already on screen can be thrown away (if it’s something cheap enough like text) then cached somewhere in its own database system since the data will not be available at the same instance the user is using the application.
However, what about caching all of those profile pictures? Also, what if one of those profile pictures change?
Sure, a developer can just do return PositionNone;
as he or she overrides GetItemOffset(int position)
and then do a NotifyDataSetChanged();
against the ViewPagerAdapter
. Let’s say for our purposes, we want to do something similar for our application. At the same time, let’s multiply the limited amount of profile pictures by 1000. That phone’s or tablet’s RAM cannot handle that all at once for sure!
And then to handle that kind of situation requires quite some complex logic by keeping track of the view’s position possibly by using a DataSetObserver
that has to be registered. The next developer who comes on the project wouldn’t understand it right away.
There’s a better way!
Because the Android framework gets updated after each API update, there’s now the SnapHelper
class along with its subclasses PagerSnapHelper
and LinearSnapHelper
. These classes have been around a little over a year ago, so they are sure to be quite stable. The most recent Google Play app even utilizes SnapHelper
. Here, we will be focusing on the PagerSnapHelper
class.
To use it, we’ll first have to instantiate our RecyclerView
just as usual with our RecyclerView.Adapter
and our LinearLayoutManager
set horizontally. Next, it will be good to subclass the PagerSnapHelper
class so that we will be able to keep track of the position as we will want. Then, we will just need to override the FindTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY)
so that as the RecyclerView
scrolls to the viewable position, the essential data to be displayed at that time will be bound there instead of OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
by loading our variable there. Why we’re doing this is because as we snap along to the other child views, the RecyclerView
will still try to bind the next 2 views ahead or behind the current position as it predicts those views are upon interaction.
With that said, the code will look like this:
class MySnapHelper : PagerSnapHelper { // C# action that will take an integer as a parameter readonly ActionpositionTracker; public MySnapHelper(Action tracker) { positionTracker = tracker; } public override int FindTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) { var snapTracker = base.FindTargetSnapPosition(layoutManager, velocityX, velocityY); positionTracker.Invoke(snapTracker); return snapTracker; } }
Looking good! Now in our OnCreate(Bundle savedInstanceState)
method we can do this:
... ListurlList = ... ; // Data that exists from a source var rv = new RecyclerView(this); // arbitrary RecyclerView var lm = new LinearLayoutManager(this, LinearLayoutManager.Horizontal, false); // adapter code omitted ... rv.SetLayoutManager(lm); // Where the data binding will happen var sh = new MySnapHelper(viewingPosition => { // Our data at position var url = urlList[viewingPosition]; // Here's our view: var viewAtPosition = lm.FindViewForPosition(viewingPosition); // Or if you prefer to get the ViewHolder var viewHolderAtPosition = rv.FindViewHolderForAdapterPosition(viewingPosition); // don't forget to make the cast to your custom ViewHolder! :) // Do awesome stuff with url along with the View or ViewHolder bound here!!! }); ...
And that’s all there is to it! This is much cleaner and a lot easier for other developers to understand! 🙂
Until next time,
Brian.