경계에 맞게 텍스트 크기 조정 텍스트보기 텍스트 만들고 싶습니다. 자동 크기 조정이 필요한

TextViewgetHeight 및 getWidth 범위 내에 맞도록 텍스트 줄 바꿈의 크기를 조정하는 최적의 방법을 찾고 있습니다. 나는 단순히 텍스트를 줄 바꿈하는 방법을 찾고 있지 않습니다. 나는 텍스트를 줄 바꿈하고 화면에 완전히 들어갈 수 있도록 작게 만들고 싶습니다.

자동 크기 조정이 필요한 StackOverflow에서 몇 가지 사례를 보았지만 해킹 솔루션이있는 매우 특별한 경우이거나 해결책이 없거나 TextView충분히 작을 때까지 재귀 적으로 다시 그려야합니다 (메모리가 강렬하고 사용자는 모든 재귀마다 텍스트가 단계별로 축소되는 것을 볼 수 있습니다).

그러나 누군가가 내가하고있는 일과 관련이없는 좋은 해결책을 찾았습니다. 텍스트를 구문 분석하고 측정하고 텍스트의 크기를 조정하고 적절하게 작은 크기가 발견 될 때까지 반복하는 몇 가지 무거운 루틴을 작성하십시오.

TextView텍스트를 감싸는 데 어떤 루틴을 사용합니까? 텍스트가 충분히 작을 지 예측하는 데 어떻게 사용할 수 없습니까?

tl; dr : TextViewgetHeight 및 getWidth 범위에서 a 에 맞게 자동으로 크기를 조정하고 감싸는 가장 좋은 방법이 있습니까?



답변

2018 년 6 월부터 Android는 공식적으로 Android 4.0 (API 레벨 14) 이상 에서이 기능을 지원하기 시작했습니다 . TextViews 자동 크기 조정
에서 확인하십시오.

Android 8.0 (API 레벨 26) 이상 :

<?xml version="1.0" encoding="utf-8"?>
<TextView
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:autoSizeTextType="uniform"
    android:autoSizeMinTextSize="12sp"
    android:autoSizeMaxTextSize="100sp"
    android:autoSizeStepGranularity="2sp" />

프로그래밍 방식으로 :

setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize,
        int autoSizeStepGranularity, int unit)

textView.setAutoSizeTextTypeUniformWithConfiguration(
                1, 17, 1, TypedValue.COMPLEX_UNIT_DIP);

Android 8.0 이전의 Android 버전 (API 레벨 26) :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

  <TextView
      android:layout_width="match_parent"
      android:layout_height="200dp"
      app:autoSizeTextType="uniform"
      app:autoSizeMinTextSize="12sp"
      app:autoSizeMaxTextSize="100sp"
      app:autoSizeStepGranularity="2sp" />

</LinearLayout>

프로그래밍 방식으로 :

TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(
TextView textView, int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)

TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(textView, 1, 17, 1,
TypedValue.COMPLEX_UNIT_DIP);

주의 : TextView 는 layout_width = ” match_parent “또는 절대 크기 여야합니다 !


답변

모바일 개발자로서, 자동 크기 조정을 지원하는 네이티브를 찾지 못해 슬 sad습니다. 검색 결과가 나에게 도움이되는 결과를 얻지 못했지만 결국 주말의 절반을 더 많이 보냈고 자동 크기 조정 텍스트보기를 만들었습니다. 여기에 코드를 게시하고 다른 사람에게 도움이되기를 바랍니다.

이 클래스는 원래 텍스트 뷰의 텍스트 페인트와 함께 정적 레이아웃을 사용하여 높이를 측정합니다. 거기에서 2 글꼴 픽셀 씩 내려 가서 맞는 크기가 될 때까지 다시 측정합니다. 결국 텍스트가 여전히 맞지 않으면 줄임표를 추가합니다. 텍스트에 애니메이션을 적용하고보기를 재사용해야하는 요구 사항이 있었으며, 이것이 가지고있는 장치에서 제대로 작동하는 것 같고 빠른 속도로 실행되는 것 같습니다.

/**
 *               DO WHAT YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 *
 * Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
 *
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 *
 *            DO WHAT YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 *
 *  0. You just DO WHAT YOU WANT TO.
 */

import android.content.Context;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

/**
 * 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.
 *
 * @author Chase Colburn
 * @since Apr 4, 2011
 */
public class AutoResizeTextView extends TextView {

    // 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(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, 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, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        return layout.getHeight();
    }

}

경고. 모든 AutoResizingTextView 위젯이 작동하지 않도록하는 Android 3.1-4.04에 영향을 미치는 중요한 수정 된 버그가 있습니다. 읽어보십시오 : https : //.com/a/21851157/2075875


답변

업데이트 : 다음 코드는 여기에 설명 된대로 이상적인 AutoScaleTextView 의 요구 사항을 충족합니다 .Android 용 TextView 자동 맞춤 및 승자로 표시됩니다.

업데이트 2 : maxlines 지원이 추가되어 이제 API 레벨 16 이전에 제대로 작동합니다.

업데이트 3 : 지원 android:drawableLeft, android:drawableRight, android:drawableTopandroid:drawableBottom태그 MartinH의 간단한 수정에 감사를 추가 여기 .


내 요구 사항은 조금 다릅니다. 정수를 애니메이션으로 만들었으므로 크기를 조정하는 효율적인 방법이 필요 TextView했습니다 .2 초 안에 0 ~ ~ 4000 일 수 있으며 그에 따라 크기를 조정하고 싶었습니다. 내 솔루션은 조금 다르게 작동합니다. 최종 결과는 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

그리고 그것을 생성 한 코드 :

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp" >

    <com.vj.widgets.AutoResizeTextView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:ellipsize="none"
        android:maxLines="2"
        android:text="Auto Resized Text, max 2 lines"
        android:textSize="100sp" /> <!-- maximum size -->

    <com.vj.widgets.AutoResizeTextView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:ellipsize="none"
        android:gravity="center"
        android:maxLines="1"
        android:text="Auto Resized Text, max 1 line"
        android:textSize="100sp" /> <!-- maximum size -->

    <com.vj.widgets.AutoResizeTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Auto Resized Text"
        android:textSize="500sp" /> <!-- maximum size -->

</LinearLayout>

그리고 마지막으로 자바 코드 :

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.RectF;
import android.os.Build;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.widget.TextView;

public class AutoResizeTextView extends TextView {
private interface SizeTester {
    /**
     *
     * @param suggestedSize
     *            Size of text to be tested
     * @param availableSpace
     *            available space in which text must fit
     * @return an integer < 0 if after applying {@code suggestedSize} to
     *         text, it takes less space than {@code availableSpace}, > 0
     *         otherwise
     */
    public int onTestSize(int suggestedSize, RectF availableSpace);
}

private RectF mTextRect = new RectF();

private RectF mAvailableSpaceRect;

private SparseIntArray mTextCachedSizes;

private TextPaint mPaint;

private float mMaxTextSize;

private float mSpacingMult = 1.0f;

private float mSpacingAdd = 0.0f;

private float mMinTextSize = 20;

private int mWidthLimit;

private static final int NO_LINE_LIMIT = -1;
private int mMaxLines;

private boolean mEnableSizeCache = true;
private boolean mInitiallized;

public AutoResizeTextView(Context context) {
    super(context);
    initialize();
}

public AutoResizeTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initialize();
}

public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    initialize();
}

private void initialize() {
    mPaint = new TextPaint(getPaint());
    mMaxTextSize = getTextSize();
    mAvailableSpaceRect = new RectF();
    mTextCachedSizes = new SparseIntArray();
    if (mMaxLines == 0) {
        // no value was assigned during construction
        mMaxLines = NO_LINE_LIMIT;
    }
    mInitiallized = true;
}

@Override
public void setText(final CharSequence text, BufferType type) {
    super.setText(text, type);
    adjustTextSize(text.toString());
}

@Override
public void setTextSize(float size) {
    mMaxTextSize = size;
    mTextCachedSizes.clear();
    adjustTextSize(getText().toString());
}

@Override
public void setMaxLines(int maxlines) {
    super.setMaxLines(maxlines);
    mMaxLines = maxlines;
    reAdjust();
}

public int getMaxLines() {
    return mMaxLines;
}

@Override
public void setSingleLine() {
    super.setSingleLine();
    mMaxLines = 1;
    reAdjust();
}

@Override
public void setSingleLine(boolean singleLine) {
    super.setSingleLine(singleLine);
    if (singleLine) {
        mMaxLines = 1;
    } else {
        mMaxLines = NO_LINE_LIMIT;
    }
    reAdjust();
}

@Override
public void setLines(int lines) {
    super.setLines(lines);
    mMaxLines = lines;
    reAdjust();
}

@Override
public void setTextSize(int unit, float size) {
    Context c = getContext();
    Resources r;

    if (c == null)
        r = Resources.getSystem();
    else
        r = c.getResources();
    mMaxTextSize = TypedValue.applyDimension(unit, size,
            r.getDisplayMetrics());
    mTextCachedSizes.clear();
    adjustTextSize(getText().toString());
}

@Override
public void setLineSpacing(float add, float mult) {
    super.setLineSpacing(add, mult);
    mSpacingMult = mult;
    mSpacingAdd = add;
}

/**
 * Set the lower text size limit and invalidate the view
 *
 * @param minTextSize
 */
public void setMinTextSize(float minTextSize) {
    mMinTextSize = minTextSize;
    reAdjust();
}

private void reAdjust() {
    adjustTextSize(getText().toString());
}

private void adjustTextSize(String string) {
    if (!mInitiallized) {
        return;
    }
    int startSize = (int) mMinTextSize;
    int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom()
        - getCompoundPaddingTop();
    mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft()
        - getCompoundPaddingRight();
    mAvailableSpaceRect.right = mWidthLimit;
    mAvailableSpaceRect.bottom = heightLimit;
    super.setTextSize(
            TypedValue.COMPLEX_UNIT_PX,
            efficientTextSizeSearch(startSize, (int) mMaxTextSize,
                    mSizeTester, mAvailableSpaceRect));
}

private final SizeTester mSizeTester = new SizeTester() {
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public int onTestSize(int suggestedSize, RectF availableSPace) {
        mPaint.setTextSize(suggestedSize);
        String text = getText().toString();
        boolean singleline = getMaxLines() == 1;
        if (singleline) {
            mTextRect.bottom = mPaint.getFontSpacing();
            mTextRect.right = mPaint.measureText(text);
        } else {
            StaticLayout layout = new StaticLayout(text, mPaint,
                    mWidthLimit, Alignment.ALIGN_NORMAL, mSpacingMult,
                    mSpacingAdd, true);
            // return early if we have more lines
            if (getMaxLines() != NO_LINE_LIMIT
                    && layout.getLineCount() > getMaxLines()) {
                return 1;
            }
            mTextRect.bottom = layout.getHeight();
            int maxWidth = -1;
            for (int i = 0; i < layout.getLineCount(); i++) {
                if (maxWidth < layout.getLineWidth(i)) {
                    maxWidth = (int) layout.getLineWidth(i);
                }
            }
            mTextRect.right = maxWidth;
        }

        mTextRect.offsetTo(0, 0);
        if (availableSPace.contains(mTextRect)) {
            // may be too small, don't worry we will find the best match
            return -1;
        } else {
            // too big
            return 1;
        }
    }
};

/**
 * Enables or disables size caching, enabling it will improve performance
 * where you are animating a value inside TextView. This stores the font
 * size against getText().length() Be careful though while enabling it as 0
 * takes more space than 1 on some fonts and so on.
 *
 * @param enable
 *            enable font size caching
 */
public void enableSizeCache(boolean enable) {
    mEnableSizeCache = enable;
    mTextCachedSizes.clear();
    adjustTextSize(getText().toString());
}

private int efficientTextSizeSearch(int start, int end,
        SizeTester sizeTester, RectF availableSpace) {
    if (!mEnableSizeCache) {
        return binarySearch(start, end, sizeTester, availableSpace);
    }
    String text = getText().toString();
    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;

}

@Override
protected void onTextChanged(final CharSequence text, final int start,
        final int before, final int after) {
    super.onTextChanged(text, start, before, after);
    reAdjust();
}

@Override
protected void onSizeChanged(int width, int height, int oldwidth,
        int oldheight) {
    mTextCachedSizes.clear();
    super.onSizeChanged(width, height, oldwidth, oldheight);
    if (width != oldwidth || height != oldheight) {
        reAdjust();
    }
}
}


답변

실제로 솔루션은 Google의 DialogTitle 클래스에 있습니다 … 허용되는 것만 큼 효과적이지는 않지만 훨씬 간단하고 적응하기 쉽습니다.

public class SingleLineTextView extends TextView {

  public SingleLineTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    setSingleLine();
    setEllipsize(TruncateAt.END);
  }

  public SingleLineTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
    setSingleLine();
    setEllipsize(TruncateAt.END);
  }

  public SingleLineTextView(Context context) {
    super(context);
    setSingleLine();
    setEllipsize(TruncateAt.END);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    final Layout layout = getLayout();
    if (layout != null) {
      final int lineCount = layout.getLineCount();
      if (lineCount > 0) {
        final int ellipsisCount = layout.getEllipsisCount(lineCount - 1);
        if (ellipsisCount > 0) {

          final float textSize = getTextSize();

          // textSize is already expressed in pixels
          setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1));

          super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
      }
    }
  }

}


답변

Chase의 솔루션으로 시작했지만 장치 (Galaxy Nexus, Android 4.1)에서 예상대로 작동하기 전에 두 가지를 조정해야했습니다.

  1. 레이아웃을 측정하기 위해 TextPaint의 복사본 사용 TextView.getPaint ()에 대한 문서는 읽기 전용으로 사용해야하므로 페인트 개체를 측정에 사용하는 두 곳에서 복사했습니다.

    // 1. in resizeText()
    if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
      // Draw using a static layout
      // modified: use a copy of TextPaint for measuring
      TextPaint paint = new TextPaint(textPaint);
    
    // 2. in getTextHeight()
    private int getTextHeight(CharSequence source, TextPaint originalPaint, 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 paint = new TextPaint(originalPaint);
      // Update the text paint object
      paint.setTextSize(textSize);
      ...
  2. 텍스트 크기 설정에 단위 추가

    // modified: setting text size via this.setTextSize (instead of textPaint.setTextSize(targetTextSize))
    setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
    setLineSpacing(mSpacingAdd, mSpacingMult);

이 두 가지 수정으로 솔루션이 완벽하게 작동합니다. Chase! 원래 솔루션이 작동하지 않은 것이 Android 4.x 때문인지 모르겠습니다. 실제로 작동하는지 또는 장치에서 실제로 작동하는지 테스트하려는 경우이 솔루션을 사용하여 플래시 카드 텍스트의 크기를 조정하는 플래시 카드 응용 프로그램 Flashcards ToGo를 살펴볼 수 있습니다 . 텍스트는 임의의 길이를 가질 수 있으며 플래시 카드는 다른 활동으로 표시되며 때로는 더 작거나 때로는 더 커지고 가로 + 세로 모드에서 표시되며 솔루션이 제대로 작동하지 않는 코너 케이스를 찾지 못했습니다 …


답변

Chase의 AutoResizeTextView 클래스로 시작하여 세로 및 가로에 맞게 약간 변경했습니다.

또한 다소 모호한 조건에서 Eclipse의 레이아웃 편집기에서 Null 포인터 예외를 발생시키는 버그를 발견했습니다.

변경 1 : 텍스트를 세로 및 가로로 맞추십시오.

체이스의 오리지널 버전은 텍스트가 세로에 맞을 때까지 텍스트 크기를 줄이지 만 텍스트가 대상보다 넓을 수 있습니다. 제 경우에는 지정된 너비에 맞게 텍스트가 필요했습니다.

이 변경으로 인해 텍스트가 세로와 가로에 모두 맞을 때까지 크기가 조정됩니다.

에서 resizeText(INT ,INT) 변화에서 :

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

에:

// 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 > mMinTextSize) {
    targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
    textHeight = getTextHeight(text, textPaint, width, targetTextSize);
    textWidth  = getTextWidth(text, textPaint, width, targetTextSize);
    }

그런 다음 파일의 끝에 getTextWidth()루틴을 추가하십시오 . 약간 수정되었습니다 getTextHeight(). 높이와 너비를 모두 반환하는 하나의 루틴으로 결합하는 것이 더 효율적일 것입니다.

// Set the text size of the text paint object and use a static layout to render text off screen before measuring
private int getTextWidth(CharSequence source, TextPaint paint, int width, float textSize) {
    // Update the text paint object
    paint.setTextSize(textSize);
    // Draw using a static layout
    StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
    layout.draw(sTextResizeCanvas);
    return layout.getWidth();
}  

변경 2 : Eclipse Android 레이아웃 편집기에서 EmptyStackException 수정

다소 모호하고 매우 정확한 조건에서 레이아웃 편집기는 레이아웃의 그래픽 표시를 표시하지 않습니다. com.android.ide.eclipse.adt에서 “EmptyStackException : null”예외가 발생합니다.

필요한 조건은 다음과 같습니다.
-AutoResizeTextView 위젯
작성-해당 위젯에 대한 스타일 작성-스타일에
텍스트 항목을 지정하십시오. 위젯 정의에 없음

에서와 같이 :

res / layout / main.xml :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <com.ajw.DemoCrashInADT.AutoResizeTextView
        android:id="@+id/resizingText"
        style="@style/myTextStyle" />

</LinearLayout>

res / values ​​/ myStyles.xml :

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <style name="myTextStyle" parent="@android:style/Widget.TextView">
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_width">fill_parent</item>
        <item name="android:text">some message</item>
    </style>

</resources>

이러한 파일을 사용하여 편집 할 때 그래픽 레이아웃 탭을 선택하면 다음 main.xml이 표시됩니다.

오류!
EmptyStackException : null
예외 정보가 Window> Show View> Error Log에 기록됩니다.

레이아웃의 그래픽보기 대신.

이미 너무 긴 이야기를 더 짧게 유지하기 위해 다음 줄로 다시 추적했습니다 resizeText.

// 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;

문제는 특정 조건에서 mTextSize가 초기화되지 않는다는 것입니다. 값은 0입니다.

위와 같이, targetTextSizeMath.min의 결과로 0으로 설정됩니다.

이 0은 인수 로 getTextHeight()(and getTextWidth())에 전달됩니다 textSize. 가에 도달 할 때
layout.draw(sTextResizeCanvas);
우리 예외를 얻을.

그것은 만약 테스트에 더 효율적 (mTextSize == 0)의 시작 부분 resizeText()에 테스트보다는 getTextHeight()getTextWidth() . 조기에 테스트하면 모든 개입 작업이 절약됩니다.

이러한 업데이트를 통해 파일 (충돌 데모 테스트 앱에서와 같이)은 다음과 같습니다.

//
// from:  http://stackoverflow.com/questions/5033012/auto-scale-textview-text-to-fit-within-bounds
//
//

package com.ajw.DemoCrashInADT;

import android.content.Context;
import android.graphics.Canvas;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

/**
 * 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.
 *
 * 2011-10-29 changes by Alan Jay Weiner
 *              * change to fit both vertically and horizontally
 *              * test mTextSize for 0 in resizeText() to fix exception in Layout Editor
 *
 * @author Chase Colburn
 * @since Apr 4, 2011
 */
public class AutoResizeTextView extends TextView {

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

    // Off screen canvas for text size rendering
    private static final Canvas sTextResizeCanvas = new Canvas();

    // 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(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() {
        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
        // or if mTextSize has not been initialized
        if (text == null || text.length() == 0 || height <= 0 || width <= 0
                || mTextSize == 0) {
            return;
        }

        // 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);
        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 > mMinTextSize) {
            targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
            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 (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
            // Draw using a static layout
            StaticLayout layout = new StaticLayout(text, textPaint, width,
                    Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
            layout.draw(sTextResizeCanvas);
            int lastLine = layout.getLineForVertical(height) - 1;
            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
        textPaint.setTextSize(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) {
        // Update the text paint object
        paint.setTextSize(textSize);
        // Draw using a static layout
        StaticLayout layout = new StaticLayout(source, paint, width,
                Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        layout.draw(sTextResizeCanvas);
        return layout.getHeight();
    }


    // Set the text size of the text paint object and use a static layout to
    // render text off screen before measuring
    private int getTextWidth(CharSequence source, TextPaint paint, int width,
            float textSize) {
        // Update the text paint object
        paint.setTextSize(textSize);
        // Draw using a static layout
        StaticLayout layout = new StaticLayout(source, paint, width,
                Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        layout.draw(sTextResizeCanvas);
        return layout.getWidth();
    }

}

초기 코드를 게시 해 주셔서 감사합니다. 나는 그것이 어떻게 작동하는지보기 위해 그것을 읽는 것을 즐겼으며, 그것을 추가 할 수있어서 기쁩니다.


답변

AppcompatTextView는 이제 Support Library 26.0부터 자동 크기 조정을 지원합니다. Android O의 TextView도 같은 방식으로 작동합니다. 자세한 내용은 여기를 참조하십시오. 간단한 데모 앱은 여기 에서 찾을 수 있습니다 .

<LinearLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      android:layout_width="match_parent"
      android:layout_height="wrap_content">

      <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:autoSizeTextType="uniform"
        app:autoSizeMinTextSize="12sp"
        app:autoSizeMaxTextSize="100sp"
        app:autoSizeStepGranularity="2sp"
      />

</LinearLayout>