Xamarin.Droid and MvvmCross: Setting Fonts in XML

In Android, your options are kind of small for setting a font purely in XML.

image

In this post, I’ll create a converter so we can specify the font in an MvvmCross binding.

Getting a font

Before going any further, you need to actually get and include a font in your android app.  For this example I will be using Fontin.  The main reason I’m using it is because it was easy to find and is free. Download the TTF versions and and include them in your project.  Add a new subdirectory to the Assets folder in your android project named “font”.  Drop the files into that folder and include them in your project.  Make sure that the build action is set to AndroidAsset.

image        image

If you get the following error, make sure that the font names are spelled exactly the same as the folder and file names, including capitalization.

java.lang.RuntimeException: native typeface cannot be made

In the example from my screenshots, my font name should be:  font/Fontin-Italic.ttf 

And again, capitalization matters.

Binding to Typeface

The first step is to use MvvmCross.  Next all you really need to do is bind directly to the Typeface property on the TextView.  They Typeface property is an instance of of Typeface (nothing surprising there) which meanst that you’ll need a converter.

  <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        local:MvxBind="Text Hello; Typeface StringToFont(FontName)" />

The converter is pretty straight forward:

public class StringToFontConverter : MvxValueConverter<string, Typeface>
{
    private static Dictionary<string, Typeface> _cache = new Dictionary<string, Typeface>();

    protected override Typeface Convert(string fontName, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        try
        {
            if (!fontName.StartsWith(@"font/")) fontName = @"font/" + fontName;
            if (!fontName.EndsWith(".ttf")) fontName += ".ttf";

            if (!_cache.ContainsKey(fontName))
            {
                _cache[fontName] = Typeface.CreateFromAsset(Application.Context.Assets, fontName);
            }

            return _cache[fontName];
        }
        catch (Exception e)
        {
            Android.Util.Log.Error("AndroidFont", e.ToString());

            return Typeface.Default;
        }
    }
}

First thing the converter does is to clean the input, ensuring that the font name starts with the directory and ends with the ttf extension. This makes the binding a bit easier in that we don’t have to remember to get the full font path correct.

It then check its static cache to see if it already has an instance of the the font, if not it creates one by calling Typeface.CreateFromAsset.  If creation fails it does some logging and return the default typeface.  This is important because in my testing VisualStudio hang pretty hard under some circumstances where errors were ignored.

Fire this up, and you’ll see that the font is in fact set.

One problem with this example is that we are forcing the ViewModel to know the correct name for the font.  In some cases that’s ok, in others, we won’t want to handle font in the VM layer.  Luckily we can use Tibet binding and just bind to a static string in the xml.  Just remember to surround it with single quotes.

<TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    local:MvxBind="Text Hello; 
                   Typeface StringToFont('Fontin-Bold')" />

Sample

Here’s a sample layout putting everything together.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <EditText
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="24dp"
        android:textSize="40dp"
        local:MvxBind="Text Hello; Typeface StringToFont('Fontin-Bold')" />

<!-- Binding to the View Model -->
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="16dp"
        android:text="Bind to Typeface:  " />
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="24dp"
        android:textSize="24dp"
        local:MvxBind="Text Hello; Typeface StringToFont(SelectedFont)" />
    <MvxSpinner
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="24dp"
        android:textSize="24dp"
        local:MvxBind="SelectedItem SelectedFont; ItemsSource FontNames;" />

<!-- Binding to a constant -->
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="16dp"
        android:text="Constant Typeface:  " />
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="24dp"
        android:layout_marginBottom="24dp"
        android:textSize="24dp"
        local:MvxBind="Text Hello; Typeface StringToFont('Fontin-Bold')" />

<!-- Error -->
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="16dp"
        android:text="Error Handling:  " />
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="24dp"
        android:layout_marginBottom="24dp"
        android:textSize="24dp"
        local:MvxBind="Text Hello; Typeface StringToFont('Not a font name')" />
</LinearLayout>

The edit box and first text box are bound to the value in the spinner.  The second text box is staticly bound to the bold font.  The last text box is bound to a value that is not a valid font and defaults to the default Android font.

FontBindingSample

Here’s a link to the working project on git.

Happy Coding

Ready, set, action

I just had the need to style the search box in the actionbar of my Xamarin Android application. For those of you who don’t know, Xamarin lets you write native Android apps in .Net, specifically C#.

Here’s the search box before styling:

image

Here’s the look I was going for:

image

I was using ActionBarSherlock to create the actionbar.  After searching for a while, I came to the conclusion that the only way to customize the actionbar using the Android style system was to switch to AppCompat.  So now the steps seem pretty clear and easy:

  1. Migrate from ActionBarSherlock to AppCompat
  2. Create a style for my search box
  3. Apply style in just the right place.

I ran into more bumps along the way than expected, so I wanted to write down exactly what I had to do to get this working.

Migrating to from Sherlock to AppCompat

Before really considering using AppCompat, I checked to see how well it was supported by Xamarin and found a useful post on their blog with some sample code.  This looked promising and I was able to get it to compile locally, so full steam ahead.  Back in my project, I deleted the ActionBarSherlock Xamarin component and added in the AppCompat component.  I then walked through my code changing all code referencing Sherlock to AppCompat.  Wolram Rittmeyer has an excellent post on the step by step process to do this.

My first concern was that I also use MvvmCross, which requires that all Activity classes implement IMvxEventSourceActivity and IMvxAndroidView.  So months ago (almost a year ago according to my commit history) I created the  MvxActionBarSherlockFragmentActivity base class that inherits from SherlockFragmentActivity and implements the MvvmCross interfaces.  Not remembering what went into creating the class I was concerned it would be tedious to replace it with an AppCompat version.  Turns out it was trivial.  All I had to do was inhert from ActionBarActivity instead.  It was literally a one word change. Here’s my new MvxActionBarActivity:

using Android.App;
using Android.Content;
using Android.OS;
using Android.Support.V7.App;
using Cirrious.CrossCore.Core;
using Cirrious.CrossCore.Droid.Views;
using Cirrious.MvvmCross.Binding.BindingContext;
using Cirrious.MvvmCross.Binding.Droid.BindingContext;
using Cirrious.MvvmCross.Droid.Views;
using Cirrious.MvvmCross.ViewModels;
using System;

namespace Masterdevs.Droid.Views
{
    public class MvxActionBarActivity : ActionBarActivity, IMvxEventSourceActivity, IMvxAndroidView
    {
        protected MvxActionBarActivity()
        {
            BindingContext = new MvxAndroidBindingContext(this, this);
            this.AddEventListeners();
        }

        public event EventHandler<MvxValueEventArgs<MvxActivityResultParameters>> ActivityResultCalled;
        public event EventHandler<MvxValueEventArgs<Bundle>> CreateCalled;
        public event EventHandler<MvxValueEventArgs<Bundle>> CreateWillBeCalled;
        public event EventHandler DestroyCalled;
        public event EventHandler DisposeCalled;
        public event EventHandler<MvxValueEventArgs<Intent>> NewIntentCalled;
        public event EventHandler PauseCalled;
        public event EventHandler RestartCalled;
        public event EventHandler ResumeCalled;
        public event EventHandler<MvxValueEventArgs<Bundle>> SaveInstanceStateCalled;
        public event EventHandler<MvxValueEventArgs<MvxStartActivityForResultParameters>> StartActivityForResultCalled;
        public event EventHandler StartCalled;
        public event EventHandler StopCalled;

        public IMvxBindingContext BindingContext { get; set; }

        public object DataContext
        {
            get { return BindingContext.DataContext; }
            set { BindingContext.DataContext = value; }
        }

        public IMvxViewModel ViewModel
        {
            get { return DataContext as IMvxViewModel; }
            set
            {
                DataContext = value;
                OnViewModelSet();
            }
        }

        public void MvxInternalStartActivityForResult(Intent intent, int requestCode)
        {
            base.StartActivityForResult(intent, requestCode);
        }

        public override void SetContentView(int layoutResId)
        {
#if DEBUG // This try catch is super useful when debugging bad layouts.  No real need for it in prod.
            try
            {
#endif
                var view = this.BindingInflate(layoutResId, null);
                SetContentView(view);
#if DEBUG
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);  // Because of the JNI layers, this is the easiest way to reliably get the message from the exception when debugging.  The watch window isn't as reliable/timely
                throw;
            }
#endif
        }

        public override void StartActivityForResult(Intent intent, int requestCode)
        {
            StartActivityForResultCalled.Raise(this, new MvxStartActivityForResultParameters(intent, requestCode));
            base.StartActivityForResult(intent, requestCode);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                DisposeCalled.Raise(this);
            }
            base.Dispose(disposing);
        }

        protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
        {
            ActivityResultCalled.Raise(this, new MvxActivityResultParameters(requestCode, resultCode, data));
            base.OnActivityResult(requestCode, resultCode, data);
        }

        protected override void OnCreate(Bundle bundle)
        {
            CreateWillBeCalled.Raise(this, bundle);
            base.OnCreate(bundle);
            CreateCalled.Raise(this, bundle);
        }

        protected override void OnDestroy()
        {
            DestroyCalled.Raise(this);
            base.OnDestroy();
        }

        protected override void OnNewIntent(Intent intent)
        {
            base.OnNewIntent(intent);
            NewIntentCalled.Raise(this, intent);
        }

        protected override void OnPause()
        {
            PauseCalled.Raise(this);
            base.OnPause();
        }

        protected override void OnRestart()
        {
            base.OnRestart();
            RestartCalled.Raise(this);
        }

        protected override void OnResume()
        {
            base.OnResume();
            ResumeCalled.Raise(this);
        }

        protected override void OnSaveInstanceState(Bundle outState)
        {
            SaveInstanceStateCalled.Raise(this, outState);
            base.OnSaveInstanceState(outState);
        }

        protected override void OnStart()
        {
            base.OnStart();
            StartCalled.Raise(this);
        }

        protected override void OnStop()
        {
            StopCalled.Raise(this);
            base.OnStop();
        }

        protected virtual void OnViewModelSet()
        {
        }
    }
}

With that done, all my MvvmCross worries were over and my app should compile.  Not quite.  On either score.  It turns out that the version of MvvmCross I was using was referencing the old Mono.Android.Support.v4.dll while the AppCompat library referenced the new Xamarin.Android.Support.v4.dll.  These are essentially the same library, but with different names.  There is an excellent summary on Xamarin’s bugzilla.  Finally after carefully reading through all of the bug report, at the very bottom, I found Stuart’s comment saying that he’d already released a fixed version.  All I had to do was update to the latest version of MvvmCross in NuGet.  And now my code actually compiled and my MvvmCross concerns were over.

Fixing the Null SearchView

While my code happily compiled, it wasn’t quite as happy about actually running.

public override bool OnCreateOptionsMenu(IMenu menu)
{
    MenuInflater.Inflate(Resource.Menu.ManageUsers, menu);
    var searchItem = menu.FindItem(Resource.Id.action_search);

    var view = MenuItemCompat.GetActionView(searchItem);
    var searchView = view.JavaCast<Android.Support.V7.Widget.SearchView>();

    searchView.QueryTextChange += (s, e) => ViewModel.Filter = e.NewText;

    return base.OnCreateOptionsMenu(menu);
}

Whenever I tried to get the action view from the search menu item, it was null.   My first instinct was to double check my menu definition:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto" >
  <item android:id="@+id/action_search"
        android:title="Search Friends"
        android:icon="@android:drawable/ic_menu_search"
        app:showAsAction="ifRoom|collapseActionView"
        app:actionViewClass="android.support.v7.widget.SearchView" />
</menu>

It looked right.  I had remembered to use the AppCompat search view.  After some digging on the inter-tubes, I found a post on StackOverflow explaining that my themes had to specifically derive from AppCompat themes.  Ok, so a trip to the style generator and I’m using the correct themes.

Styling Search

So now it’s been a while, and I have a lot of checked out code.  But I’ve finally gotten back to where I was when I started.  An app that compiles, runs, and has an ugly search box.

The trick, (thanks to Andreas Nilsson for explaining it) is to set your own style  searchViewAutoCompleteTextView in the Theme.AppCompat style.

<resources>
    <style name="AppTheme" parent="@style/Theme.AppCompat.Light.DarkActionBar">
        <item name="android:actionBarWidgetTheme">@style/ActionBarWidget.actionbar</item>
    </style>

    <style name="ActionBarWidget.actionbar" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="searchViewAutoCompleteTextView">@style/AutoCompleteTextView.actionbar</item>
    </style>

    <style name="AutoCompleteTextView.actionbar" parent="Widget.AppCompat.Light.AutoCompleteTextView">
        <item name="android:textColor">#FFFFFF</item>
        <item name="android:textCursorDrawable">@null</item>
    </style>
</resources>

Thanks and Resources

Thanks to Wolfram Rittmeyer for his methodical description on how to migrate from Sherlock to AppCompat.  Another thanks to Andreas Nilsson for his blog post showing that it was even possible to customize the the search box using styles.  I encourage you to read both blog posts, since they go into far greater detail.

Happy Coding.

Building iOS views in code

The Problem

I started doing iOS development in Xamarin in C# (one look at Objective-C was enough for me).  Writing C# for iOS in Xamarin Studio is where I spend most of my days.  Occasionally I switch over to Windows to lather in the Visual Studio goodness, however, Xamarin Studio is mostly enjoyable.  When it comes to creating UIs in iOS, the standard approach is to fire up Xcode, build your UI in the WYSIWYG editor, wire up your outlets, save your changes, and hope Xamarin Studio picks up your changes.  It’s a fairly arduous process, but it works.

My coworkers don’t have access to an instance of Xcode (there’s only one MacBook in my team).  This means, they can’t make any UI changes.  On top of that, I don’t like building UIs in the Xib interface builder.  I find it rather confusing and overkill for my needs.  I’m also the type of person who would rather build my UI out in Photoshop, and translate it directly to code.  I guess that’s the web developer in me.

The Solution

With iOS 6 came Auto Layout – the ability to create relationships between UI elements using constraints.  Once you add all your elements to the parent element, you define the relationships between each of the objects.  The platform will then draw the UI for you, honoring all of your constraints (if you specified them correctly).

Let’s say, I want to build the following view.  Here’s what the code looks like (come back when you’re finished throwing up):

OldConstraintsParentChild

That just sucks.

Another solution

XibFree is a really nice solution.  It does what it says – let’s you build UIs without a XIB.  It was the first solution I played around with.  I’m not going to go into too many details about it (ironically, the documentation is what I find most lacking).  It’s a lot less wordy and mostly uses the Android XML language.  If you’re doing cross platform development AND you’re coming from Android, the learning curve is not as steep.

I guess my biggest issue with XibFree was animating, showing, hiding, etc.  For the most part, it worked fine.  As my interface got more and more complicated, it became difficult to debug some of these issues.

My favorite solution

If you aren’t familiar with @slodge or MvvmCross, get ready to have a good time.

In a mere couple hours, Stuart Lodge changed me life.  To see how, read hist post (http://slodge.blogspot.com/2013/07/playing-with-constraints.html).  In short, he’s build a library which allows you to use a declarative way to define your views using the native NSLayoutConstraints.  Score!

It’s so easy to use, let’s make the example a little bit more complicated.

ConstraintsParentChild

   

And another example.

ConstraintsSameLeftBelow

I know you’re excited.  Now go download the library and develop away.  It’s available on GitHub – https://github.com/MvvmCross/MvvmCross.

What can you do

Like XibFree, the documentation is a little lacking.  However, you have the source.  So, let’s see what we can do with this library.

  • child.AtTopOf(parent[, margin]) – attaches the top of the child’s frame to the top of parent’s frame
  • child.AtLeftOf(parent[, margin]) – attaches the left side of the child’s frame to the left side of the parent’s frame
  • child.AtRightOf(parent[, margin]) – attaches the right side of the child’s frame to the right side of the parent’s frame
  • child.AtBottomOf(parent[, margin]) – attaches the bottom of child’s frame to the bottom of the parent’s frame
  • view1.WithSameLeft(view2) – attaches the left value of view1 (view1.Frame.X) to the left X value of view2
  • view1.WithSameRight(view2) – attaches the position of the right side of view1 (view1.Frame.Right) to the same right value as view2
  • view1.WithSameTop(view2) – attaches the top Y value of view1’s frame (view1.Frame.Y) to the top Y value of view2
  • view1.WithSameBottom(view2) – attaches the bottom of view1’s frame (view1.Frame.Bottom) to the bottom as view2
  • view1.WithSameCenterX(view2) – attaches the horizontal center of view1 to the horizontal center of view2
  • view1.WithSameCenterY(view2) – attaches the vertical center of view1 to the vertical center of view2
  • view1.WithSameWidth(view2) – attaches the width of view1 to the width of view2
  • view1.WithRelativeWidth(view2[, scale]) – attaches the width of view1 to the width of view2, with an optional scale factor
  • view1.WithSameHeight(view2) – attaches the height of view1 to the height of view2
  • view1.WithRelativeHeight(view2[, scale]) – attaches the height of view1 to the height of view2, with an optional scale factor
  • view1.Below(view2[, margin]) – attaches the top value of view1 to the bottom value of view frame
  • view1.Above(view2[, margin]) – attaches the bottom value view1 to the top value of view2
  • view1.ToRightOf(view2[, margin]) – attaches the left value of view1 to the right value of view2
  • view1.ToLeftOf(view2[, margin]) – attaches the right value of view1 to the left value of view2
  • view1.FullWidthOf(view2[, margin]) – attaches the left and right values of view1 to view2
  • view1.FullHeightOf(view2[, margin]) – attaches the top and bottom of view1 to view2

Headers and Footers on an MvxListView

Full source for this post can be found on GitHub:  HeaderedGridView

Last week I posted about adding a header to a GridView.  Today I’ll be discussing how to add a header (and footer!) to a MvxListView.  The MvxListView is a data-binding friendly ListView from MvvmCross.  Adding a header here is a little different because we need an instance of HeaderViewListAdapter that implements IMvxAdapter .  Other than that, the code should be very familiar.

Requirements

Just so it is clear what we are building, here are my requirements for this control:

  1. Ability to add a header and footer to the MvxListView in xml markup
  2. The header is to be laid out inline with the rest of the items, at the top
  3. The footer is to be laid out inline with the rest of the items, at the bottom
  4. The header and footer need to scroll with the rest of the content, i.e., they are not always visible
  5. The header and footer must be able to use MvvmCross bindings

Some quick screenshots from the sample project will help us understand what this should look like.

top.png

The list is scrolled to the top with purple header visible

middle.png

Shows the list at some point in the middle when neither header nor footer is visible

bottom.png

The list scrolled all the way to the bottom with the green footer visible

Code

Full source for this post can be found on GitHub:  HeaderedGridView

The MvxListView assumes that the adapter it has implements IMvxAdapter.  We can create a simple wrapper class that inherits from HeaderViewListAdapter and implements that interface.  The constructor will accept the list of headers and footers like the base class in addition to accepting an IMvxAdapter to wrap.  All of the interface methods for IMvxAdapter can then be passed down to the internal adapter.

In the snippet of HeaderMxAdapter above we have two constructors.  The first can be used if you do not want to add a footer.  It simply calls into the second passing an empty list of footers to add to the adapter.  The only other thing the constructor does is to squirrel away the adapter in a private field for later use.

The implementation for DropDownItemTemplateId is simple and indicative of the rest of the methods in the class.  It merely calls into that squirreled away adapter, calling its implementation of DropDownItemTemplateId.  The rest of the methods are exactly the same and have been omitted for brevity.

Next step is to add a pair of attributes to use for our header and footer.  This is exactly the same as the headered grid view and is done in attrs.xml.

Some boiler-plate code in MvxListViewWithHeader will read these values so we know what views to use for the header and footer.  Like last time, I’m using some helper classes to do the parsing.  They are included in the project.

Next we need to create the views for the header and footer .

First we check if the id is the default header id, if it is then we don’t have a view defined in the xml, and we just return null.  After that we get the current binding context and use it to inflate our view.  This is the magic that lets us bind within our header and footer.

Once we have the view, we need to wrap it in a list of FixedViewInfo which is what the HeaderMxAdapter expects in its constructor.

Nothing earth shattering here.  If the view is null, return an empty list, otherwise construct the FixedViewInfo and add it to the list.  Returning an empty list allows us to specify only the header, only the footer, or neither without causing any errors.

A couple of helper methods make help to make it clear and concrete what GetFixedViewInfos does.

All of this is called from the constructor which creates the MvxAdapter and wraps it in our new HeaderMvxAdapter.  It then set the adapter property in the base class.

The constructor is also responsible for reading in the item template id which the MvxListView uses to display the individual items in the list.

Using the Control

You would use the MvxListViewWithHeader the same way as a MvxListView.  The only difference is that you can now optionally specify a header or footer attribute which would point to another layout.  The following is taken from the FirstView in the sample.

The header and footer layout in the sample are very simple.  They are just a TextView with the text property bound to the view model.  Here is the header layout, but the footer is almost identical:

Future Improvements

Like the headered grid view, this implementation only allows a single header. If we had need for multiple headers we could copy the  onFinishedInflate method from ListView.  It collects all the child layouts for the list view and adds them to the list of headers.

Happy Coding

Headered Grid View in MvvmCross. Now with bindings!

Full source code for this article can be downloaded on GitHub:  HeaderedGridView.

For the easy import feature of Yarly, I needed to create a view that would easily allow a user to select an existing photo to import into the app or to take new photo. A grid view of photos from the camera roll but with the first item as a button that would load the camera would serve nicely for this use case. See the screen shot below of an early version of the UI:

Yarly Screen Shots 2014-04-20_194747_242000

This reminded me of the AddHeaderView method available on ListView. With that as a guide, I decided to add similar functionality to the GridView.  Since I’m using MvvmCross, I also want to make sure that I can use bindings in the header control. While I’m at it, having to explicitly call AddHeaderView in my activity code is useful, but I’d much rather to be able to do this in markup. 

Requirements

To sum up the above requirements:

  1. Ability to add a header to a GridView in xml markup
  2. The header is laid out inline with the rest of the items in the GridView
  3. The header scrolls with the rest of the content, i.e., it is not always visible
  4. The header must be able to use MvvmCross bindings

Code

A full sample project can be downloaded from my GitHub repo:  HeaderedGridView.

In attrs.xml, add an attribute to use for our header. 

<!-- attrs.xml --> <declare-styleable name="GridView">

   <attr name="header" format="reference" /> </declare-styleable>

In the HeaderedGridView, all we need to do now is check for the presence of this attribute, and if it exists, inflate the view.  Reading the header id is straight forward processing of the IAttributeSet.  I use some helper classes to iterate and dispose of the attributes in a more C# way.  The details aren’t very important, but the relevant classes are included in the sample solution.  The MvvmCross class MvxAndroidBindingContextHelpers can return the current binding context which can be used to inflate and bind the header at the same time.

// HeaderedGridView.cs private void ProcessAttrs(Context c, IAttributeSet attrs) {

     _headerId = DEFAULT_HEADER_ID;

     using (var attributes = c.ObtainDisposableStyledAttributes(attrs, Resource.Styleable.GridView))

     {

         foreach (var a in attributes)

         {

             switch (a)

             {

                 case Resource.Styleable.GridView_header:

                     _headerId = attributes.GetResourceId(a, DEFAULT_HEADER_ID);

                     break;

             }

 

         }

     } } private void LoadHeader() {

     if (_headerId == DEFAULT_HEADER_ID) return;

     IMvxAndroidBindingContext bindingContext = MvxAndroidBindingContextHelpers.Current();

     _header = bindingContext.BindingInflate(_headerId, null); }

Now that we have a header, we can wrap our current adapter in the HeaderViewListAdapter.  As the name implies, this is the exact same adapter used by the ListView.  It handles knowing when and where to show the header.  In my sample code, I have the grid create the adapter directly, but this can work just as well if an adapter is passed in from outside.

// HeaderedGridView.cs

private IListAdapter GetAdapter() {

     var headerInfo = GetHeaders();

     ICursor cursor = ImageAdapter.CreateCursor(Context);

     IListAdapter adapter = new ImageAdapter(Context, cursor);

 

     if (headerInfo != null)

     {

         adapter = new HeaderViewListAdapter(headerInfo, null, adapter);

     }

     return adapter; }

So I don’t have to hard code sizes and so the header matches the rest of the items in the grid, I set the height and width once the grid is being laid out.  In the OnMeasure method we check if the header isn’t null, and if the ColumnWidth doesn’t match the previous column width we saw.  Caching the width and testing this prevents us from setting the layout parameters when we don’t have to; OnMeasure is called multiple times.

// HeaderedGridView.cs protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) {

     base.OnMeasure(widthMeasureSpec, heightMeasureSpec);

 

     if (_header != null && base.ColumnWidth != _cachedColumnWidth)

     {

         _cachedColumnWidth = base.ColumnWidth;

         _header.LayoutParameters = new ViewGroup.LayoutParams(_cachedColumnWidth, _cachedColumnWidth);

     } }

Now all we need to do is include our grid in a layout.   It’s almost exactly the same as the regular GridView, except we can now optionally specify a header.  This is from the FirstView layout in the sample.

<!--FirstView.axml--> <HeaderedGridView

   android:layout_width="fill_parent"

   android:layout_height="fill_parent"

   android:layout_weight="1"

   android:numColumns="3"

   android:verticalSpacing="10dp"

   android:horizontalSpacing="10dp"

   android:stretchMode="columnWidth"

   android:gravity="center"

   android:fastScrollEnabled="true"

   android:background="#000000"

   local:header="@layout/gridheader" />

The gridheader layout is just a simple layout with a single image button button.  Note that we are binding the click of the button to the ClickCommand in our view model.

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=http://schemas.android.com/apk/res/android

     xmlns:local=http://schemas.android.com/apk/res-auto

     android:orientation="vertical"

     android:layout_width="fill_parent"

     android:layout_height="fill_parent"

     android:padding="5dp">

     <ImageButton

         android:layout_width="fill_parent"

         android:layout_height="fill_parent"

         android:background="#90C53E"

         android:src="@drawable/camera"

         local:MvxBind="Click ClickCommand" /> </LinearLayout>

Firing up the sample solution and we should see something similar to:

Yarly Screen Shots 2014-04-21_184439_262000 

Future Improvements

My implementation restricts me to only being able to add one header.  While one header is currently good enough for me, if I wanted to expand it add more, I could emulate ListView a bit more.  Specifically the onFinishedInflate method which adds child layouts as a list of headers.  Of course, I could also add an explicit AddHeaderView method that could be called multiple times.

I’ve ignored footers entirely.  Again, this is just because I don’t currently have any need for a footer.  They could be added easily enough following the same pattern as headers.

Happy Coding.