Instantly share code, notes, and snippets.
mj-hd/AutoResizeButton.cs
Last active Oct 14, 2016
/* 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