I was recently working on a walkthrough library that I can reuse in my apps, and I wanted to blend the background colors together as you swipe through each page using a scroll event. After implementing it, I planned to add animations and other effects in the same way. Turns out this wasn’t as straight forward as I thought, but Rx allowed me to use a different approach to implement it. In this post we will take a look at the walkthrough control from a high level using Xamarin.Android, and in more detail, how I used Reactive Extensions (Rx) to achieve the desired result by enhancing the ViewPager.ScrolledEvent.

Tutorial screens are easy. Right?

Every app has one these days; A few pages when you first open the app that tell you how the app works. Each page usually has an icon along with a description below it, and varying amounts of polish along with it. Setting this up in Xamarin.Android is pretty easy, we just add an Activity that has a ViewPager and a FragmentStatePagerAdapter as members to our project. The ViewPager is the UI control which will manage the transitions between pages and provide us with the scroll event we want, and our FragmentStatePagerAdapter will feed it the Fragments that represent each page in the walkthrough.

Shortfalls of the PageTransformer

If you are an Android developer you might be wondering why we aren’t using the recommended approach from the Android walkthrough Using ViewPager for Screen Sliders, which is to use a PageTransformer. After wrestling with it for a bit, I eventually ditched it in favour of Rx because:

  1. The project is written using the MVVM pattern and PageTransformer only takes a View. I wanted everything from coloring, etc. to be driven by the View Models.
  2. We need a scroll direction and you’re only given the current position.
  3. It’s somewhat difficult to debug because its tranformPage method is called simultaneously for 2 or 3 Views.
  4. I wanted to have the background do a nice color blending transition, but the out of the box experience made this very difficult.

Building a Better ViewPager.PageScrolled with Reactive Extensions

Let’s have a look at how we replaced the PageTransformer with an enhanced version of the PageScrolled event using Rx. Have a glance at this code You can probably see what it’s doing without any further explanation, which is one of the nice things about Rx -readability. When you write code using the Rx, it starts to tell a story. There are no callbacks, no jumping around code files. Just a nice block of code that tells you where data comes in and how it gets processed.

Observable.FromEventPattern<ViewPager.PageScrolledEventArgs> (
    x => _viewPager.PageScrolled += x,
    x => _viewPager.PageScrolled -= x)
  .Select (args => args.EventArgs)

Starting at the beginning, we convert the event into an IObservable and provide a lambda used for subscribing and unsubscribing from the event. This is boilerplate Rx functionality and makes managing the event easy because, each time the event fires off, this observable will emit a value. In our case, as the pages scroll, we will be notified of all those changes as a stream of data. We can also filter down the results. In this case, we are only really interested in the EventArgs and not the sender.

  .StartWith(new ViewPager.PageScrolledEventArgs(0, 0, 0))
  .PairWithPrevious()
  .Skip(1)

Since the observable is essentially a stream of events, we can do cool things with it, such as prime the stream. In this case, we tell the observable to start with a default value. Since this observable is based off of events, there is no guarantee when the first one will fire off, so we just instruct the observable to use this as a starting point. From there, we are going to use an extension method to pair the last two events data. This can be somewhat of pain to code traditionally using events because you need to store instance variables each time the event fires off and then do a comparison. Finally, we Skip the first node that the ViewPager event generates because it is not of interest to us. Again, Rx makes this functionality easy, no instance variables, no state management, just skip the first notification in the stream.

  .Select(args => {
    var scrollDirection =
        args.Item1.Position != args.Item2.Position
            ? ScrollDirection.Center
            : args.Item1.PositionOffset > args.Item2.PositionOffset
                ? ScrollDirection.Left
                : ScrollDirection.Right;
    return new {
        Current =  _pagerAdapter.GetItem(args.Item2.Position) as Walkthrough, 
        Next = _pagerAdapter.GetItem(args.Item2.Position+1) as Walkthrough, 
        ScrollDirection = scrollDirection,
        args.Item2.PositionOffset,
    };
    })

So far, this Observable produces nodes of type Tuple<PageScrolledEventArgs, PageScrolledEventArgs>. What we really want to do though is find out which direction we are moving, which page we are currently on, which page is next and how far we are scrolled. Rx allows us to take the previous results that we had and further refine them. In this case, we just create an anonymous type to capture this information for us.

    .Where(args => args.ScrollDirection == ScrollDirection.Center || args.Current != null && args.Next != null)

Since Rx interoperates with LINQ, we can use that to filter out any results that are not of interest to us. Here we filter the sequence to ensure we always have the data we need in the following Subscribe.

    .Subscribe (args => {
    if (args.ScrollDirection == ScrollDirection.Center) {
        args.Current.View?.SetBackgroundColor (args.Current.ViewModel.BackgroundColor.ToNative());
    } else if (args.ScrollDirection == ScrollDirection.Left || args.ScrollDirection == ScrollDirection.Right) {
        var color = args.Current.ViewModel.BackgroundColor.Lerp (args.Next.ViewModel.BackgroundColor, args.PositionOffset).ToNative ();
        args.Current.View?.SetBackgroundColor (color);
        args.Next.View?.SetBackgroundColor (color);
    }   
    }).DisposeWith(ControlBindings);

The final thing that we do is to subscribe to the stream of data. The nice thing about everything we’ve done so far is that by the time we reach this Subscribe the code is quite simple. When centered we simply use the pages background color, and when scrolling left or right we blend our two colors and set the background color of both pages to the result.

setitforgetit

Ultimately, this is one of the things that is so neat about Rx. We setup this code block and then that is it. Just like any great Ronco product.

If you’re a .NET or a Xamarin developer and you don’t use Rx yet, you should start. You can simplify problems or enhance your existing apps with Rx while improving readability and consistency with the fluent syntax.

We have provided a full code example of the solution above running on Xamarin.Android at https://github.com/TheEightBot/RxPageScrolled