Johan Karlsson: Building the Swiper control – Part 2 (Android)

This is part two in a three-part series about the Swiper control. The first part can be read here, if you are new to custom control/renderers in Xamarin I suggest you read it first since I’ll skip the parts that the renderers have in common.

Why didn’t I just use the native controls?

I’ve been getting some comments about the iOS and Android implementation for this, stating that I could have done this a lot simpler by using the native controls (the ViewPager and the UICollectionView). This is perfectly true, but it wasn’t the purpose why I created the control.

The reasons I choose to do it the way I did was

  • I originally planned to add custom graphic effects
  • I wanted to see if I could make it perfectly fluid on my own
Having that said, I might convert the control to use the ViewPager and the UICollectionView and create another experimental Swiper as separate control. I got lazy for the WP implementation of the renderer and used a Panorama. So I’m kind of in between the two ways to do this.

So what’s the theory behind the Droid renderer?

I went even more back to basic this time. I decided to juggle pure bitmaps and override the Draw(…) method of the renderer. This means we are doing pure rendering on demand of the entire control. 
If we start from the top, you’ll see that this time, the renderer inherits from ViewRenderer where View is the thin wrapper for an Android View which is the most basic building block for an Android GUI thingy. View itself inherits from java.lang.Object so it’s pretty much bare metal from here on.
   public class SwiperRenderer : ViewRenderer<Swiper, View>
As with the iOS version we need to override a few methods to get going
  • OnElementChanged
  • OnElementPropertyChanged
  • Draw
  • OnTouchEvent

OnElementChanged

The first one being OnElementChanged which is called by Xamarin Forms when it’s time to create a platform specific object. (I don’t like to use the word Native since it implies that Xamarin isn’t native). Anyhow, the method looks like this. It’s way shorter than the iOS counter-part.

  protected override void OnElementChanged(ElementChangedEventArgs<Swiper> e)
  {
       base.OnElementChanged(e);
            
       UpdateSizes();

       _rootView = new View(Context);
       SetNativeControl(_rootView);
  }

The first thing we do is call UpdateSizes that simple copies size data into local variables for easier lookup later on.
  private void UpdateSizes()
   {
        if (this.Element == null)
        {
            return;
        }

        if (this.Width > 0 && this.Height > 0)
        {
            _width = this.Width;
            _halfWidth = _width / 2;

            _height = this.Height;
            _halfHeight = _height / 2;
        }
   }

Then we create the platform specific control and set it as the “Native” control. Since this is Android we need to pass the context to every corner of our code. I’m pretty sure the Android team use that as the solution to everything in life, as long as we have a context… We’re fine!
At this point we have a control that will do the rendering for us.

OnElementPropertyChanged

This method is a lookalike to the iOS counterpart and I should really look into sharing some more code here by abstracting the events that go on in the renderers. All renderers have an InitializeImages method for example. I could define an interface for all common stuff and create a platform agnostic controller… Well, I didn’t so we’re stuck with code duplication for the time being. I didn’t include the whole method in the sample below, simply the first two as an example of what it looks like.
        protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            if (e.PropertyName == Swiper.SourceProperty.PropertyName)
            {
                InitializeImages();
            }

            if (e.PropertyName == Swiper.WidthProperty.PropertyName || e.PropertyName == Swiper.HeightProperty.PropertyName)
            {
                UpdateSizes();
            }

            if (e.PropertyName == Swiper.SelectedIndexProperty.PropertyName)
            {
                // TODO Check for index overrun
                if (this.Element.SelectedIndex > 0 &&
                    _currentImageUrl != this.Element.Source[this.Element.SelectedIndex])
                {
                    _currentImageUrl = this.Element.Source[this.Element.SelectedIndex];
                    InitializeImages();
                }
            }

            // Code omitted (there’s more in this method)
    }

As with the iOS version, we listen for changes in properties and call the appropriate methods to handle this change. For example, if we change the source we need to reinitialize the images. If we change the width or height we need to update sizes. Those sizes are needed to render later on.

Async image downloading

I played around with a couple of different approaches to async image downloading. I ended up with the basic WebClient since it would be cool to have download progress if the images are large. Looking at the code now, I realize that it’s not fully implemented yet. I registered an issue (#13) for this and hopefully I’ll get something done. The downloading works but the showing the progress is not completed yet. We just draw a loading text.

We directly convert the downloaded bits into a Android Bitmap object.

      private async Task LoadWithProgress()
        {
            try
            {
                var webClient = new WebClient();
                webClient.DownloadProgressChanged += webClient_DownloadProgressChanged;

                var bytes = await webClient.DownloadDataTaskAsync(new Uri(_url));
                _bitmap = await BitmapFactory.DecodeByteArrayAsync(bytes, 0, bytes.Length);

                if (Completed != null && _bitmap != null)
                {
                    Completed(this);
                }
            }
            catch (Exception ex)
            {
                Log.Debug(SwipeRenderer, Exception loading image {0} using WebClient, _url);
            }
        }

        void webClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            int i = 42;
        }

Caching

The caching in the Android version is just as basic. We simply store a dictionary of string and AsyncImageLoader for now. It’s the same with all three platforms and a common caching strategy is required here.

Tracking your fingers

Handling touch events is easy. Simply override the OnTouchEvent method and check what type of event is being raised. We can break down this method in three parts categorized by the action type.

  1. The user starts a touch and we get a MotionEventActions.Down. We store the start location of the X-axis of the touch position to keep for later reference.
  2. If the action is Move we calculate the offset value of the current finger position on the X-axis. Then we call Invalidate() to force a redraw of the control.
  3. When the user lets go of the image we need to check if the image has moved far enough to count as a image switch motion and in that case, what direction. If it’s not a switch we still need to animate the images back into the original place.

 public override bool OnTouchEvent(MotionEvent e)
        {
            switch(e.Action)
            {
                case MotionEventActions.Down:
                    _swipeStartX = e.GetX();
                    return true;


                case MotionEventActions.Move:
                    _swipeCurrectXOffset = e.GetX()  _swipeStartX;
                    Invalidate();
                    return true;

                case MotionEventActions.Up:
                    var index = this.Element.Source.IndexOf(_currentImageUrl);
                    
                    if(Math.Abs(_swipeCurrectXOffset)>30) // TODO Add a variable for the trigger offset?
                    {
                        if(_swipeCurrectXOffset > 0 && index > 0)
                        {
                            // Left swipe
                            AnimateLeft(index);
                        }
                        else if (_swipeCurrectXOffset < 0 && index < this.Element.Source.Count() 1)
                        {
                            // Right swipe
                            AnimateRight(index);
                        }
                        else
                        {
                            AnimateBackToStart();
                        }
                    }
                    else
                    {
                        AnimateBackToStart();
                    }
                    
                    return true;

                
            }

            return base.OnTouchEvent(e);
        }

Animation of images

Each platform offers different kind of animation APIs. In this sample I’ve chosen to use the static ValueAnimator to animate a float value. We can take the AnimateLeft(…) method as an example. It’s dead simple to use. Simply state the initial value and the end value in the ValueAnimator.OfFloat(…). Hook up events for Update (that fires every frame) and for AnimationEnd(that fires when the end result has been achieved).
For this specific function we continue to animate the current x-offset to the left and when we hit the end we set a new _currentImageUrl (that represents the center image) and reinitialize all images so the new images are displayed.
       private void AnimateLeft(int index)
        {
            var animator = ValueAnimator.OfFloat(_swipeCurrectXOffset, this.Width);
            animator.Start();

            animator.Update += (object sender, ValueAnimator.AnimatorUpdateEventArgs args) =>
            {
                _swipeCurrectXOffset = (float)args.Animation.AnimatedValue;
                Invalidate();
            };
            animator.AnimationEnd += (object sender, EventArgs args) =>
            {
                _swipeCurrectXOffset = 0f;
                _currentImageUrl = this.Element.Source[index  1];
                InitializeImages();
            };
        }

Drawing

The method signature of Draw(…) looks like this.
    public override void Draw(Android.Graphics.Canvas canvas)

It passes in a single argument in the form of a Canvas object. This Canvas represents the drawable surface that we have access to. The Draw(…) method is called everytime Invalidate() is called else where in the code or when the operating system wants you to update.

The method is quite repetitive so I’ll just take a sample out of it.

     // Clear the canvas
            canvas.DrawARGB(255, 255, 255, 255);

            if(_centerBitmap != null && _centerBitmap.Bitmap != null)
            {
                var dest = CalculateCentrationRect(_centerBitmap.Bitmap);
                 canvas.DrawBitmap(_centerBitmap.Bitmap, dest.Left + _swipeCurrectXOffset, dest.Top, null);
            }
            else if (_centerBitmap != null)
            {
                DrawLoadingText(canvas, 0);
            }

This is pretty much what’s going on, but times three. One for each image. First we need to clear the frame from the previous stuff drawn onto it. We do that with the canvas.DrawARBG(…) call. This could easily be extended to take a background color or image instead.

Then for each image we either draw the image or draw a loading text if the image isn’t downloaded yet.

Summary

You could have made this a lot simpler and perhaps I’ll revisit this control and redo it. But as a learning experience it was quite fun. Feel free to steal any code! My code is your code!
Details

Matt Ward: NuGet Support in Xamarin Studio 5.9.2

Changes

  • NuGet 2.8.5 support
  • NuGet warning and error messages in Status Bar

More information on all the changes in Xamarin Studio 5.9.2 can be found in the release notes.

NuGet 2.8.5 support

Xamarin Studio now supports NuGet 2.8.5.

NuGet 2.8.5 adds support for three new .NET target frameworks: DNX, DNXCore and Core.

With NuGet 2.8.5 supported you can now install the latest pre-release version of xUnit.

NuGet warning and error messages in Status Bar.

Xamarin Studio 5.9 has a new native Status Bar on the Mac. This new Status Bar has a smaller width so the NuGet warning and error messages could be too long to be displayed. The screenshots below show a NuGet warning and error message in Xamarin Studio 5.9 that do not fit in the Status Bar.

NuGet warning message truncated in status bar

NuGet error message truncated in status bar

In Xamarin Studio 5.9.2 the NuGet Status Bar messages have been shortened so they can be displayed in the new Status Bar without being truncated. The screenshots below show the new format of the NuGet warning and error messages shown in the Status Bar.

Shortened NuGet warning message in status bar

Shortened NuGet error message in status bar

Details

Frank Krueger: Introducing the iCircuit Gallery

image

TLDR; I wrote a website to share circuits made with my app iCircuit and I hope you’ll check it out.

Finally, a place to share

iCircuit users create amazing things. For the past 5 years of reading support emails, I have been privy to just a fraction of these wonders. Circuits far bigger than I ever thought iCircuit could handle – circuits that were clever and required me going back to my college texts to understand – and circuits that just made me laugh. I learned something from each of them.

It was a shame that all these wonders were hidden in my inbox. Well, no more.

Introducing, the iCircuit Gallery – a community driven web site full of circuits.

Now iCircuit users have a place to upload their and share circuits with the world. Each circuit is lovingly rendered in SVG and can contain rich textual descriptions of the circuit. Even if you’re not an iCircuit user, you can still learn a lot from the gallery.

I have seeded the site with the standard example circuits and Windows Phone users have (believe it or not) been able to upload circuits for years – so the site has some initial work in it already. But,

I am asking iCircuit users to share their designs – big or small – novel or standard – brilliant or otherwise. Share them with the world! There is great satisfaction to be had in sharing your work with others. I hope also to see educational examples pop up that take full advantage of the ability to document the circuit.

Simply click the Upload button, create an account (email optional), and pick the files off your device. Right now, that means Mac and Windows users have the easiest time with the gallery. I am working on iOS and Android updates to make uploading a snap there too.

I am very excited to see your designs!

Future Improvements

I have lots of ideas on how to improve upon this initial release but hope to get some feedback from the community before pursuing any of them. For example, I hope to add Tags to help organize things and Comments if contributors desire.

Also, I will be integrating the gallery into the app to make browsing and uploading easier. Keep your eye out for updates!

Colophon

Oh my, I wrote a website! With servers and all that. Part of the reason it took me 5 years to write this thing is that I am scared to death of running servers. My ability to manage a server only gives it a life span of a few months before some hacker is using it as a spam bot.

So what’s changed? App hosting is what’s changed. I adored Google App Engine for it remedied the whole server problem – host apps instead of servers – genius! They provided a great database and a great toolset.

But it wasn’t .NET and I always wanted to run the iCircuit engine on the server.

And then Azure came along. Azure has a million enterprisy “solutions” and one awesome service called Mobile Services. But they their Cloud Service was the most confusing thing ever. It acted like an app host but also acted like a server. Which was it? So very confusing.

Well, Azure fixed that with a Web Apps service. Finally, after that little marketing spin and an assurance that I’m not managing a server, I became a customer.

Building the site was a snap with ASP.NET MVC. My only possible mistake is that I’m using Azure’s Table Storage – not sure how that decision will pan out. I foresee a future of migrating to SQL…

I am also scared to death about cloud pricing. Every page on the site has an HTTP and memory cache of 5 minutes. It’s ridiculously high. Almost as ridiculously high as my fear of cloud service pricing.

But there’s only one way to find out…

Details