Skip to content

Instantly share code, notes, and snippets.

@mj-hd
Last active October 14, 2016 18:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mj-hd/350d67001087124d0d85edf6e37851f4 to your computer and use it in GitHub Desktop.
Save mj-hd/350d67001087124d0d85edf6e37851f4 to your computer and use it in GitHub Desktop.
/* from http://forums.xamarin.com/discussion/comment/149607 */
using System;
using Android.Content;
using Android.Runtime;
using Android.Text;
using Android.Widget;
using Android.Util;
using Android.Graphics;
using Java.Lang;
using Math = System.Math;
using String = System.String;
namespace Somewhere
{
public class AutoResizeButton : Button
{
// Minimum text size for this text view
public static float MinTextSize = 20;
// Interface for resize notifications
public interface IOnTextResizeListener
{
void OnTextResize(TextView textView, float oldSize, float newSize);
}
#region Fields
private static readonly Canvas TextResizeCanvas = new Canvas();
// Our ellipse string
private const String Ellipsis = "...";
// Registered resize listener
private IOnTextResizeListener _textResizeListener;
// Flag for text and/or size changes to force a resize
private bool _needsResize = false;
// Text size that is set from code. This acts as a starting point for resizing
private float _textSize;
// Temporary upper bounds on the starting text size
private float _maxTextSize = 0;
// Lower bounds for text size
private float _minTextSize = MinTextSize;
// Text view line spacing multiplier
private float _spacingMult = 1.0f;
// Text view additional line spacing
private float _spacingAdd = 0.0f;
// Add ellipsis to text that overflows at the smallest text size
private bool _addEllipsis = true;
#endregion
#region Constructors
public AutoResizeButton(IntPtr a, JniHandleOwnership b) : base(a, b) { }
public AutoResizeButton(Context context) :
this(context, null)
{
}
public AutoResizeButton(Context context, IAttributeSet attrs) :
this(context, attrs, 0)
{
}
public AutoResizeButton(Context context, IAttributeSet attrs, int defStyle) :
base(context, attrs, defStyle)
{
_textSize = TextSize;
}
#endregion
#region Public Methods
//When text changes, set the force resize flag to true and reset the text size.
protected override void OnTextChanged(ICharSequence text, int start, int before, int after)
{
_needsResize = true;
// Since this view may be reused, it is good to reset the text size
ResetTextSize();
}
// If the text view size changed, set the force resize flag to true
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
if (w != oldw || h != oldh)
{
_needsResize = true;
}
}
//Register listener to receive resize notifications
public void SetOnResizeListener(IOnTextResizeListener listener)
{
_textResizeListener = listener;
}
public override void SetTextSize(ComplexUnitType unitType, float size)
{
base.SetTextSize(unitType, size);
_textSize = TextSize;
}
/**
* Override the set text size to update our internal reference values
*/
//@Override
//public void setTextSize(int unit, float size) {
// super.setTextSize(unit, size);
// mTextSize = getTextSize();
//}
// Override the set line spacing to update our internal reference values
public override void SetLineSpacing(float add, float mult)
{
base.SetLineSpacing(add, mult);
_spacingMult = mult;
_spacingAdd = add;
}
//Set the upper text size limit and invalidate the view
public void SetMaxTextSize(float maxTextSize)
{
_maxTextSize = maxTextSize;
RequestLayout();
Invalidate();
}
//Return upper text size limit
public float GetMaxTextSize()
{
return _maxTextSize;
}
//Set the lower text size limit and invalidate the view
public void SetMinTextSize(float minTextSize)
{
_minTextSize = minTextSize;
RequestLayout();
Invalidate();
}
//Return lower text size limit
public float SetMinTextSize()
{
return _minTextSize;
}
//Set flag to add ellipsis to text that overflows at the smallest text size
public void SetAddEllipsis(bool addEllipsis)
{
_addEllipsis = addEllipsis;
}
//Return flag to add ellipsis to text that overflows at the smallest text size
public bool GetAddEllipsis()
{
return _addEllipsis;
}
//Reset the text to the original size
public void ResetTextSize()
{
if (_textSize > 0)
{
base.SetTextSize(ComplexUnitType.Px, _textSize);
_maxTextSize = _textSize;
}
}
//Resize text after measuring
protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
{
if (changed || _needsResize)
{
int widthLimit = (right - left) - CompoundPaddingLeft - CompoundPaddingRight;
int heightLimit = (bottom - top) - CompoundPaddingBottom - CompoundPaddingTop;
ResizeText(widthLimit, heightLimit);
}
base.OnLayout(changed, left, top, right, bottom);
}
//Resize the text size with default width and height
public void ResizeText()
{
int heightLimit = Height - PaddingBottom - PaddingTop;
int widthLimit = Width - PaddingLeft - PaddingRight;
ResizeText(widthLimit, heightLimit);
}
// Resize the text size with specified width and height
public void ResizeText(int width, int height)
{
string text = Text;
// Do not resize if the view does not have dimensions or there is no text
if (string.IsNullOrEmpty(text) || height <= 0 || width <= 0 || _textSize == 0)
return;
// Get the text view's paint object
TextPaint textPaint = Paint;
// Store the current text size
float oldTextSize = textPaint.TextSize;
// If there is a max text size set, use the lesser of that and the default text size
float targetTextSize = _maxTextSize > 0 ? Math.Min(_textSize, _maxTextSize) : _textSize;
// Get the required text height
int textHeight = GetTextHeight(text, textPaint, width, targetTextSize);
int textWidth = GetTextWidth(text, textPaint, width, targetTextSize);
// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while (((textHeight >= height) && (textWidth >= width)) && targetTextSize > _minTextSize)
{
targetTextSize = Math.Max(targetTextSize - 2, _minTextSize);
textHeight = GetTextHeight(text, textPaint, width, targetTextSize);
textWidth = GetTextWidth(text, textPaint, width, targetTextSize);
}
// If we had reached our minimum text size and still don't fit, append an ellipsis
if (_addEllipsis && targetTextSize == _minTextSize && textHeight > height)
{
// Draw using a static layout
StaticLayout layout = new StaticLayout(text, textPaint, width, Layout.Alignment.AlignNormal, _spacingMult, _spacingAdd, false);
// Check that we have a least one line of rendered text
if (layout.LineCount > 0)
{
// Since the line at the specific vertical position would be cut off,
// we must trim up to the previous line
int lastLine = layout.GetLineForVertical(height) - 1;
// If the text would not even fit on a single line, clear it
if (lastLine < 0)
{
Text = "";
}
// Otherwise, trim to the previous line and add an ellipsis
else
{
int start = layout.GetLineStart(lastLine);
int end = layout.GetLineEnd(lastLine);
float lineWidth = layout.GetLineWidth(lastLine);
float ellipseWidth = textPaint.MeasureText(Ellipsis);
// Trim characters off until we have enough room to draw the ellipsis
while (width < lineWidth + ellipseWidth)
{
lineWidth = textPaint.MeasureText(text.Substring(start, --end + 1));
}
Text = (text.Substring(0, end) + Ellipsis);
}
}
}
// Some devices try to auto adjust line spacing, so force default line spacing
// and invalidate the layout as a side effect
Console.WriteLine(String.Format("target: {0}", targetTextSize));
textPaint.TextSize = targetTextSize;
SetLineSpacing(_spacingAdd, _spacingMult);
// Notify the listener if registered
if (_textResizeListener != null)
{
_textResizeListener.OnTextResize(this, oldTextSize, targetTextSize);
}
// Reset force resize flag
_needsResize = false;
}
#endregion
#region Private methods
// Set the text size of the text paint object and use a static layout to render text off screen before measuring
private int GetTextHeight(string source, TextPaint paint, int width, float textSize)
{
// Update the text paint object
paint.TextSize = textSize;
// Measure using a static layout
StaticLayout layout = new StaticLayout(source, paint, width, Layout.Alignment.AlignNormal, _spacingMult, _spacingAdd, true);
return layout.Height;
}
// Set the text size of the text paint object and use a static layout to render text off screen before measuring
private int GetTextWidth(string source, TextPaint paint, int width, float textSize)
{
// Update the text paint object
paint.TextSize = textSize;
// Draw using a static layout
StaticLayout layout = new StaticLayout(source, paint, width, Layout.Alignment.AlignNormal, _spacingMult, _spacingAdd, true);
//layout.Draw(TextResizeCanvas);
return layout.Width;
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment