Skip to content

Instantly share code, notes, and snippets.

@descorp
Last active December 4, 2015 14:50
Show Gist options
  • Select an option

  • Save descorp/b17789d50434e5d626b6 to your computer and use it in GitHub Desktop.

Select an option

Save descorp/b17789d50434e5d626b6 to your computer and use it in GitHub Desktop.
using System;
using Android.Widget;
using Android.Graphics;
using Android.Util;
using Android.Text;
using Android.Content;
using Android.Runtime;
using Java.Lang;
using Android.Content.Res;
namespace Timebook.Droid.Controls
{
[Register("com.timebook.android.controls.AutoResizeTextView")]
public class AutoResizeTextView : TextView
{
private RectF mTextRect = new RectF();
private RectF mAvailableSpaceRect;
private SparseIntArray mTextCachedSizes;
internal TextPaint mPaint;
private float mMaxTextSize;
private float mSpacingMult = 1.0f;
private float mSpacingAdd = 0.0f;
private float mMinTextSize = 20;
private int mWidthLimit;
private static int NO_LINE_LIMIT = -1;
private int mMaxLines;
private bool mEnableSizeCache = true;
private bool mInitiallized;
public AutoResizeTextView(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
Initialize();
}
public AutoResizeTextView(Context context)
: base(context)
{
Initialize();
}
public AutoResizeTextView(Context context, IAttributeSet attrs)
: base(context, attrs)
{
Initialize();
}
public AutoResizeTextView(Context context, IAttributeSet attrs, int defStyle)
: base(context, attrs, defStyle)
{
Initialize();
}
private void Initialize()
{
mPaint = new TextPaint(this.Paint);
this.mMaxTextSize = this.TextSize;
this.mAvailableSpaceRect = new RectF();
this.mTextCachedSizes = new SparseIntArray();
if (mMaxLines == 0)
{
// no value was assigned during construction
mMaxLines = NO_LINE_LIMIT;
}
this.mSizeTester = new SizeTester(this);
mInitiallized = true;
}
public override void SetText(ICharSequence text, Android.Widget.TextView.BufferType type)
{
base.SetText(text, type);
AdjustTextSize(this.Text);
}
public override float TextSize
{
get
{
return base.TextSize;
}
set
{
base.TextSize = value;
mMaxTextSize = value;
mTextCachedSizes.Clear();
AdjustTextSize(this.Text);
}
}
public override void SetMaxLines(int maxlines)
{
base.SetMaxLines(maxlines);
mMaxLines = maxlines;
ReAdjust();
}
public override int MaxLines
{
get
{
return mMaxLines;
}
}
public override void SetSingleLine()
{
base.SetSingleLine();
mMaxLines = 1;
ReAdjust();
}
public override void SetSingleLine(bool singleLine)
{
base.SetSingleLine(singleLine);
if (singleLine)
{
mMaxLines = 1;
}
else
{
mMaxLines = NO_LINE_LIMIT;
}
ReAdjust();
}
public override void SetLines(int lines)
{
base.SetLines(lines);
mMaxLines = lines;
ReAdjust();
}
public override void SetTextSize(ComplexUnitType unit, float size)
{
Context c = this.Context;
Resources r;
if (c == null)
{
r = Resources.System;
}
else
{
r = c.Resources;
}
mMaxTextSize = TypedValue.ApplyDimension(unit, size, r.DisplayMetrics);
mTextCachedSizes.Clear();
AdjustTextSize(this.Text);
}
public override void SetLineSpacing(float add, float mult)
{
base.SetLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
public float MinTextSize
{
set
{
mMinTextSize = value;
ReAdjust();
}
}
private void ReAdjust()
{
AdjustTextSize(this.Text);
}
private void AdjustTextSize(string text)
{
if (!mInitiallized)
{
return;
}
int startSize = (int)mMinTextSize;
int heightLimit = this.MeasuredHeight - this.CompoundPaddingBottom - this.CompoundPaddingTop;
mWidthLimit = MeasuredWidth - CompoundPaddingLeft - CompoundPaddingRight;
mAvailableSpaceRect.Right = mWidthLimit;
mAvailableSpaceRect.Bottom = heightLimit;
base.SetTextSize(ComplexUnitType.Px, EfficientTextSizeSearch(startSize, (int)mMaxTextSize, mSizeTester, mAvailableSpaceRect));
}
public void EnableSizeCache(bool enable)
{
mEnableSizeCache = enable;
mTextCachedSizes.Clear();
AdjustTextSize(this.Text);
}
private int EfficientTextSizeSearch(int start, int end, SizeTester sizeTester, RectF availableSpace)
{
if (!mEnableSizeCache)
{
return BinarySearch(start, end, sizeTester, availableSpace);
}
var text = this.Text;
int key = text == null ? 0 : text.Length;
int size = mTextCachedSizes.Get(key);
if (size != 0)
{
return size;
}
size = BinarySearch(start, end, sizeTester, availableSpace);
mTextCachedSizes.Put(key, size);
return size;
}
private static int BinarySearch(int start, int end, SizeTester sizeTester, RectF availableSpace)
{
int lastBest = start;
int lo = start;
int hi = end - 1;
int mid = 0;
while (lo <= hi)
{
mid = (lo + hi) >> 1;
int midValCmp = sizeTester.OnTestSize(mid, availableSpace);
if (midValCmp < 0)
{
lastBest = lo;
lo = mid + 1;
}
else if (midValCmp > 0)
{
hi = mid - 1;
lastBest = hi;
}
else
{
return mid;
}
}
// make sure to return last best
// this is what should always be returned
return lastBest;
}
protected override void OnTextChanged(ICharSequence text, int start, int before, int after)
{
base.OnTextChanged(text, start, before, after);
ReAdjust();
}
protected override void OnSizeChanged(int width, int height, int oldwidth, int oldheight)
{
mTextCachedSizes.Clear();
base.OnSizeChanged(width, height, oldwidth, oldheight);
if (width != oldwidth || height != oldheight)
{
ReAdjust();
}
}
private SizeTester mSizeTester;
internal class SizeTester
{
AutoResizeTextView parent;
public SizeTester(AutoResizeTextView parent)
{
this.parent = parent;
}
public int OnTestSize(int suggestedSize, RectF availableSpace)
{
this.parent.mPaint.TextSize = suggestedSize;
var text = this.parent.Text;
var singleline = this.parent.MaxLines == 1;
if (singleline)
{
this.parent.mTextRect.Bottom = this.parent.mPaint.FontSpacing;
this.parent.mTextRect.Right = this.parent.mPaint.MeasureText(text);
}
else
{
var layout = new StaticLayout(text, this.parent.mPaint, this.parent.mWidthLimit, Layout.Alignment.AlignNormal, this.parent.mSpacingMult, this.parent.mSpacingAdd, true);
// return early if we have more lines
if (this.parent.MaxLines != NO_LINE_LIMIT && layout.LineCount > this.parent.MaxLines)
{
return 1;
}
this.parent.Bottom = layout.Height;
int maxWidth = -1;
for (int i = 0; i < layout.LineCount; i++)
{
if (maxWidth < layout.GetLineWidth(i))
{
maxWidth = (int)layout.GetLineWidth(i);
}
}
this.parent.mTextRect.Right = maxWidth;
}
this.parent.mTextRect.OffsetTo(0, 0);
if (availableSpace.Contains(this.parent.mTextRect))
{
// may be too small, don't worry we will find the best match
return -1;
}
else
{
// too big
return 1;
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment