Автоматическая подгонка TextView для Android

231

Задний план

Много раз нам нужно автоматически подгонять шрифт TextView к заданным границам.

Эта проблема

К сожалению, даже несмотря на то, что существует множество потоков и сообщений (и предлагаемых решений), рассказывающих об этой проблеме (пример здесь , здесь и здесь ), ни один из них на самом деле не работает хорошо.

Вот почему я решил проверить каждый из них, пока не найду реальную сделку.

Я думаю, что требования от такого textView должны быть:

  1. Должно позволять использовать любой шрифт, шрифт, стиль и набор символов.

  2. Должен обрабатывать ширину и высоту

  3. Нет усечения, если только текст не может уместиться из-за ограничений, которые мы ему дали (пример: слишком длинный текст, слишком маленький доступный размер). Тем не менее, мы могли бы запросить горизонтальную / вертикальную полосу прокрутки, если мы хотим, только для этих случаев.

  4. Следует разрешить многострочное или однострочное. В случае многострочных, разрешите линии max и min.

  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, я решил опубликовать библиотеку, чтобы каждый мог легко использовать ее и внести свой вклад в нее здесь .

разработчик Android
источник
Вы пробовали это? androidviews.net/2012/12/autoscale-textview
Тракбад
@ Thrakbad, это одна из ссылок, которые я упомянул. Это также не проходит тест.
Android-разработчик
Ах, извините, я как-то пропустил последний пример
Тракбад
Да, пожалуйста, поверьте мне, я пробовал много примеров, и я также пытался модифицировать их, чтобы исправить обнаруженные проблемы, но никогда не получалось. Если вы найдете что-то, что, по вашему мнению, может сработать, пожалуйста, проверьте это. Я отправил пример кода только для этого.
Android-разработчик
1
@rule, это одна из постов, которые я уже прочитал, и я проверил все примеры кода. Также я думаю, что вы дважды опубликовали.
Android-разработчик

Ответы:

148

Благодаря простому затруднительного MartinH в здесь , этот код также заботится android:drawableLeft, android:drawableRight, android:drawableTopи android:drawableBottomтеги.


Мой ответ здесь должен вас порадовать. Автоматическое масштабирование текста TextView, чтобы поместиться в пределах границ

Я изменил ваш тестовый пример:

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

Я размещаю код здесь по запросу разработчика Android :

Конечный эффект:

Введите описание изображения здесь

Пример файла макета:

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

И код Java:

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
источник
хорошо, я проверил код, и кажется, что все в порядке. однако вы использовали getMaxLines (), который предназначен для API 16, но вы можете сохранить maxLines и получить его, переопределив setMaxLines и сохранив его значение. Я изменил его, и теперь он работает нормально. Кроме того, так как иногда текст может быть даже слишком длинным для наименьшего размера, я пытался использовать setEllipsize, и это тоже сработало! Я думаю, что у нас есть победитель. пожалуйста, оставьте свой код здесь, чтобы я мог пометить его как правильный.
Android-разработчик
Вы не удалили getMaxLines () и использовали свой собственный. он все еще будет работать только с API16 ... пожалуйста, измените его, чтобы каждый мог его использовать. Я предлагаю переопределить setMaxLines, чтобы сохранить параметр в поле, а затем получить доступ к полю вместо использования getMaxLines.
Android-разработчик
проверьте сейчас, добавлена ​​поддержка макс. строк.
M-WaJeEh
кажется в порядке. Вы должны добавить «@TargetApi (Build.VERSION_CODES.JELLY_BEAN)» в метод onTestSize (), чтобы Lint не выдавал ошибку / предупреждение об этом, и вместо этого инициализировать его в CTOR. установка статического двоичного поиска также полезна, и вы можете выполнить инициализацию на самом большом CTOR и вызывать его из других CTOR. однако, это действительно лучший ответ, и вы заслуживаете V. это, наконец, хороший ответ на этот старый вопрос. хорошая работа!
Android-разработчик
@ M-WaJeEh проверить новый пост в этой теме. пользователь с именем "MartinH" говорит, что у него есть исправление для вашего кода.
Android-разработчик
14

Я немного изменил ответ 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));
}
MartinH
источник
Вы обновили источник M-WaJeEh. почему ты поставил это как новый ответ? я знаю, что у тебя есть добрая воля, но хорошо ли это говорить как ответ? Я не уверен, что он может видеть, заметить ваш ответ ...
Android-разработчик
1
Я только что подписался, чтобы иметь возможность предоставить это обновление. Моя репутация только 1, что мешает мне комментировать посты не мои (минимум 15).
MartinH
Не беспокойтесь, возможно, вы хотели бы передать мой ответ M-WajeEh, так как я даже не могу связаться с ним в данный момент: /
MartinH
Можете ли вы попытаться описать лучше, что вы сделали? вы исправили ошибку? Можете ли вы показать скриншот, чтобы продемонстрировать ошибку?
Android-разработчик
1
Привет @ MartinH, добро пожаловать в ТАК. Я постараюсь разобраться в этом и обновлю свой ответ, упомянув ваше имя и разместив ссылку на ваш пост после тестирования . Просто дай мне несколько дней, пожалуйста. Я застрял где-то еще в наши дни.
M-WaJeEh
7

Хорошо, я использовал последнюю неделю, чтобы массово переписать свой код, чтобы он точно соответствовал вашему тесту. Теперь вы можете скопировать это 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

Но я добавил обширные комментарии, чтобы помочь вам построить это, если хотите!


Пожалуйста, обратите внимание:

Если вы просто используете это как обычно, не ограничивая его одной строкой, то может произойти разрыв слов, как вы уже упоминали ранее. Это функция Android , а не вина 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());

Это даст очень красивые (и более реалистичные) результаты.
Вы найдете комментарии, чтобы вы начали в этом вопросе.

Удачи и наилучших пожеланий

Патрик
источник
Есть некоторые проблемы, но в целом все работает так хорошо. проблемы: не поддерживает многострочность (как вы написали) и будет ломать слово в случае, если вы попытаетесь это сделать, включает функцию API16 (addOnGlobalLayoutListener), которую, я думаю, можно полностью избежать (или, по крайней мере, используйте вместо нее OnPreDrawListener), использует бинарный поиск, поэтому он может проверять размеры, которые ему вообще не подходят (и использовать больше памяти без причины, что может вызвать исключения, если он слишком большой), не поддерживает обработку, когда что-то меняется (например, сам текст).
Android-разработчик
Кстати, вам не нужно использовать переменную mTestView и проводить тестирование непосредственно на текущем представлении, и таким образом вам не придется учитывать все особые вещи. Кроме того, я думаю, что вместо MAX_TEXT_SIZE вы можете использовать targetFieldHeight.
Android-разработчик
Я думаю, что вместо чистого бинарного поиска (особенно 2 из них), вы можете получить более точное предположение по отношению цели к измеренному размеру. Есть и другие методы (Ньютон и кое-что еще, что я не помню), но я не думаю, что они здесь подходят. тот, который я предложил сейчас, лучше, так как вы можете начать с минимального размера вместо тестирования максимального размера. Фактически вам не нужен максимальный размер, так как вы продолжаете делать более правильное предположение.
Android-разработчик
я также думаю, что 2 бинарных поиска могут быть объединены в одну строку, когда условие может быть просто: if (getMeasuredWidth ()> = targetFieldWidth || getMeasuredHeight ()> = targetFieldHeight)
разработчик Android
1
я обнаружил еще одну проблему - гравитацию текста. я думаю, что это не может быть отцентрировано вертикально.
Android-разработчик
4

Мое требование заключается в

  • Нажмите на ScalableTextView
  • Откройте listActivity и отобразите строковые элементы различной длины.
  • Выберите текст из списка.
  • Установите текст обратно в ScalableTextView в другой деятельности.

Я сослался на ссылку: Auto Scale 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);
            }
        }
    }
}
}

Удачного кодирования ....

Девендра Вая
источник
Разве вы не должны делать это с помощью бинарного поиска вместо сокращения на единицу для каждой итерации? Кроме того, кажется, что этот код заставляет TextView быть с одной строкой текста, вместо того, чтобы иметь несколько строк.
Android-разработчик
Это было единственное решение, которое сработало для меня. Моя проблема была связана с тем, что TExtView не соответствовал представлению только в 4.1
FOliveira
кажется, что это не соответствует родителю на 100%. см. скриншот: s14.postimg.org/93c2xgs75/Screenshot_2.png
user25
4

Я объясню, как работает этот атрибут более низких версий Android, шаг за шагом:

1- Импорт библиотеки поддержки 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" />
Синан Эргин
источник
Их реализация Auto-Fit TextView не работает, хотя. Иногда вместо изменения размера шрифта текст просто переносится на следующую строку, что плохо ... Это даже показывается на их видео, как будто это хорошо: youtu.be/fjUdJ2aVqE4?t=14 . Обратите внимание, как «w» в «TextView» переходит к следующей строке ... В любом случае, здесь создан новый запрос об этом: issetracker.google.com/issues/68787108 . Пожалуйста, подумайте об этом.
разработчик Android
@androiddeveloper Вы правы. Я не объяснил, что реализация тега работает в одну строку. Если вам нужны переносы строк, вы должны изменить этот атрибут: android: layout_height = "wrap_content". Но android.support.v7.widget.AppCompatTextView и реализация Gradle гарантируют, что этот атрибут работает.
Синан Эргин
Я не думаю, что вы понимаете. Я не хочу, чтобы парные слова переносились на следующую строку, если нет другого способа решить это. В видео вы можете легко увидеть, что шрифты могли просто уменьшиться. Это проблема. Вы говорите, что знаете, как это исправить? То есть не будет переносов слов, а только изменения размера шрифта?
разработчик Android
@androiddeveloper Есть много сценариев. Мой сценарий таков: текст с автоподборкой исправляется, а не перенос слов. При увеличении размера системного шрифта текст должен отображать не перенос слов в текстовом представлении. Если перенос слов не имеет значения для вас, не стоит устанавливать фиксированную высоту. Это свойство не использует только перенос слов, вы можете автоподбор текста с фиксированной шириной TextView.
Синан Эргин
Согласно моим тестам, размеры шрифтов изменяются неправильно, и может возникнуть проблема переноса слов.
разработчик Android
3

Предупреждение, ошибка в Android 3 (Honeycomb) и Android 4.0 (Ice Cream Sandwich)

Версии Android: 3.1-4.04 имеют ошибку, из-за которой setTextSize () внутри TextView работает только в первый раз (первый вызов).

Ошибка описана в выпуске 22493: ошибка высоты TextView в Android 4.0 и выпуск 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, но я хотел сохранить код исправления снаружи.

Malachiasz
источник
Можете ли вы показать, какие именно строки (до / после каких строк) и в какой функции?
Android-разработчик
хорошо, спасибо. Я надеюсь, что это все исправит, и тот, кто прочитает это, найдет это полезным. в настоящее время я не могу проверить это. возможно в следующий раз, когда я буду использовать это.
Android-разработчик
Этот код вызывается извне кода представления. Знаете ли вы, возможно, хороший альтернативный способ обработки этого внутри кода представления вместо этого?
разработчик Android
Вы можете переопределить textView.setText () и поместить этот код в класс TextView
Малахия
Разве это не означает, что getText () вернет текст с дополнительными символами?
разработчик Android
3

Теперь есть официальное решение этой проблемы. Авторазмер TextViews, представленный в Android O, доступен в библиотеке поддержки 26 и обратно совместим вплоть до Android 4.0.

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

Я не уверен, почему https://stackoverflow.com/a/42940171/47680, который также включал эту информацию, был удален администратором.

Артем Руссаковский
источник
1
Да, я тоже слышал об этом, но насколько это хорошо? Есть ли образец его использования? Даже в их видео я заметил ошибку: письмо из одного слова попало в строку после (обернуто): youtu.be/1N9KveJ-FU8?t=781 (обратите внимание на "W")
разработчик Android
3

С июня 2018 года Android официально поддерживает эту функцию для Android 4.0 (уровень API 14) и выше.
С Android 8.0 (уровень API 26) и выше:

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

Версии Android до Android 8.0 (уровень API 26):

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

Проверьте мой подробный ответ .

Подумайте дважды код один раз
источник
1

Преобразуйте текстовое представление в изображение, и масштабируйте изображение в пределах границ.

Вот пример того, как преобразовать представление в изображение: Преобразование представления в растровое изображение без его отображения в Android?

Проблема в том, что ваш текст не будет выделяться, но это должно сработать. Я не пробовал, поэтому не уверен, как это будет выглядеть (из-за масштабирования).

Доктор NotSoKind
источник
Это хорошая идея, но это также сделает текст пикселированным и удалит любую функцию, которую я могу использовать с textView.
Android-разработчик
0

Ниже приведено текстовое представление avalancha с добавленной функциональностью для пользовательских шрифтов.

Использование:

<?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 - шрифты невидимы или очень маленькие (оригинальная avalancha тоже не работает). Ниже приведено решение этой ошибки: https://stackoverflow.com/a/21851239/2075875

Malachiasz
источник
0

Попробуй это

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) { }
        };
Забыл имя
источник
Я не думаю, что это будет работать, так как каждый раз, когда вы изменяете размер текста, он будет вызывать рисование только после того, как вы закончите с блоком (то есть он будет добавлен в очередь событий). Это означает, что цикл будет работать только один раз при каждом изменении текста, и это не гарантирует, что количество строк не будет превышать 3 строк текста.
Android-разработчик
0

Если вы ищете что-то проще:

 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);
}
шлифовальный
источник
Это кажется очень коротким, но может ли он пройти тест, который я подготовил? Кроме того, я не понимаю, когда эта функция вызывается.
Android-разработчик
Нет, это скорее предложение. Я улучшу его, чтобы он мог пройти ваш тест как можно скорее.
Сандер
Ну, если это предложение, не могли бы вы опубликовать его прямо в Github?
Android-разработчик
0

Быстрое решение проблемы, описанной @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)

Ионут Негру
источник
Зачем создавать новую функцию setTextCompat вместо переопределения setText? Кроме того, какой вопрос?
разработчик Android
setText () - последний метод TextView, поэтому вы не сможете его переопределить. Другим вариантом будет сделать это за пределами пользовательского TextView, но это решение для использования его внутри TextView.
Ионут Негру
Не совсем. Некоторые из «setText» являются частными, некоторые являются открытыми, а некоторые из них не являются окончательными. Кажется, что большинство может быть обработано путем переопределения открытого void setText (текст CharSequence, тип BufferType). Есть последняя функция, которую я не знаю, хотя ее назначение: public final void setText (char [] text, int start, int len). Может быть, есть более глубокий взгляд на код.
Android-разработчик
Все эти варианты setText () на самом деле вызовут метод setText (CharSequence text) в конце. Если вы хотите обеспечить такое же поведение, вам необходимо переопределить этот метод, в противном случае было бы намного лучше просто добавить свой собственный метод setText ().
Ионут Негру
Да, но, может быть, в большинстве случаев это нормально.
Android-разработчик
0

Попробуйте добавить LayoutParamsи MaxWidthи MaxHeightк TextView. Это заставит компоновку соблюдать родительский контейнер, а не переполнять.

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

int GeneralApproxWidthOfContainer = 400;
int GeneralApproxHeightOfContainer = 600;
textview.setMaxWidth(400);
textview.setMaxHeight(600);` 
user3891647
источник
Я не уверен, о чем ты говоришь. Вы предлагаете добавить это в пример textView, чтобы у него не было небольшой проблемы, которую я иногда вижу? если так, то почему вы не опубликовали об этом на сайте github?
Android-разработчик
0

Начиная с 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 полностью поддерживает функцию автоматического изменения размера TextView на устройствах с версиями Android до Android O. Библиотека поддерживает Android 4.0 (уровень API 14) и выше. Пакет android.support.v4.widget содержит класс TextViewCompat для доступа к функциям обратно совместимым образом.

Javatar
источник
К сожалению, согласно моим тестам, и даже в видео Google IO, я заметил, что у него есть проблемы, такие как неправильная обертка слов, вместо изменения размера шрифта. Я сообщил об этом здесь: issetracker.google.com/issues/38468964 , и именно поэтому я до сих пор не использую его.
Android-разработчик
0

После того, как я попробовал официальный Autosizing TextView для Android , я обнаружил, что ваша версия 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();

и в AppCompatDelegateImplхcreateView

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

это использует AppCompatViewInflaterинфляционное представление, когда AppCompatViewInflatercreateView это будет использовать AppCompatTextView для "TextView".

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

В моем проекте я не использую AppCompatActivity, поэтому мне нужно использовать <android.support.v7.widget.AppCompatTextView>в XML.

weei.zh
источник
1
В большинстве случаев инфлятор использует AppCompatTextView, когда вы пишете только «TextView», поэтому вам не нужно это делать.
разработчик Android
@androiddeveloper Но когда я использую TextView, авторазмер не работает.
weei.zh
@androiddeveloper Спасибо, причина в том, что я не пользуюсь AppCompatActivity. AppCompatActivity использует макет инфлятора AppCompatViewInflater.
weei.zh
Я не понимаю
разработчик Android