Mono for Android

Auto Resize TextView for Android using Mono for Android

AutoResizeTextView Example

If you need your TextView to automatically shrink the text to fit (rather than truncating), Andreas Krings has a great solution. However, this solution is written in Java rather than C#. So here I present a version ported to Mono for Android using C#.

The implementation is pretty much identical, with a default minimum size of 10dp. The TextView will then attempt to find the largest size possible for the text that will fit the frame (the one limitation is that only the horizontal dimension is taken into account), with a maximum size of the initial size; and a minimum size of 10dp (unless overridden).  Usage is the same as well:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/com.danclarke.AutoResizeTextView"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TextView
        android:text="Normal TextView"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/textView1" />
    <TextView
        android:text="@string/longString"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="25dp"
        android:height="50dp" />

    <TextView
        android:text="AutoResizeTextView"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/textView2"
        android:layout_marginTop="20dp" />
    <AutoResizeTextView.Controls.AutoResizeTextView
        android:text="@string/longString"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="25dp"
        android:height="50dp" />

    <TextView
        android:text="AutoResizeTextView with minTextSize"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/textView3"
        android:layout_marginTop="20dp" />
    <AutoResizeTextView.Controls.AutoResizeTextView
        android:text="@string/longString"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="25dp"
        android:height="50dp"
        app:minTextSize="20dp" />
</LinearLayout>

Don’t forget to view the full project at GitHub!

Code Listing

For the impatient, here’s the full control’s code listing:

using System;

using Android.Content;
using Android.Runtime;
using Android.Widget;
using Android.Util;
using Android.Graphics;

namespace AutoResizeTextView.Controls
{
    /// <summary>
    /// TextView that automatically resizes it's content to fit the layout dimensions
    /// </summary>
    /// <remarks>Port of: http://ankri.de/autoscale-textview/</remarks>
    public class AutoResizeTextView : TextView
    {
        /// <summary>
        /// How close we have to be to the perfect size
        /// </summary>
        private const float Threshold = .5f;
        
        /// <summary>
        /// Default minimum text size
        /// </summary>
        private const float DefaultMinTextSize = 10f;
        
        private Paint _textPaint;
        private float _preferredTextSize;
        
        public AutoResizeTextView(Context context) : base(context)
        {
            Initialise(context, null);
        }
        
        public AutoResizeTextView(Context context, IAttributeSet attrs) : base(context, attrs)
        {
            Initialise(context, attrs);
        }
        
        public AutoResizeTextView(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs, defStyle)
        {
            Initialise(context, attrs);
        }
        
        // Default constructor override for MonoDroid
        public AutoResizeTextView(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
        {
            Initialise(null, null);
        }
        
        private void Initialise(Context context, IAttributeSet attrs)
        {
            _textPaint = new Paint();
            
            if (context != null && attrs != null)
            {
                var attributes = context.ObtainStyledAttributes(attrs, Resource.Styleable.AutoResizeTextView);
                MinTextSize = attributes.GetDimension(Resource.Styleable.AutoResizeTextView_minTextSize, DefaultMinTextSize);
                attributes.Recycle();
                
                _preferredTextSize = TextSize;
            }
        }
        
        /// <summary>
        /// Minimum text size in actual pixels
        /// </summary>
        public float MinTextSize { get; set; }
        
        /// <summary>
        /// Resize the text so that it fits.
        /// </summary>
        /// <param name="text">Text</param>
        /// <param name="textWidth">Width of the TextView</param>
        protected virtual void RefitText(string text, int textWidth)
        {
            if (textWidth <= 0 || string.IsNullOrWhiteSpace(text))
                return;
            
            int targetWidth = textWidth - PaddingLeft - PaddingRight;
            _textPaint.Set(this.Paint);
            
            while ((_preferredTextSize - MinTextSize) > Threshold)
            {
                float size = (_preferredTextSize + MinTextSize) / 2f;
                _textPaint.TextSize = size;
                
                if (_textPaint.MeasureText(text) >= targetWidth)
                    _preferredTextSize = size; // Too big
                else
                    MinTextSize = size; // Too small
            }
            
            SetTextSize(ComplexUnitType.Px, MinTextSize);
        }
        
        protected override void OnTextChanged(Java.Lang.ICharSequence text, int start, int before, int after)
        {
            base.OnTextChanged(text, start, before, after);
            
            RefitText(text.ToString(), Width);
        }
        
        protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
        {
            base.OnSizeChanged(w, h, oldw, oldh);
            
            if (w != oldw)
                RefitText(Text, Width);
        }
    }
}
Posted by Dan in Mono for Android, 2 comments

Implementing IParcelable in Mono for Android

If you want to pass values between activities in Android, you’ll probably want to implement Parcelable sooner or later. This isn’t particularly slick even when writing natively, but add Mono in and it becomes even murkier. Fortunately .NET’s Generics support helps us out a little. The reason for the rather heavy implementation is intents can cross process boundaries, meaning you can’t just provide a memory address. This gives you the ability to call apps / resources you haven’t programmed yourself, but unfortunately it means inter-activity communication is ‘heavy’ for want of a better term. If you’re thinking of using IParcelable in a brand new app that isn’t going to be receiving 3rd party parcels, consider the ‘alternatives’ at the end of this guide first. IParcelable is somewhat of a convenience API for serialising objects to binary and then de-serialising. You might be better off doing this yourself rather than using the ‘Android’ method.

Our Class

For this demo I want to pass an array of a really simple class:

public sealed class SelectListItem
{
	// Convenience constructors
	public SelectListItem() {}
	public SelectListItem(int id, string name)
	{
		Id = id;
		Name = name;
	}

	// The actual properties for this class
	public int Id { get; set; }
	public string Name { get; set; }
}

Reference ‘Mono.Android.Export’

Before we start, make sure to reference the ‘Mono.Android.Export’ assembly:

Mono.Android.Export Assembly Reference

Implementing IParcelable

This class will be used to populate a ListActivity with a series of items that can be selected. First we need to inherit from Java.Lang.Object, and implement the IParcelable interface:

public sealed class SelectListItem : Java.Lang.Object, IParcelable

So why do we have to inherit from Java.Lang.Object? The Android runtime needs to be able to call the methods that we define as part of IParcelable, to do this our class must have a dual-personality. One half of the class is .NET, the other half is Java, it is this Java side that Android can see. When a method is called on the Java side, it will execute the .NET code. Clever huh? Unfortunately this means we now have two objects instead of one in memory, it also means that Mono must keep track of both objects. To do this it uses a global ID or reference, this reference links the two instances together. Unfortunately GREFs, are a very, very limited resource. The emulator only supports ~2,000 and devices usually only support ~50,000 GREFs. For more information on GREFs, check the Architecture and Garbage Collection documents over at Xamarin.

Before we implement the interface, we need to add an additional constructor that can accept a Parcel type. This constructor will populate the instance with the values from the parcel – the parcel being the ‘wrapped’ data of an instance of the same type:

// Create a new SelectListItem populated with the values in parcel
private SelectListItem(Parcel parcel)
{
	Id = parcel.ReadInt();
	Name = parcel.ReadString();
}

Note the order we read in the values, this is very important and must mirror the order in WriteToParcel below.

Next we need to implement the interface:

public int DescribeContents()
{
	return 0;
}

// Save this instance's values to the parcel
public void WriteToParcel(Parcel dest, ParcelableWriteFlags flags)
{
	dest.WriteInt(Id);
	dest.WriteString(Name);
}

DescribeContents is used for flagging ‘special objects’ within the parcel. I’ve never had cause to use this field, and 0 means ‘no special objects’.

WriteToParcel is where we write the instance data to the parcel, pay special attention to the order you write the values. This same order must be mirrored when reading in the parcel above. The flags attribute gives additional information on how the data should be written, it’s unlikely you’ll need to check these flags.

The CREATOR field

All Parcelable objects must have a public static field called CREATOR. The instance behind this field is responsible for creating instances of the object and populating them from the parcel. This can be done in two ways, an internal class that is implementation-specific, or a generic class that can be re-used repeatedly. The first method is the method commonly used in Java applications. However, with .NET we gain a much more robust Generics implementation so we’ll go down that route. However if you’re interested in the Java-favoured implementation it’s available in the sample project as a comment.

Without further ado, let’s look at how to declare our creator:

// The creator creates an instance of the specified object
private static readonly GenericParcelableCreator<SelectListItem> _creator
	= new GenericParcelableCreator<SelectListItem>((parcel) => new SelectListItem(parcel));

[ExportField("CREATOR")]
public static GenericParcelableCreator<SelectListItem> GetCreator()
{
	return _creator;
}

First we declare a private static instance, it’s required to be static by the Android API. Because this instance is static, it’s critical that your creator is thread safe otherwise bad things will happen. After the actual instance declaration we create a public static method, and decorate it with the ExportField attribute. Unfortunately this attribute can only be used on methods, so we can’t decorate our field directly. ExportField will ensure the Java version of our class gains a public static field named CREATOR.

A Generic ParcelableCreator Implementation

Earlier I mentioned that when developing with Java, each Parcelable requires a custom creator. Fortunately with .NET we gain a robust Generic implementation and we can use a generic creator that we can use again and again:

public sealed class GenericParcelableCreator<T> : Java.Lang.Object, IParcelableCreator
	where T : Java.Lang.Object, new()
{
	private readonly Func<Parcel, T> _createFunc;

	/// <summary>
	/// Initializes a new instance of the <see cref="ParcelableDemo.GenericParcelableCreator`1"/> class.
	/// </summary>
	/// <param name='createFromParcelFunc'>
	/// Func that creates an instance of T, populated with the values from the parcel parameter
	/// </param>
	public GenericParcelableCreator(Func<Parcel, T> createFromParcelFunc)
	{
		_createFunc = createFromParcelFunc;
	}

	#region IParcelableCreator Implementation

	public Java.Lang.Object CreateFromParcel(Parcel source)
	{
		return _createFunc(source);
	}

	public Java.Lang.Object[] NewArray(int size)
	{
		return new T[size];
	}

	#endregion
}

The creator must also be usable directly by Android, so again we have to inherit from Java.Lang.Object. We add two restrictions to the generic type parameter, first it must inherit from Java.Lang.Object (as all Parcelables must), and second it must have an empty constructor. This allows us to create an array of instances of the type, as required by IParcelableCreator.

Next in the constructor we require a delegate that can create and populate an instance of T. If you check back at our instantiation of the creator, we use a lambda expression that instantiates the instance using the private constructor we created that accepts a Parcel. Finally we implement the two methods required by IParcelableCreator, these simply create and populate the type, or create an array of the type.

Also remember that the creator must be thread-safe, if it isn’t bad things happen. To ensure thread safety we’ve made the state immutable, immutable types are by definition thread safe. If you need to create your own creator, be careful to follow thread safety guidelines. Microsoft have a good book on patterns and thread safety you can download for free.

Sample Code

You can download the sample project from GitHub here.

Select Item Item Selection Item Selected

Alternative to IParcelable

The problem with Parcelables is that you’re using up your very limited GREF resource. Additionally running two instances of an object across two garbage collectors isn’t the best use of memory or CPU resources. You’ll actually double up on instances as well, the instances that get packaged up in a parcel, and the instances that get extracted. The first set (senders) you can often clear from memory quickly, however the second set (received values) could be memory resident for quite some time as they’re used by the activity. The alternative is to either send just enough information for the activity to generate its own data, or you can send the data in an alternative format. You can send byte arrays, so you can use ISerializable and serialise your objects to binary. The other option is to use JSON (or other text-based format) and pass JSON strings between activities instead.

Unfortunately there is no ‘best’ solution, only the solution that meets your specific needs best.

Posted by Dan in C#, Mono for Android, Programming, Tutorials, 2 comments