Last active
October 14, 2016 18:48
-
-
Save mj-hd/bf77480062f5db26e1fab85a9f78ca17 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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 | |
{ | |
/// | |
/// TextView that automatically resizes it's content to fit the layout dimensions | |
/// | |
public class AutoResizeTextView : TextView | |
{ | |
// 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 AutoResizeTextView(IntPtr a, JniHandleOwnership b) : base(a, b) { } | |
// Default constructor override | |
public AutoResizeTextView(Context context) | |
: this(context, null) | |
{ | |
} | |
// Default constructor when inflating from XML file | |
public AutoResizeTextView(Context context, IAttributeSet attrs) | |
: this(context, attrs, 0) | |
{ | |
} | |
// Default constructor override | |
public AutoResizeTextView(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 | |
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