Как выровнять вид по вертикали по тексту внутри TextView?

Я пытаюсь добиться такого результата:

Как выровнять вид по вертикали по тексту внутри TextView?

Сейчас у меня есть ConstraintLayout, который включает три Views: TextView, ImageView, ImageView.

Требования к дизайну:

  1. Текст по центру TextView;
  2. TextView имеет фиксированный размер 80dp
  3. Размер текста должен быть AutoSizeable;
  4. ImageViews должен менять положение по горизонтали в зависимости от положения текста внутри TextView. См. Изображения с делом №1 / №2 для справки.

Можно ли достичь пункта 4 в XML? А программно?

[Дополнительная информация]

Автоматическое изменение размера не работает должным образом с шириной wrap_content, см. документация по автоподбору:

Note: If you set autosizing in an XML file, it is not recommended to use the value "wrap_content" for the layout_width or layout_height attributes of a TextView. It may produce unexpected results.

Вы ответили на свой вопрос, и это здорово. Похоже, вы применили награду после ответа на свой вопрос. Если да, то как насчет вашего ответа вас не устраивает?

Cheticamp 26.10.2018 17:43

Мое решение работает хорошо, но я не уверен в эффективности. И я хочу получить больше взглядов и мнений по этому поводу. У нас есть такая простая штука, как layout_constraintBaseline_toBaselineOf, для выравнивания видов по горизонтали. А как насчет вертикального? Может, в будущем добавят :) Как лучше это сделать? @Cheticamp

Mykhailo 26.10.2018 18:08
16
2
1 473
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Итак, после расследования было найдено два решения для этого:

  • Расчет ширины текста и применение горизонтального смещения к ImageView;
  • Пользовательский текстовый вид - AutoResizeTextView;

В первый случай мы получаем фактический width текста в TextView:

Rect bounds = new Rect();
textView.getPaint().getTextBounds(textView.getText().toString(), 0, textView.getText().length(), bounds);
float width = bounds.width();

А затем примените простую математику, чтобы вычислить horizontal bias для ImageView.

Второй вариант - добавить настраиваемый вид. Он отличается от autosizing-textview. Размер текста изменится после измерения в onLayout. А в onTextChanged мы сбрасываем размер текста без изменения размера. Эллипсирование - это дополнительная функция, которая может быть полезна в некоторых случаях.

/**
 * 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();
    }
}

Источник кода: https://gist.github.com/chathudan/61fd2f6e5919738d7821

Играя с некоторыми заданными значениями размеров, которые вы можете настроить в соответствии с вашим приложением, я добился этого, используя только xml.

Изображения глухие, я вырезал их из оригинального изображения. Я использовал компоновку ограничений и относительную компоновку. Текст реагирует на изменение размера и поворот.

Вертикальный вид

Горизонтальный вид

Мелкий текст

Большой текст

Конечно, поиграйте с размерами, чтобы они соответствовали вашим потребностям.

    <?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>

Используйте AppCompatTextView. Измените значения autoSizeMaxTextSize, autoSizeMinTextSize и autoSizeStepGranularity.

                    <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

Другие вопросы по теме