-
Notifications
You must be signed in to change notification settings - Fork 690
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added height fitting option (by KonradJanica) #69
base: master
Are you sure you want to change the base?
Changes from all commits
108edd3
61020f1
3366f58
32a1e95
9a51582
47e92b4
a0f9e0a
f6ef8d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,7 +24,8 @@ | |
* A helper class to enable automatically resizing {@link TextView}`s {@code textSize} to fit | ||
* within its bounds. | ||
* | ||
* @attr ref R.styleable.AutofitTextView_sizeToFit | ||
* @attr ref R.styleable.AutofitTextView_autofitWidthEnabled | ||
* @attr ref R.styleable.AutofitTextView_autofitHeightEnabled | ||
* @attr ref R.styleable.AutofitTextView_minTextSize | ||
* @attr ref R.styleable.AutofitTextView_precision | ||
*/ | ||
|
@@ -60,7 +61,8 @@ public static AutofitHelper create(TextView view, AttributeSet attrs) { | |
*/ | ||
public static AutofitHelper create(TextView view, AttributeSet attrs, int defStyle) { | ||
AutofitHelper helper = new AutofitHelper(view); | ||
boolean sizeToFit = true; | ||
boolean autofitWidthEnabled = true; | ||
boolean autofitHeightEnabled = false; | ||
if (attrs != null) { | ||
Context context = view.getContext(); | ||
int minTextSize = (int) helper.getMinTextSize(); | ||
|
@@ -71,16 +73,20 @@ public static AutofitHelper create(TextView view, AttributeSet attrs, int defSty | |
R.styleable.AutofitTextView, | ||
defStyle, | ||
0); | ||
sizeToFit = ta.getBoolean(R.styleable.AutofitTextView_sizeToFit, sizeToFit); | ||
autofitWidthEnabled = ta.getBoolean(R.styleable.AutofitTextView_autofitWidthEnabled, | ||
autofitWidthEnabled); | ||
autofitHeightEnabled = ta.getBoolean(R.styleable.AutofitTextView_autofitHeightEnabled, | ||
autofitHeightEnabled); | ||
minTextSize = ta.getDimensionPixelSize(R.styleable.AutofitTextView_minTextSize, | ||
minTextSize); | ||
precision = ta.getFloat(R.styleable.AutofitTextView_precision, precision); | ||
ta.recycle(); | ||
|
||
helper.setMinTextSize(TypedValue.COMPLEX_UNIT_PX, minTextSize) | ||
.setPrecision(precision); | ||
.setPrecision(precision); | ||
} | ||
helper.setEnabled(sizeToFit); | ||
helper.setAutofitWidthEnabled(autofitWidthEnabled); | ||
helper.setAutofitHeightEnabled(autofitHeightEnabled); | ||
|
||
return helper; | ||
} | ||
|
@@ -89,7 +95,7 @@ public static AutofitHelper create(TextView view, AttributeSet attrs, int defSty | |
* Re-sizes the textSize of the TextView so that the text fits within the bounds of the View. | ||
*/ | ||
private static void autofit(TextView view, TextPaint paint, float minTextSize, float maxTextSize, | ||
int maxLines, float precision) { | ||
int maxLines, float precision, boolean isAutofitWidthEnabled, boolean isAutofitHeightEnabled) { | ||
if (maxLines <= 0 || maxLines == Integer.MAX_VALUE) { | ||
// Don't auto-size since there's no limit on lines. | ||
return; | ||
|
@@ -128,19 +134,43 @@ private static void autofit(TextView view, TextPaint paint, float minTextSize, f | |
displayMetrics); | ||
} | ||
|
||
if (isAutofitHeightEnabled) { | ||
int targetHeight = view.getHeight() - view.getPaddingTop() - view.getPaddingBottom(); | ||
if (targetHeight > 0) { | ||
float textHeight = getTextHeight(text, paint, targetWidth, size); | ||
float heightRatio = targetHeight / textHeight; | ||
float newSize = size * heightRatio; | ||
if (newSize < size) { | ||
size = newSize; | ||
} | ||
} | ||
} | ||
|
||
if (size < minTextSize) { | ||
size = minTextSize; | ||
} | ||
|
||
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, size); | ||
} | ||
|
||
/** | ||
* Try to fit the text with current size to a static layout to calculate height needed | ||
* by that text size. | ||
* @note Can be put in a loop where text size is gradually decreased etc. | ||
* @return float The height size required by the text. | ||
*/ | ||
private static float getTextHeight(CharSequence text, TextPaint paint, int width, float textSize) { | ||
StaticLayout textHeightAdjuster = new StaticLayout(text, paint, width, | ||
Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true); | ||
return textHeightAdjuster.getHeight(); | ||
} | ||
|
||
/** | ||
* Recursive binary search to find the best size for the text. | ||
*/ | ||
private static float getAutofitTextSize(CharSequence text, TextPaint paint, | ||
float targetWidth, int maxLines, float low, float high, float precision, | ||
DisplayMetrics displayMetrics) { | ||
float targetWidth, int maxLines, float low, float high, float precision, | ||
DisplayMetrics displayMetrics) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: revert |
||
float mid = (low + high) / 2.0f; | ||
int lineCount = 1; | ||
StaticLayout layout = null; | ||
|
@@ -196,7 +226,7 @@ else if (lineCount < maxLines) { | |
} | ||
|
||
private static int getLineCount(CharSequence text, TextPaint paint, float size, float width, | ||
DisplayMetrics displayMetrics) { | ||
DisplayMetrics displayMetrics) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: revert |
||
paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, size, | ||
displayMetrics)); | ||
StaticLayout layout = new StaticLayout(text, paint, (int)width, | ||
|
@@ -232,8 +262,9 @@ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { | |
private float mMaxTextSize; | ||
private float mPrecision; | ||
|
||
private boolean mEnabled; | ||
private boolean mIsAutofitWidthEnabled; | ||
private boolean mIsAutofitting; | ||
private boolean mIsAutofitHeightEnabled; | ||
|
||
private ArrayList<OnTextSizeChangeListener> mListeners; | ||
|
||
|
@@ -418,20 +449,21 @@ public AutofitHelper setMaxLines(int lines) { | |
} | ||
|
||
/** | ||
* Returns whether or not automatically resizing text is enabled. | ||
* Returns whether or not automatically resizing text | ||
* by width and number of lines is enabled. | ||
*/ | ||
public boolean isEnabled() { | ||
return mEnabled; | ||
public boolean isAutofitWidthEnabled() { | ||
return mIsAutofitWidthEnabled; | ||
} | ||
|
||
/** | ||
* Set the enabled state of automatically resizing text. | ||
*/ | ||
public AutofitHelper setEnabled(boolean enabled) { | ||
if (mEnabled != enabled) { | ||
mEnabled = enabled; | ||
public AutofitHelper setAutofitWidthEnabled(boolean autofitWidthEnabled) { | ||
if (mIsAutofitWidthEnabled != autofitWidthEnabled) { | ||
mIsAutofitWidthEnabled = autofitWidthEnabled; | ||
|
||
if (enabled) { | ||
if (autofitWidthEnabled) { | ||
mTextView.addTextChangedListener(mTextWatcher); | ||
mTextView.addOnLayoutChangeListener(mOnLayoutChangeListener); | ||
|
||
|
@@ -446,6 +478,27 @@ public AutofitHelper setEnabled(boolean enabled) { | |
return this; | ||
} | ||
|
||
/** | ||
* Returns whether or not automatically resizing text | ||
* by height is enabled. | ||
* @return boolean True when height scaling is on. | ||
*/ | ||
public boolean isAutofitHeightEnabled() { | ||
return mIsAutofitHeightEnabled; | ||
} | ||
|
||
/** | ||
* Sets the state of automatically resizing by text fitting in height. | ||
* Calls an autofit if it is already enabled. | ||
* @param autofitHeightEnabled The state to update the height fitting member | ||
*/ | ||
public AutofitHelper setAutofitHeightEnabled(boolean autofitHeightEnabled) { | ||
mIsAutofitHeightEnabled = autofitHeightEnabled; | ||
// Fit if required | ||
setAutofitWidthEnabled(mIsAutofitWidthEnabled); | ||
return this; | ||
} | ||
|
||
/** | ||
* Returns the original text size of the View. | ||
* | ||
|
@@ -496,7 +549,7 @@ private void autofit() { | |
float textSize; | ||
|
||
mIsAutofitting = true; | ||
autofit(mTextView, mPaint, mMinTextSize, mMaxTextSize, mMaxLines, mPrecision); | ||
autofit(mTextView, mPaint, mMinTextSize, mMaxTextSize, mMaxLines, mPrecision, mIsAutofitWidthEnabled, mIsAutofitHeightEnabled); | ||
mIsAutofitting = false; | ||
|
||
textSize = mTextView.getTextSize(); | ||
|
@@ -535,7 +588,7 @@ public void afterTextChanged(Editable editable) { | |
private class AutofitOnLayoutChangeListener implements View.OnLayoutChangeListener { | ||
@Override | ||
public void onLayoutChange(View view, int left, int top, int right, int bottom, | ||
int oldLeft, int oldTop, int oldRight, int oldBottom) { | ||
int oldLeft, int oldTop, int oldRight, int oldBottom) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: revert |
||
autofit(); | ||
} | ||
} | ||
|
@@ -551,4 +604,4 @@ public interface OnTextSizeChangeListener { | |
*/ | ||
public void onTextSizeChange(float textSize, float oldTextSize); | ||
} | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: revert |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,13 +15,15 @@ | |
* A {@link ViewGroup} that re-sizes the text of it's children to be no larger than the width of the | ||
* view. | ||
* | ||
* @attr ref R.styleable.AutofitTextView_sizeToFit | ||
* @attr ref R.styleable.AutofitTextView_autofitWidthEnabled | ||
* @attr ref R.styleable.AutofitTextView_autofitHeightEnabled | ||
* @attr ref R.styleable.AutofitTextView_minTextSize | ||
* @attr ref R.styleable.AutofitTextView_precision | ||
*/ | ||
public class AutofitLayout extends FrameLayout { | ||
|
||
private boolean mEnabled; | ||
private boolean mIsAutofitWidthEnabled; | ||
private boolean mIsAutofitHeightEnabled; | ||
private float mMinTextSize; | ||
private float mPrecision; | ||
private WeakHashMap<View, AutofitHelper> mHelpers = new WeakHashMap<View, AutofitHelper>(); | ||
|
@@ -42,7 +44,8 @@ public AutofitLayout(Context context, AttributeSet attrs, int defStyle) { | |
} | ||
|
||
private void init(Context context, AttributeSet attrs, int defStyle) { | ||
boolean sizeToFit = true; | ||
boolean autofitWidthEnabled = true; | ||
boolean autofitHeightEnabled = false; | ||
int minTextSize = -1; | ||
float precision = -1; | ||
|
||
|
@@ -52,14 +55,18 @@ private void init(Context context, AttributeSet attrs, int defStyle) { | |
R.styleable.AutofitTextView, | ||
defStyle, | ||
0); | ||
sizeToFit = ta.getBoolean(R.styleable.AutofitTextView_sizeToFit, sizeToFit); | ||
autofitWidthEnabled = ta.getBoolean(R.styleable.AutofitTextView_autofitWidthEnabled, | ||
autofitWidthEnabled); | ||
autofitHeightEnabled = ta.getBoolean(R.styleable.AutofitTextView_autofitHeightEnabled, | ||
autofitHeightEnabled); | ||
minTextSize = ta.getDimensionPixelSize(R.styleable.AutofitTextView_minTextSize, | ||
minTextSize); | ||
precision = ta.getFloat(R.styleable.AutofitTextView_precision, precision); | ||
ta.recycle(); | ||
} | ||
|
||
mEnabled = sizeToFit; | ||
mIsAutofitWidthEnabled = autofitWidthEnabled; | ||
mIsAutofitHeightEnabled = autofitHeightEnabled; | ||
mMinTextSize = minTextSize; | ||
mPrecision = precision; | ||
} | ||
|
@@ -68,8 +75,9 @@ private void init(Context context, AttributeSet attrs, int defStyle) { | |
public void addView(View child, int index, ViewGroup.LayoutParams params) { | ||
super.addView(child, index, params); | ||
TextView textView = (TextView) child; | ||
AutofitHelper helper = AutofitHelper.create(textView) | ||
.setEnabled(mEnabled); | ||
AutofitHelper helper = AutofitHelper.create(textView); | ||
helper.setAutofitWidthEnabled(mIsAutofitWidthEnabled); | ||
helper.setAutofitHeightEnabled(mIsAutofitHeightEnabled); | ||
if (mPrecision > 0) { | ||
helper.setPrecision(mPrecision); | ||
} | ||
|
@@ -92,4 +100,4 @@ public AutofitHelper getAutofitHelper(TextView textView) { | |
public AutofitHelper getAutofitHelper(int index) { | ||
return mHelpers.get(getChildAt(index)); | ||
} | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: revert |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,8 @@ | |
/** | ||
* A {@link TextView} that re-sizes its text to be no larger than the width of the view. | ||
* | ||
* @attr ref R.styleable.AutofitTextView_sizeToFit | ||
* @attr ref R.styleable.AutofitTextView_autofitWidthEnabled | ||
* @attr ref R.styleable.AutofitTextView_autofitHeightEnabled | ||
* @attr ref R.styleable.AutofitTextView_minTextSize | ||
* @attr ref R.styleable.AutofitTextView_precision | ||
*/ | ||
|
@@ -81,26 +82,49 @@ public AutofitHelper getAutofitHelper() { | |
/** | ||
* Returns whether or not the text will be automatically re-sized to fit its constraints. | ||
*/ | ||
public boolean isSizeToFit() { | ||
return mHelper.isEnabled(); | ||
public boolean isAutofitWidthEnabled() { | ||
return mHelper.isAutofitWidthEnabled(); | ||
} | ||
|
||
/** | ||
* Sets the property of this field (sizeToFit), to automatically resize the text to fit its | ||
* Sets the property of this field (autofitWidthEnabled), to automatically resize the text to fit its | ||
* constraints. | ||
*/ | ||
public void setSizeToFit() { | ||
setSizeToFit(true); | ||
public void setAutofitWidthEnabled() { | ||
setAutofitWidthEnabled(true); | ||
} | ||
|
||
/** | ||
* If true, the text will automatically be re-sized to fit its constraints; if false, it will | ||
* act like a normal TextView. | ||
* | ||
* @param sizeToFit | ||
* @param autofitWidthEnabled | ||
*/ | ||
public void setSizeToFit(boolean sizeToFit) { | ||
mHelper.setEnabled(sizeToFit); | ||
public void setAutofitWidthEnabled(boolean autofitWidthEnabled) { | ||
mHelper.setAutofitWidthEnabled(autofitWidthEnabled); | ||
} | ||
|
||
/** | ||
* Returns whether or not the text will be automatically re-sized to fit its height. | ||
*/ | ||
public boolean isAutofitHeightEnabled() { | ||
return mHelper.isAutofitHeightEnabled(); | ||
} | ||
|
||
/** | ||
* Sets the property of this field (autofitHeightEnabled), to automatically resize the text to fit | ||
* its height. | ||
*/ | ||
public void setAutofitHeightEnabled() { | ||
setAutofitHeightEnabled(true); | ||
} | ||
|
||
/** | ||
* Enables automatic text resizing to fit the textview height | ||
* @param autofitHeightEnabled If true, the text will automatically be re-sized to fit its height | ||
*/ | ||
public void setAutofitHeightEnabled(boolean autofitHeightEnabled) { | ||
mHelper.setAutofitHeightEnabled(autofitHeightEnabled); | ||
} | ||
|
||
/** | ||
|
@@ -189,4 +213,4 @@ public void setPrecision(float precision) { | |
public void onTextSizeChange(float textSize, float oldTextSize) { | ||
// do nothing | ||
} | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: revert |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,14 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<resources> | ||
|
||
<declare-styleable name="AutofitTextView"> | ||
<!-- Minimum size of the text. --> | ||
<attr name="minTextSize" format="dimension" /> | ||
<!-- Amount of precision used to calculate the correct text size to fit within its | ||
bounds. Lower precision is more precise and takes more time. --> | ||
<attr name="precision" format="float" /> | ||
<!-- Defines whether to automatically resize text to fit to the view's bounds. --> | ||
<attr name="sizeToFit" format="boolean" /> | ||
<attr name="minTextSize" format="dimension"/> | ||
<!-- Amount of precision used to calculate the correct text size to fit within its bounds. | ||
Lower precision is more precise and takes more time. --> | ||
<attr name="precision" format="float"/> | ||
<!-- Defines whether to automatically resize text to fit to the view's width bounds. --> | ||
<attr name="autofitWidthEnabled" format="boolean"/> | ||
<!-- Defines whether to automatically resize text to fit to the view's height bounds. --> | ||
<attr name="autofitHeightEnabled" format="boolean"/> | ||
</declare-styleable> | ||
</resources> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this method of calculating a fitting textSize to match the height is interesting and different than how we calculate fitting textSize to match the width... are we guaranteed that text height changes proportionally with textSize? if we are, could we use a similar method for width? if not, should we use a similar procedure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually not super familiar with StaticLayout; that's just what KonradJanica used to calculate height. However, I think this article on The Mathematics of Golden Ratio Typography sheds some light on the mathematics between text height, text width, and text size.