IT story

Android 용 TextView 자동 맞춤

hot-time 2020. 4. 16. 08:29
반응형

Android 용 TextView 자동 맞춤


배경

여러 번 TextView의 글꼴을 주어진 경계에 자동 맞춤해야합니다.

문제

안타깝게도이 문제 (예 : here , herehere ) 에 대해 이야기하는 스레드 및 게시물 (및 제안 된 솔루션)이 많이 있지만 실제로는 잘 작동하지 않습니다.

그렇기 때문에 실제 거래를 찾을 때까지 각각을 테스트하기로 결정했습니다.

그러한 textView의 요구 사항은 다음과 같아야한다고 생각합니다.

  1. 글꼴, 서체, 스타일 및 문자 집합을 사용할 수 있어야합니다.

  2. 너비와 높이를 모두 처리해야합니다

  3. 제한으로 인해 텍스트를 맞출 수 없으면 잘리지 않습니다 (예 : 텍스트가 너무 길거나 사용 가능한 크기가 너무 작음). 그러나 원하는 경우 가로 / 세로 스크롤 막대를 요청할 수 있습니다.

  4. 여러 줄 또는 한 줄을 허용해야합니다. 여러 줄의 경우 최대 및 최소 줄을 허용하십시오.

  5. 계산 속도가 느려서는 안됩니다. 최상의 크기를 찾기 위해 루프를 사용하십니까? 최소한 최적화하고 매번 샘플링을 1 씩 늘리지 마십시오.

  6. 여러 줄의 경우 크기 조정 또는 더 많은 줄 사용을 선호하거나 "\ n"문자를 사용하여 줄을 직접 선택할 수 있어야합니다.

내가 시도한 것

나는 많은 샘플 (링크의 샘플을 포함하여)을 시도했으며, 사례를 처리하기 위해 샘플을 수정하려고 시도했지만 실제로 이야기하지는 않았습니다.

TextView가 자동으로 맞는지 시각적으로 확인할 수있는 샘플 프로젝트를 만들었습니다.

현재 샘플 프로젝트는 텍스트 (영어 알파벳과 숫자)와 textView의 크기 만 무작위로 지정하고 한 줄로 유지하도록 허용하지만 시도한 샘플에서는 제대로 작동하지 않습니다.

코드는 다음과 같습니다 ( 여기 에서도 사용 가능 ).

파일 res/layout/activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  android:layout_height="match_parent" tools:context=".MainActivity">
  <Button android:id="@+id/button1" android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true" android:text="Button" />
  <FrameLayout android:layout_width="match_parent"
    android:layout_height="wrap_content" android:layout_above="@+id/button1"
    android:layout_alignParentLeft="true" android:background="#ffff0000"
    android:layout_alignParentRight="true" android:id="@+id/container"
    android:layout_alignParentTop="true" />

</RelativeLayout>

파일 src/.../MainActivity.java

public class MainActivity extends Activity
  {
  private final Random        _random            =new Random();
  private static final String ALLOWED_CHARACTERS ="qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890";

  @Override
  protected void onCreate(final Bundle savedInstanceState)
    {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final ViewGroup container=(ViewGroup)findViewById(R.id.container);
    findViewById(R.id.button1).setOnClickListener(new OnClickListener()
      {
        @Override
        public void onClick(final View v)
          {
          container.removeAllViews();
          final int maxWidth=container.getWidth();
          final int maxHeight=container.getHeight();
          final FontFitTextView fontFitTextView=new FontFitTextView(MainActivity.this);
          final int width=_random.nextInt(maxWidth)+1;
          final int height=_random.nextInt(maxHeight)+1;
          fontFitTextView.setLayoutParams(new LayoutParams(width,height));
          fontFitTextView.setSingleLine();
          fontFitTextView.setBackgroundColor(0xff00ff00);
          final String text=getRandomText();
          fontFitTextView.setText(text);
          container.addView(fontFitTextView);
          Log.d("DEBUG","width:"+width+" height:"+height+" text:"+text);
          }
      });
    }

  private String getRandomText()
    {
    final int textLength=_random.nextInt(20)+1;
    final StringBuilder builder=new StringBuilder();
    for(int i=0;i<textLength;++i)
      builder.append(ALLOWED_CHARACTERS.charAt(_random.nextInt(ALLOWED_CHARACTERS.length())));
    return builder.toString();
    }
  }

질문

실제로 작동하는 일반적인 문제에 대한 해결책을 아는 사람이 있습니까?

심지어 내가 작성한 것보다 훨씬 적은 기능을 가진 솔루션조차, 예를 들어 일정한 수의 텍스트 줄을 가지고 크기에 따라 글꼴을 조정하지만 이상한 결함이 없으며 텍스트도 얻지 못하는 솔루션 사용 가능한 공간과 비교하여 크거나 작습니다.


GitHub 프로젝트

이것이 중요한 TextView이기 때문에 모든 사람들이 쉽게 라이브러리를 사용하고 여기에 기여할 수 있도록 라이브러리를 게시하기로 결정했습니다 .


MartinH의 간단한 수정 덕분에 이곳은 ,이 코드는 처리 소요 android:drawableLeft, android:drawableRight, android:drawableTopandroid:drawableBottom태그를.


여기에 내 대답은 당신이 행복해야한다 경계에 맞도록 자동 스케일 텍스트 뷰의 텍스트를

테스트 사례를 수정했습니다.

@Override
protected void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final ViewGroup container = (ViewGroup) findViewById(R.id.container);
    findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(final View v) {
            container.removeAllViews();
            final int maxWidth = container.getWidth();
            final int maxHeight = container.getHeight();
            final AutoResizeTextView fontFitTextView = new AutoResizeTextView(MainActivity.this);
            final int width = _random.nextInt(maxWidth) + 1;
            final int height = _random.nextInt(maxHeight) + 1;
            fontFitTextView.setLayoutParams(new FrameLayout.LayoutParams(
                    width, height));
            int maxLines = _random.nextInt(4) + 1;
            fontFitTextView.setMaxLines(maxLines);
            fontFitTextView.setTextSize(500);// max size
            fontFitTextView.enableSizeCache(false);
            fontFitTextView.setBackgroundColor(0xff00ff00);
            final String text = getRandomText();
            fontFitTextView.setText(text);
            container.addView(fontFitTextView);
            Log.d("DEBUG", "width:" + width + " height:" + height
                    + " text:" + text + " maxLines:" + maxLines);
        }
    });
}

안드로이드 개발자의 요청 에 따라 여기에 코드를 게시하고 있습니다 .

최종 효과 :

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

샘플 레이아웃 파일 :

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

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

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

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

    public int getMaxLines() {
        return mMaxLines;
    }

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

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

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

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

    @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;
        adjustTextSize();
    }

    private void adjustTextSize() {
        if (!mInitializedDimens) {
            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);
        }
        int key = getText().toString().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 the 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);
        adjustTextSize();
    }

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

경고:

Android 3.1 (Honeycomb) 의이 해결 된 버그주의하십시오 .


M-WaJeEh의 답변을 수정하여 측면의 복합 드로어 블을 고려했습니다.

getCompoundPaddingXXXX()방법은 반환 padding of the view + drawable space. 예를 들어 TextView.getCompoundPaddingLeft ()를 참조하십시오.

문제 : 텍스트에 사용 가능한 TextView 공간의 너비와 높이 측정이 수정됩니다. 드로어 블 크기를 고려하지 않으면 무시되고 텍스트가 드로어 블과 겹칩니다.


업데이트 된 세그먼트 adjustTextSize(String):

private void adjustTextSize(final String text) {
  if (!mInitialized) {
    return;
  }
  int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom() - getCompoundPaddingTop();
  mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();

  mAvailableSpaceRect.right = mWidthLimit;
  mAvailableSpaceRect.bottom = heightLimit;

  int maxTextSplits = text.split(" ").length;
  AutoResizeTextView.super.setMaxLines(Math.min(maxTextSplits, mMaxLines));

  super.setTextSize(
      TypedValue.COMPLEX_UNIT_PX,
      binarySearch((int) mMinTextSize, (int) mMaxTextSize,
                   mSizeTester, mAvailableSpaceRect));
}

Ok 지난주에 테스트 정확하게 맞게 코드를 크게 다시 작성했습니다 . 이제이 1 : 1을 복사하면 즉시 작동 setSingleLine()합니다. 조정 MIN_TEXT_SIZE하고 MAX_TEXT_SIZE극단적 인 가치를 추구하는 경우를 잊지 마십시오 .

수렴 알고리즘은 다음과 같습니다.

for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {

    // Go to the mean value...
    testSize = (upperTextSize + lowerTextSize) / 2;

    // ... inflate the dummy TextView by setting a scaled textSize and the text...
    mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
    mTestView.setText(text);

    // ... call measure to find the current values that the text WANTS to occupy
    mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
    int tempHeight = mTestView.getMeasuredHeight();

    // ... decide whether those values are appropriate.
    if (tempHeight >= targetFieldHeight) {
        upperTextSize = testSize; // Font is too big, decrease upperSize
    }
    else {
        lowerTextSize = testSize; // Font is too small, increase lowerSize
    }
}

그리고 전체 학급은 여기에서 찾을 수 있습니다.

결과는 매우 유연합니다. 이것은 xml에서 선언 된 것과 동일하게 작동합니다.

<com.example.myProject.AutoFitText
    android:id="@+id/textView"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="4"
    android:text="@string/LoremIpsum" />

... 테스트에서와 같이 프로그래밍 방식으로 구축되었습니다.

나는 당신이 이것을 지금 사용할 수 있기를 바랍니다. setText(CharSequence text)그런데 지금 전화 해서 사용할 수 있습니다 . 수업은 엄청나게 드문 예외를 처리하며 견고해야합니다. 알고리즘이 아직 지원 하지 않는 유일한 것은 :

  • setMaxLines(x)어디로 전화x >= 2

그러나 원하는 경우이를 작성할 수 있도록 광범위한 의견을 추가했습니다.


참고 :

이것을 한 줄로 제한하지 않고 정상적으로 사용하면 앞에서 언급 한 것처럼 단어가 깨질 수 있습니다. 이것은 안드로이드 기능 이며 오류가 아닙니다AutoFitText . Android는 항상 TextView에 비해 너무 긴 단어를 중단하며 실제로는 매우 편리합니다. 203 행에서 시작하는 내 의견과 코드를 보시오. .

결론 : 공백 문자도 지원하도록 테스트를 다시 작성하는 것이 좋습니다.

final Random _random = new Random();
final String ALLOWED_CHARACTERS = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890";
final int textLength = _random.nextInt(80) + 20;
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < textLength; ++i) {
    if (i % 7 == 0 && i != 0) {
        builder.append(" ");
    }
    builder.append(ALLOWED_CHARACTERS.charAt(_random.nextInt(ALLOWED_CHARACTERS.length())));
}
((AutoFitText) findViewById(R.id.textViewMessage)).setText(builder.toString());

이것은 매우 아름답고 (보다 현실적인) 결과를 낳을 것입니다.
이 문제를 시작하기위한 주석도 있습니다.

행운과 안부


내 요구 사항은

  • ScalableTextView를 클릭하십시오
  • listActivity를 열고 다양한 길이의 문자열 항목을 표시하십시오.
  • 목록에서 텍스트를 선택하십시오.
  • 다른 활동에서 텍스트를 ScalableTextView로 다시 설정하십시오.

나는 경계에 맞는 AutoView TextView Text (주석 포함) 링크와 DialogTitle.java 링크를 언급했다.

제공된 솔루션이 훌륭하고 간단하지만 텍스트 상자의 크기를 동적으로 변경하지는 않습니다. 때 좋은 작품 목록보기에서 선택한 텍스트의 길이가에서 기존 텍스트의 아이폰에보다 크기가 크다 ScalableTextView . 에서 기존 텍스트보다 길이가 작은 텍스트를 선택하면 텍스트 ScalableTextView크기가 늘어나지 않고 텍스트가 더 작은 크기로 표시됩니다.

텍스트 길이에 따라 텍스트 크기를 다시 조정하기 위해 ScalableTextView.java를 수정했습니다. 여기, 내 것이요ScalableTextView.java

public class ScalableTextView extends TextView
{
float defaultTextSize = 0.0f;

public ScalableTextView(Context context, AttributeSet attrs, int defStyle)
{
    super(context, attrs, defStyle);
    setSingleLine();
    setEllipsize(TruncateAt.END);
    defaultTextSize = getTextSize();
}

public ScalableTextView(Context context, AttributeSet attrs)
{
    super(context, attrs);
    setSingleLine();
    setEllipsize(TruncateAt.END);
    defaultTextSize = getTextSize();
}

public ScalableTextView(Context context)
{
    super(context);
    setSingleLine();
    setEllipsize(TruncateAt.END);
    defaultTextSize = getTextSize();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
    setTextSize(TypedValue.COMPLEX_UNIT_PX, defaultTextSize);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

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

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

                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                ellipsisCount = layout.getEllipsisCount(lineCount - 1);
            }
        }
    }
}
}

행복한 코딩 ....


이 속성이 더 낮은 Android 버전에서 단계별로 작동하는 방법을 설명합니다.

1- 프로젝트 gradle 파일에서 Android 지원 라이브러리 26.xx를 가져옵니다. IDE에 지원 라이브러리가 없으면 자동으로 다운로드됩니다.

dependencies {
    compile 'com.android.support:support-v4:26.1.0'
    compile 'com.android.support:appcompat-v7:26.1.0'
    compile 'com.android.support:support-v13:26.1.0' }

allprojects {
    repositories {
        jcenter()
        maven {
            url "https://maven.google.com"
        }
    } }

2- 레이아웃 XML 파일을 열고이 태그처럼 TextView를 리 팩터하십시오. 이 시나리오는 다음과 같습니다. 시스템에서 글꼴 크기가 너무 크면 텍스트 줄 바꿈이 아닌 사용 가능한 너비에 텍스트를 맞 춥니 다.

<android.support.v7.widget.AppCompatTextView
            android:id="@+id/textViewAutoSize"
            android:layout_width="match_parent"
            android:layout_height="25dp"
            android:ellipsize="none"
            android:text="Auto size text with compatible lower android versions."
            android:textSize="12sp"
            app:autoSizeMaxTextSize="14sp"
            app:autoSizeMinTextSize="4sp"
            app:autoSizeStepGranularity="0.5sp"
            app:autoSizeTextType="uniform" />

경고, Android 3 (Honeycomb) 및 Android 4.0 (Ice Cream Sandwich)의 버그

Android 버전 : 3.1-4.04에는 버그가 있으며 TextView 내부의 setTextSize ()는 처음으로 만 작동합니다 (처음 호출).

버그는 이슈 22493 : Android 4.0의 TextView 높이 버그이슈 17343 : 버튼의 높이 및 텍스트가 HoneyComb의 텍스트 크기를 늘리거나 줄인 후 원래 상태로 돌아 오지 않음에 설명되어 있습니다.

해결 방법은 크기를 변경하기 전에 TextView에 지정된 텍스트에 줄 바꿈 문자를 추가하는 것입니다.

final String DOUBLE_BYTE_SPACE = "\u3000";
textView.append(DOUBLE_BYTE_SPACE);

다음과 같이 코드에서 사용합니다.

final String DOUBLE_BYTE_SPACE = "\u3000";
AutoResizeTextView textView = (AutoResizeTextView) view.findViewById(R.id.aTextView);
String fixString = "";
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR1
   && android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {  
    fixString = DOUBLE_BYTE_SPACE;
}
textView.setText(fixString + "The text" + fixString);

텍스트의 왼쪽과 오른쪽에이 "\ u3000"문자를 추가하여 중앙에 유지합니다. 왼쪽에 정렬 한 경우 오른쪽에만 추가하십시오. 물론 AutoResizeTextView 위젯에 내장 될 수도 있지만 수정 코드를 외부에 유지하고 싶었습니다.


이 문제에 대한 공식적인 해결책이 있습니다. Android O에 도입 된 자동 크기 조정 TextView는 지원 라이브러리 26에서 사용할 수 있으며 Android 4.0까지 하위 호환됩니다.

https://developer.android.com/preview/features/autosizing-textview.html

이 정보가 포함 된 https://stackoverflow.com/a/42940171/47680 이 관리자에 의해 삭제 된 이유를 잘 모르겠습니다 .


2018 년 6 월부터 Android는 공식적으로 Android 4.0 (API 레벨 14) 이상 에서이 기능지원 합니다 .
Android 8.0 (API 레벨 26) 이상 :

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

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

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

자세한 답변을 확인하십시오 .


텍스트보기를 이미지로 변환하고 경계 내에서 이미지 크기를 조정합니다.

뷰를 이미지로 변환하는 방법에 대한 예는 다음과 같습니다 . 뷰를 Android에 표시하지 않고 비트 맵으로 변환?

문제는 텍스트를 선택할 수 없지만 트릭을 수행해야한다는 것입니다. 나는 그것을 시도하지 않았으므로 어떻게 보일지 잘 모르겠습니다 (확장 때문에).


다음은 사용자 정의 글꼴에 대한 기능이 추가 된 눈사태 TextView입니다.

용법:

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

                <de.meinprospekt.androidhd.view.AutoFitText
                android:layout_width="wrap_content"
                android:layout_height="10dp"
                android:text="Small Text"
                android:textColor="#FFFFFF"
                android:textSize="100sp"
                foo:customFont="fonts/Roboto-Light.ttf" />

</FrameLayout>

xmlns : foo = "http://schemas.android.com/apk/res-auto"를 추가하는 것을 잊지 마십시오. 글꼴은 자산 소방서에 있어야합니다

import java.util.ArrayList;
import java.util.List;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.TextView;
import de.meinprospekt.androidhd.R;
import de.meinprospekt.androidhd.adapter.BrochuresHorizontalAdapter;
import de.meinprospekt.androidhd.util.LOG;

/**
 * https://stackoverflow.com/a/16174468/2075875 This class builds a new android Widget named AutoFitText which can be used instead of a TextView to
 * have the text font size in it automatically fit to match the screen width. Credits go largely to Dunni, gjpc, gregm and speedplane from
 * Stackoverflow, method has been (style-) optimized and rewritten to match android coding standards and our MBC. This version upgrades the original
 * "AutoFitTextView" to now also be adaptable to height and to accept the different TextView types (Button, TextClock etc.)
 * 
 * @author pheuschk
 * @createDate: 18.04.2013
 * 
 * combined with: https://stackoverflow.com/a/7197867/2075875
 */
@SuppressWarnings("unused")
public class AutoFitText extends TextView {

    private static final String TAG = AutoFitText.class.getSimpleName();

    /** Global min and max for text size. Remember: values are in pixels! */
    private final int MIN_TEXT_SIZE = 10;
    private final int MAX_TEXT_SIZE = 400;

    /** Flag for singleLine */
    private boolean mSingleLine = false;

    /**
     * A dummy {@link TextView} to test the text size without actually showing anything to the user
     */
    private TextView mTestView;

    /**
     * A dummy {@link Paint} to test the text size without actually showing anything to the user
     */
    private Paint mTestPaint;

    /**
     * Scaling factor for fonts. It's a method of calculating independently (!) from the actual density of the screen that is used so users have the
     * same experience on different devices. We will use DisplayMetrics in the Constructor to get the value of the factor and then calculate SP from
     * pixel values
     */
    private float mScaledDensityFactor;

    /**
     * Defines how close we want to be to the factual size of the Text-field. Lower values mean higher precision but also exponentially higher
     * computing cost (more loop runs)
     */
    private final float mThreshold = 0.5f;

    /**
     * Constructor for call without attributes --> invoke constructor with AttributeSet null
     * 
     * @param context
     */
    public AutoFitText(Context context) {
        this(context, null);
    }

    public AutoFitText(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public AutoFitText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        //TextViewPlus part https://stackoverflow.com/a/7197867/2075875
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoFitText);
        String customFont = a.getString(R.styleable.AutoFitText_customFont);
        setCustomFont(context, customFont);
        a.recycle();

        // AutoFitText part
        mScaledDensityFactor = context.getResources().getDisplayMetrics().scaledDensity;
        mTestView = new TextView(context);

        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());

        this.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

            @Override
            public void onGlobalLayout() {
                // make an initial call to onSizeChanged to make sure that refitText is triggered
                onSizeChanged(AutoFitText.this.getWidth(), AutoFitText.this.getHeight(), 0, 0);
                // Remove the LayoutListener immediately so we don't run into an infinite loop
                //AutoFitText.this.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                removeOnGlobalLayoutListener(AutoFitText.this, this);
            }
        });
    }

    public boolean setCustomFont(Context ctx, String asset) {
        Typeface tf = null;
        try {
        tf = Typeface.createFromAsset(ctx.getAssets(), asset);  
        } catch (Exception e) {
            LOG.e(TAG, "Could not get typeface: "+e.getMessage());
            return false;
        }

        setTypeface(tf);  
        return true;
    }

    @SuppressLint("NewApi")
    public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener){
        if (Build.VERSION.SDK_INT < 16) {
            v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
        } else {
            v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
        }
    }

    /**
     * Main method of this widget. Resizes the font so the specified text fits in the text box assuming the text box has the specified width. This is
     * done via a dummy text view that is refit until it matches the real target width and height up to a certain threshold factor
     * 
     * @param targetFieldWidth The width that the TextView currently has and wants filled
     * @param targetFieldHeight The width that the TextView currently has and wants filled
     */
    private void refitText(String text, int targetFieldWidth, int targetFieldHeight) {

        // Variables need to be visible outside the loops for later use. Remember size is in pixels
        float lowerTextSize = MIN_TEXT_SIZE;
        float upperTextSize = MAX_TEXT_SIZE;

        // Force the text to wrap. In principle this is not necessary since the dummy TextView
        // already does this for us but in rare cases adding this line can prevent flickering
        this.setMaxWidth(targetFieldWidth);

        // Padding should not be an issue since we never define it programmatically in this app
        // but just to to be sure we cut it off here
        targetFieldWidth = targetFieldWidth - this.getPaddingLeft() - this.getPaddingRight();
        targetFieldHeight = targetFieldHeight - this.getPaddingTop() - this.getPaddingBottom();

        // Initialize the dummy with some params (that are largely ignored anyway, but this is
        // mandatory to not get a NullPointerException)
        mTestView.setLayoutParams(new LayoutParams(targetFieldWidth, targetFieldHeight));

        // maxWidth is crucial! Otherwise the text would never line wrap but blow up the width
        mTestView.setMaxWidth(targetFieldWidth);

        if (mSingleLine) {
            // the user requested a single line. This is very easy to do since we primarily need to
            // respect the width, don't have to break, don't have to measure...

            /*************************** Converging algorithm 1 ***********************************/
            for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {

                // Go to the mean value...
                testSize = (upperTextSize + lowerTextSize) / 2;

                mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
                mTestView.setText(text);
                mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

                if (mTestView.getMeasuredWidth() >= targetFieldWidth) {
                    upperTextSize = testSize; // Font is too big, decrease upperSize
                } else {
                    lowerTextSize = testSize; // Font is too small, increase lowerSize
                }
            }
            /**************************************************************************************/

            // In rare cases with very little letters and width > height we have vertical overlap!
            mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

            if (mTestView.getMeasuredHeight() > targetFieldHeight) {
                upperTextSize = lowerTextSize;
                lowerTextSize = MIN_TEXT_SIZE;

                /*************************** Converging algorithm 1.5 *****************************/
                for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {

                    // Go to the mean value...
                    testSize = (upperTextSize + lowerTextSize) / 2;

                    mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
                    mTestView.setText(text);
                    mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

                    if (mTestView.getMeasuredHeight() >= targetFieldHeight) {
                        upperTextSize = testSize; // Font is too big, decrease upperSize
                    } else {
                        lowerTextSize = testSize; // Font is too small, increase lowerSize
                    }
                }
                /**********************************************************************************/
            }
        } else {

            /*********************** Converging algorithm 2 ***************************************/
            // Upper and lower size converge over time. As soon as they're close enough the loop
            // stops
            // TODO probe the algorithm for cost (ATM possibly O(n^2)) and optimize if possible
            for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {

                // Go to the mean value...
                testSize = (upperTextSize + lowerTextSize) / 2;

                // ... inflate the dummy TextView by setting a scaled textSize and the text...
                mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
                mTestView.setText(text);

                // ... call measure to find the current values that the text WANTS to occupy
                mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
                int tempHeight = mTestView.getMeasuredHeight();
                // int tempWidth = mTestView.getMeasuredWidth();

                // LOG.debug("Measured: " + tempWidth + "x" + tempHeight);
                // LOG.debug("TextSize: " + testSize / mScaledDensityFactor);

                // ... decide whether those values are appropriate.
                if (tempHeight >= targetFieldHeight) {
                    upperTextSize = testSize; // Font is too big, decrease upperSize
                } else {
                    lowerTextSize = testSize; // Font is too small, increase lowerSize
                }
            }
            /**************************************************************************************/

            // It is possible that a single word is wider than the box. The Android system would
            // wrap this for us. But if you want to decide fo yourself where exactly to break or to
            // add a hyphen or something than you're going to want to implement something like this:
            mTestPaint.setTextSize(lowerTextSize);
            List<String> words = new ArrayList<String>();

            for (String s : text.split(" ")) {
                Log.i("tag", "Word: " + s);
                words.add(s);
            }
            for (String word : words) {
                if (mTestPaint.measureText(word) >= targetFieldWidth) {
                    List<String> pieces = new ArrayList<String>();
                    // pieces = breakWord(word, mTestPaint.measureText(word), targetFieldWidth);

                    // Add code to handle the pieces here...
                }
            }
        }

        /**
         * We are now at most the value of threshold away from the actual size. To rather undershoot than overshoot use the lower value. To match
         * different screens convert to SP first. See {@link http://developer.android.com/guide/topics/resources/more-resources.html#Dimension} for
         * more details
         */
        this.setTextSize(TypedValue.COMPLEX_UNIT_SP, lowerTextSize / mScaledDensityFactor);
        return;
    }

    /**
     * This method receives a call upon a change in text content of the TextView. Unfortunately it is also called - among others - upon text size
     * change which means that we MUST NEVER CALL {@link #refitText(String)} from this method! Doing so would result in an endless loop that would
     * ultimately result in a stack overflow and termination of the application
     * 
     * So for the time being this method does absolutely nothing. If you want to notify the view of a changed text call {@link #setText(CharSequence)}
     */
    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        // Super implementation is also intentionally empty so for now we do absolutely nothing here
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
    }

    @Override
    protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
        if (width != oldWidth && height != oldHeight) {
            refitText(this.getText().toString(), width, height);
        }
    }

    /**
     * This method is guaranteed to be called by {@link TextView#setText(CharSequence)} immediately. Therefore we can safely add our modifications
     * here and then have the parent class resume its work. So if text has changed you should always call {@link TextView#setText(CharSequence)} or
     * {@link TextView#setText(CharSequence, BufferType)} if you know whether the {@link BufferType} is normal, editable or spannable. Note: the
     * method will default to {@link BufferType#NORMAL} if you don't pass an argument.
     */
    @Override
    public void setText(CharSequence text, BufferType type) {

        int targetFieldWidth = this.getWidth();
        int targetFieldHeight = this.getHeight();

        if (targetFieldWidth <= 0 || targetFieldHeight <= 0 || text.equals("")) {
            // Log.v("tag", "Some values are empty, AutoFitText was not able to construct properly");
        } else {
            refitText(text.toString(), targetFieldWidth, targetFieldHeight);
        }
        super.setText(text, type);
    }

    /**
     * TODO add sensibility for {@link #setMaxLines(int)} invocations
     */
    @Override
    public void setMaxLines(int maxLines) {
        // TODO Implement support for this. This could be relatively easy. The idea would probably
        // be to manipulate the targetHeight in the refitText-method and then have the algorithm do
        // its job business as usual. Nonetheless, remember the height will have to be lowered
        // dynamically as the font size shrinks so it won't be a walk in the park still
        if (maxLines == 1) {
            this.setSingleLine(true);
        } else {
            throw new UnsupportedOperationException("MaxLines != 1 are not implemented in AutoFitText yet, use TextView instead");
        }
    }

    @Override
    public void setSingleLine(boolean singleLine) {
        // save the requested value in an instance variable to be able to decide later
        mSingleLine = singleLine;
        super.setSingleLine(singleLine);
    }
}

알려진 버그 : Android 4.03에서 작동하지 않음-글꼴이 보이지 않거나 매우 작습니다 (원본 아 발란 차도 작동하지 않음). 아래 버그가 해당 버그의 해결 방법입니다. https://stackoverflow.com/a/21851239/2075875


이 시도

TextWatcher changeText = new TextWatcher() {
     @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                tv3.setText(et.getText().toString());
                tv3.post(new Runnable() {           
                    @Override
                    public void run() {
                    while(tv3.getLineCount() >= 3){                     
                            tv3.setTextSize((tv3.getTextSize())-1);                     
                        }
                    }
                });
            }

            @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

            @Override public void afterTextChanged(Editable s) { }
        };

더 쉬운 것을 찾고 있다면 :

 public MyTextView extends TextView{

    public void resize(String text, float textViewWidth, float textViewHeight) {
       Paint p = new Paint();
       Rect bounds = new Rect();
       p.setTextSize(1);
       p.getTextBounds(text, 0, text.length(), bounds);
       float widthDifference = (textViewWidth)/bounds.width();
       float heightDifference = (textViewHeight);
       textSize = Math.min(widthDifference, heightDifference);
       setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}

@Malachiasz가 설명 한 문제에 대한 빠른 수정

자동 크기 조정 클래스에서 이에 대한 사용자 지정 지원을 추가하여 문제를 해결했습니다.

public void setTextCompat(final CharSequence text) {
    setTextCompat(text, BufferType.NORMAL);
}

public void setTextCompat(final CharSequence text, BufferType type) {
    // Quick fix for Android Honeycomb and Ice Cream Sandwich which sets the text only on the first call
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 &&
        Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
        super.setText(DOUBLE_BYTE_WORDJOINER + text + DOUBLE_BYTE_WORDJOINER, type);
    } else {
        super.setText(text, type);
    }
}

@Override
public CharSequence getText() {
    String originalText = super.getText().toString();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 &&
        Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
        // We try to remove the word joiners we added using compat method - if none found - this will do nothing.
        return originalText.replaceAll(DOUBLE_BYTE_WORDJOINER, "");
    } else {
        return originalText;
    }
}

yourView.setTextCompat(newTextValue)대신에 전화yourView.setText(newTextValue)


추가 시도 LayoutParamsMaxWidthMaxHeight받는 사람을 TextView. 오버플로가 아닌 레이아웃이 부모 컨테이너를 준수하도록 강제합니다.

textview.setLayoutParams(new LayoutParams(LinearLayout.MATCH_PARENT,LinearLayout.WRAP_CONTENT));

int GeneralApproxWidthOfContainer = 400;
int GeneralApproxHeightOfContainer = 600;
textview.setMaxWidth(400);
textview.setMaxHeight(600);` 

Android O부터 xml로 텍스트 크기를 자동으로 조정할 수 있습니다.

https://developer.android.com/preview/features/autosizing-textview.html

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

Android O를 사용하면 TextView의 특성과 경계에 따라 레이아웃을 채우도록 텍스트 크기를 자동으로 확장하거나 축소 할 수 있도록 TextView에 지시 할 수 있습니다. 이 설정을 사용하면 동적 컨텐츠가있는 다른 화면에서 텍스트 크기를보다 쉽게 ​​최적화 할 수 있습니다.

지원 라이브러리 26.0 베타는 Android O 이전의 Android 버전을 실행하는 장치에서 자동 크기 조정 TextView 기능을 완벽하게 지원합니다. 라이브러리는 Android 4.0 (API 레벨 14) 이상을 지원합니다. android.support.v4.widget 패키지에는 이전 버전과 호환되는 방식으로 기능에 액세스하기위한 TextViewCompat 클래스가 포함되어 있습니다.


Android 공식 Autosizing TextView를 시도한 후 Android 버전이 Android 8.0 (API 레벨 26) 이전 android.support.v7.widget.AppCompatTextView인지 확인해야합니다. 지원 라이브러리 버전이 26.0.0 이상인지 확인하십시오. 예:

<android.support.v7.widget.AppCompatTextView
    android:layout_width="130dp"
    android:layout_height="32dp"
    android:maxLines="1"
    app:autoSizeMaxTextSize="22sp"
    app:autoSizeMinTextSize="12sp"
    app:autoSizeStepGranularity="2sp"
    app:autoSizeTextType="uniform" />

업데이트 :

@ android-developer의 답변에 따르면 AppCompatActivity소스 코드를 확인 하고이 두 줄을 발견했습니다.onCreate

final AppCompatDelegate delegate = getDelegate(); delegate.installViewFactory();

과에서 AppCompatDelegateImplcreateView

    if (mAppCompatViewInflater == null) {
        mAppCompatViewInflater = new AppCompatViewInflater();
    }

그것은 AppCompatViewInflaterinflater view를 사용하고 , AppCompatViewInflatercreateView 일 때 "TextView"에 AppCompatTextView를 사용합니다.

public final View createView(){
    ...
    View view = null;
    switch (name) {
        case "TextView":
            view = new AppCompatTextView(context, attrs);
            break;
        case "ImageView":
            view = new AppCompatImageView(context, attrs);
            break;
        case "Button":
            view = new AppCompatButton(context, attrs);
            break;
    ...
}

내 프로젝트에서는을 사용하지 않으므로 xml에서 AppCompatActivity사용해야 <android.support.v7.widget.AppCompatTextView>합니다.

참고 URL : https://stackoverflow.com/questions/16017165/auto-fit-textview-for-android

반응형