How to align View vertically to the text inside TextView?
So after an investigation found two solutions for this:
- Calculation of text width and applying a horizontal bias to
ImageView
s; - Custom text view -
AutoResizeTextView
;
In the first case, we get actual width
of text in the TextView
:
Rect bounds = new Rect();textView.getPaint().getTextBounds(textView.getText().toString(), 0, textView.getText().length(), bounds);float width = bounds.width();
And then apply simple math to calculate horizontal bias
for ImageView
s.
Second option is to add custom view. It is differ from autosizing-textview . It will resize text after measuring in onLayout
. And in onTextChanged
we reset text size without resizing. Ellipsizing it's an additional feature which may be useful in some cases.
/** * Text view that auto adjusts text size to fit within the view. * If the text size equals the minimum text size and still does not * fit, append with an ellipsis. */public class AutoResizeTextView extends android.support.v7.widget.AppCompatTextView { // Minimum text size for this text view public static final float MIN_TEXT_SIZE = 20; // Interface for resize notifications public interface OnTextResizeListener { public void onTextResize(TextView textView, float oldSize, float newSize); } // Our ellipse string private static final String mEllipsis = "..."; // Registered resize listener private OnTextResizeListener mTextResizeListener; // Flag for text and/or size changes to force a resize private boolean mNeedsResize = false; // Text size that is set from code. This acts as a starting point for resizing private float mTextSize; // Temporary upper bounds on the starting text size private float mMaxTextSize = 0; // Lower bounds for text size private float mMinTextSize = MIN_TEXT_SIZE; // Text view line spacing multiplier private float mSpacingMult = 1.0f; // Text view additional line spacing private float mSpacingAdd = 0.0f; // Add ellipsis to text that overflows at the smallest text size private boolean mAddEllipsis = true; // Default constructor override public AutoResizeTextView(Context context) { this(context, null); } // Default constructor when inflating from XML file public AutoResizeTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } // Default constructor override public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mTextSize = getTextSize(); } /** * When text changes, set the force resize flag to true and reset the text size. */ @Override protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) { mNeedsResize = 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 */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { if (w != oldw || h != oldh) { mNeedsResize = true; } } /** * Register listener to receive resize notifications * * @param listener */ public void setOnResizeListener(AutoResizeTextView.OnTextResizeListener listener) { mTextResizeListener = listener; } /** * Override the set text size to update our internal reference values */ @Override public void setTextSize(float size) { super.setTextSize(size); mTextSize = getTextSize(); } /** * 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 */ @Override public void setLineSpacing(float add, float mult) { super.setLineSpacing(add, mult); mSpacingMult = mult; mSpacingAdd = add; } /** * Set the upper text size limit and invalidate the view * * @param maxTextSize */ public void setMaxTextSize(float maxTextSize) { mMaxTextSize = maxTextSize; requestLayout(); invalidate(); } /** * Return upper text size limit * * @return */ public float getMaxTextSize() { return mMaxTextSize; } /** * Set the lower text size limit and invalidate the view * * @param minTextSize */ public void setMinTextSize(float minTextSize) { mMinTextSize = minTextSize; requestLayout(); invalidate(); } /** * Return lower text size limit * * @return */ public float getMinTextSize() { return mMinTextSize; } /** * Set flag to add ellipsis to text that overflows at the smallest text size * * @param addEllipsis */ public void setAddEllipsis(boolean addEllipsis) { mAddEllipsis = addEllipsis; } /** * Return flag to add ellipsis to text that overflows at the smallest text size * * @return */ public boolean getAddEllipsis() { return mAddEllipsis; } /** * Reset the text to the original size */ public void resetTextSize() { if (mTextSize > 0) { super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize); mMaxTextSize = mTextSize; } } /** * Resize text after measuring */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (changed || mNeedsResize) { int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight(); int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop(); resizeText(widthLimit, heightLimit); } super.onLayout(changed, left, top, right, bottom); } /** * Resize the text size with default width and height */ public void resizeText() { int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop(); int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight(); resizeText(widthLimit, heightLimit); } /** * Resize the text size with specified width and height * * @param width * @param height */ public void resizeText(int width, int height) { CharSequence text = getText(); // Do not resize if the view does not have dimensions or there is no text if (text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) { return; } if (getTransformationMethod() != null) { text = getTransformationMethod().getTransformation(text, this); } // Get the text view's paint object TextPaint textPaint = getPaint(); // Store the current text size float oldTextSize = textPaint.getTextSize(); // If there is a max text size set, use the lesser of that and the default text size float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize; // Get the required text height int textHeight = getTextHeight(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 && targetTextSize > mMinTextSize) { targetTextSize = Math.max(targetTextSize - 2, mMinTextSize); textHeight = getTextHeight(text, textPaint, width, targetTextSize); } // If we had reached our minimum text size and still don't fit, append an ellipsis if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) { // Draw using a static layout // modified: use a copy of TextPaint for measuring TextPaint paint = new TextPaint(textPaint); // Draw using a static layout StaticLayout layout = new StaticLayout(text, paint, width, Layout.Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false); // Check that we have a least one line of rendered text if (layout.getLineCount() > 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) { setText(""); } // 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(mEllipsis); // Trim characters off until we have enough room to draw the ellipsis while (width < lineWidth + ellipseWidth) { lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString()); } setText(text.subSequence(0, end) + mEllipsis); } } } // Some devices try to auto adjust line spacing, so force default line spacing // and invalidate the layout as a side effect setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize); setLineSpacing(mSpacingAdd, mSpacingMult); // Notify the listener if registered if (mTextResizeListener != null) { mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize); } // Reset force resize flag mNeedsResize = false; } // Set the text size of the text paint object and use a static layout to render text off screen before measuring private int getTextHeight(CharSequence source, TextPaint paint, int width, float textSize) { // modified: make a copy of the original TextPaint object for measuring // (apparently the object gets modified while measuring, see also the // docs for TextView.getPaint() (which states to access it read-only) TextPaint paintCopy = new TextPaint(paint); // Update the text paint object paintCopy.setTextSize(textSize); // Measure using a static layout StaticLayout layout = new StaticLayout(source, paintCopy, width, Layout.Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true); return layout.getHeight(); }}
Code source: https://gist.github.com/chathudan/61fd2f6e5919738d7821
Playing around with some set values for sizing that you can adjust to suit your app, I achieved this look using xml alone.
The images a gammy, I cut them from you original image. I've utilised the constraint layout and a relative layout. The text responds to resizing and rotation.
A vertical view
A horizontal view
Small text
Large text
Of course play around with the sizing to suit your needs.
<?xml version="1.0" encoding="utf-8"?><android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context="com.yvettecolomb.myapplication.MainActivity" tools:showIn="@layout/activity_main"> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/ll1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_marginLeft="0dp" android:layout_marginRight="0dp" android:layout_marginTop="10dp" android:padding="0dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"> <TextView android:id="@+id/tv1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_marginTop="10dp" android:autoSizeTextType="uniform" android:gravity="center" android:padding="2dp" android:text="TEST"/> <ImageView android:id="@+id/image1" android:layout_width="wrap_content" android:layout_height="30dp" android:layout_margin="-10dp" android:layout_toLeftOf="@+id/tv1" android:src="@drawable/a"/> <ImageView android:id="@+id/image2" android:layout_width="wrap_content" android:layout_height="30dp" android:layout_toRightOf="@+id/tv1" android:src="@drawable/b"/> <ImageView android:id="@+id/image3" android:layout_width="wrap_content" android:layout_height="30dp" android:layout_below="@+id/tv1" android:layout_margin="-10dp" android:layout_toLeftOf="@+id/tv2" android:src="@drawable/c"/> <TextView android:id="@+id/tv2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/tv1" android:layout_centerHorizontal="true" android:layout_marginTop="10dp" android:autoSizeTextType="uniform" android:gravity="center" android:padding="2dp" android:text="test test test test test"/> <ImageView android:id="@+id/image4" android:layout_width="wrap_content" android:layout_height="30dp" android:layout_below="@+id/tv1" android:layout_toRightOf="@+id/tv2" android:src="@drawable/d"/> </RelativeLayout></android.support.constraint.ConstraintLayout>
Use AppCompatTextView. Change autoSizeMaxTextSize, autoSizeMinTextSize and autoSizeStepGranularity values.
<RelativeLayout xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/rl_flashcard_back" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v7.widget.AppCompatTextView android:id="@+id/tv_text" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true" android:gravity="center" app:autoSizeMaxTextSize="16sp" app:autoSizeMinTextSize="8sp" app:autoSizeStepGranularity="1sp" app:autoSizeTextType="uniform" />android.support.v7.widget.AppCompatTextView tv_text = mRootView.findViewById(R.id.tv_text);
https://developer.android.com/guide/topics/ui/look-and-feel/autosizing-textview