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


231

Фон

Багато разів нам потрібно автоматично підлаштовувати шрифт TextView до заданих йому меж.

Проблема

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

Тому я вирішив протестувати кожен з них, поки не знайду справжню угоду.

Я думаю, що вимоги до такого 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, я вирішив опублікувати бібліотеку, щоб кожен міг легко використовувати його, і внести свій вклад в це, тут .



@Thrakbad - це одне з посилань, про які я згадував. Він також не проходить тест.
андроїд розробник

Ах вибачте, я якось пропустив останній приклад
Тракбад

Так, повірте, будь ласка, я спробував багато зразків, а також спробував змінити їх, щоб виправити проблеми, які я знайшов, але так і не досяг успіху. Якщо ви знайдете щось, що, на вашу думку, може спрацювати, будь ласка, протестуйте його. Я опублікував зразок коду саме для цього.
андроїд розробник

1
@rule Це одна з публікацій, яку я вже читав, і я перевірив усі зразки коду. також я думаю, що ви двічі розмістили повідомлення.
андроїд розробник

Відповіді:


147

Завдяки простому скрутного 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).


ОК, я перевірив код, і він здається прекрасним. проте ви використовували getMaxLines (), який призначений для API 16, але ви можете зберігати maxLines і отримувати його, переосмислюючи setMaxLines, і зберігати його значення. Я змінив його і тепер він працює добре. Крім того, оскільки іноді текст може бути занадто довгим для найменшого розміру, я намагався використовувати setEllipsize, і це теж працювало! я думаю, у нас є переможець. будь ласка, опублікуйте свій код тут, щоб я міг позначити його як правильний.
андроїд розробник

ви не видалили getMaxLines () і використали свій власний. він все ще працюватиме лише від API16 ... будь ласка, змініть його, щоб усі могли ним користуватися. Я пропоную замінити setMaxLines, щоб зберегти параметр у полі, а потім отримати доступ до поля замість використання getMaxLines.
андроїд розробник

перевірити зараз, додана підтримка максимуму рядків.
M-WaJeEh

здається прекрасним. ви повинні додати "@TargetApi (Build.VERSION_CODES.JELLY_BEAN)" до методу onTestSize (), щоб Lint не видав помилку / попередження про це, а ініціалізував би його в CTOR. встановлення бінарного пошуку на статичний - це також непогана річ, і ви можете зробити ініціалізацію на найбільшому CTOR і викликати її з інших CTOR. однак це справді найкраща відповідь, і ти заслуговуєш на V. це нарешті гарна відповідь на це старе питання. хороша робота!
андроїд розробник

@ M-WaJeEh перевірити нову публікацію в цій темі. користувач під назвою "MartinH" каже, що у нього є виправлення вашого коду.
андроїд розробник

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

ви оновили джерело M-WaJeEh чому ви поставили це як нову відповідь? я знаю, що ти маєш добру волю, але чи добре це ставити як відповідь? Я не впевнений, що він може помітити вашу відповідь ...
андроїд розробник

1
Я щойно зареєструвався, щоб надати це оновлення. Моя репутація становить лише 1, що заважає мені коментувати пости, які не є моїми (мінімум 15).
MartinH

Не хвилюйтесь, можливо, ви хочете передати мою відповідь M-WajeEh, оскільки я зараз навіть не можу зв’язатися з ним: /
MartinH

Чи можете ви, будь ласка, спробувати краще описати, що ви робили? ви виправили помилку? ви можете показати скріншот, щоб продемонструвати помилку?
андроїд розробник

1
Привіт @MartinH, Ласкаво просимо до SO. Я подивлюся на це і попрацюю свою відповідь, зазначивши ваше ім’я та опублікувавши посилання на ваш пост після тестування . Просто дайте мені, будь ласка, кілька днів. Я сьогодні застряг десь в іншому місці.
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), використовує бінарний пошук, щоб він міг перевірити розміри, які зовсім не підходять (і використовувати більше пам’яті без будь-якої причини, що може спричинити винятки, якщо він занадто великий), не підтримує обробку, коли щось змінюється (як і сам текст).
андроїд розробник

btw, вам не потрібно використовувати змінну mTestView і робити тестування безпосередньо на поточному представленні, і таким чином вам не доведеться враховувати всі особливості. Крім того, я думаю, що замість MAX_TEXT_SIZE ви можете використовувати targetFieldHeight.
андроїд розробник

Я думаю, що замість чистого бінарного пошуку (особливо 2 з них), ви можете отримати кращу здогадку за співвідношенням цілі та виміряного розміру. Є також інші методи (ньютон та щось інше, що я не пам’ятаю), але я не думаю, що вони тут підходять. те, що я запропонував зараз, краще, оскільки ви можете починати з мінімального розміру, а не тестувати максимальний розмір. Факт полягає в тому, що вам не знадобиться максимальний розмір, оскільки ви продовжуєте робити кращі здогадки.
андроїд розробник

Я також думаю, що два двійкові пошукові запити можна об'єднати в один рядок, коли умова може бути просто: якщо (getMeasuredWidth ()> = targetFieldWidth || getMeasuredHeight ()> = targetFieldHeight)
андроїд розробник

1
Ще одна проблема, яку я знайшов - це гравітація тексту. я думаю, що це не може бути зосереджено вертикально.
андроїд розробник

4

Моя вимога - це

  • Клацніть на ScalableTextView
  • Відкрийте списокАктивність та відображіть рядкові рядки з різною довжиною.
  • Виберіть текст зі списку.
  • Поверніть текст назад до ScalableTextView в іншій діяльності.

Я пересилав посилання: Текст автоматичного масштабування TextView, щоб він підходив до меж (включаючи коментарі), а також 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 бути з одним рядком тексту, а не дозволяти йому мати кілька рядків.
андроїд розробник

Це було єдине рішення, яке працювало на мене. Моя проблема була пов'язана з тим, що TExtView не відповідає перегляду лише в 4.1
FOliveira

здається, що це не відповідає батьківському на 100%. дивіться скріншот: s14.postimg.org/93c2xgs75/Screenshot_2.png
користувач25

4

Я поясню, як працює цей атрибут нижчих версій для Android крок за кроком:

1- Імпортуйте бібліотеку підтримки Android 26.xx у файл Gradle проекту. Якщо в 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" />

Їх реалізація Text-View автоматично підходить, але не працює добре. Бувають випадки, що замість зміни розміру шрифту текст просто переходить на наступний рядок, погано ... Це навіть відображається на їхніх відео, як ніби це добре: youtu.be/fjUdJ2aVqE4?t=14 . Зверніть увагу, як "w" "TextView" переходить до наступного рядка ... У будь-якому випадку, тут створено новий запит: issueetracker.google.com/isissue/68787108 . Будь ласка, подумайте про те, щоб зняти його.
андроїд розробник

@androiddeveloper Ви маєте рацію. Я не пояснив, що ця тега працює одним рядком. Якщо вам потрібні лінії обертання, вам потрібно змінити цей атрибут: android: layout_height = "wrap_content". Але гарантія реалізації android.support.v7.widget.AppCompatTextView та gradle працює на цей атрибут.
Сінан Ергін

Я не думаю, що ти розумієш. Я не хочу, щоб часткові слова переходили до наступного рядка, якщо немає іншого способу вирішити це. На відео ви можете легко побачити шрифти, які могли бути лише меншими. В цьому і полягає проблема. Ви кажете, що знаєте, як це виправити? Значить не буде обгортання слів, а змінюється лише розмір шрифту?
андроїд розробник

@androiddeveloper Є багато сценаріїв. Мій сценарій: автоматичне налаштування тексту на фіксовану, а не обгортання слів. Під час збільшення розміру шрифту системи текст повинен відображати не перенесення слів у текстовому перегляді. Якщо загортання слів для вас не має значення, не турбуйте встановлену висоту. Ця властивість не використовує лише обгортання слів, ви можете автофіксувати текст із фіксованою шириною TextView.
Сінан Ергін

Згідно з моїми тестами, розміри шрифту не змінюються правильно, і може виникнути проблема загортання слів.
андроїд розробник

3

Попередження, помилка в Android 3 (сота) та Android 4.0 (сендвіч з морозивом)

Версії 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, але я хотів зберегти код виправлення зовні.


чи можете ви вкажіть, будь ласка, у яких рядках (до / після яких рядків) та в якій функції?
андроїд розробник

добре, дякую. Я сподіваюся, що це все виправить, і той, хто прочитає це, знайде це корисним. В даний час я не можу це перевірити. можливо, наступного разу, коли я буду використовувати його.
андроїд розробник

Цей код викликається поза кодом перегляду. Чи знаєте ви, можливо, хороший альтернативний спосіб вирішити це всередині коду перегляду?
андроїд розробник

ви можете перекрити textView.setText () і ввести цей код всередині класу TextView
Малахіаш

Чи не означає це також, що "getText ()" поверне текст із зайвими символами?
андроїд розробник

3

Зараз існує офіційне рішення цієї проблеми. Автоматизація TextView, представлена ​​в 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")
андроїд розробник

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?

Проблема полягає в тому, що ваш текст не буде обраний, але він повинен зробити трюк. Я не пробував цього, тому не знаю, як це виглядатиме (через масштабування).


Це гарна ідея, але це також зробить текст піксельним і видалить будь-яку функцію, яку я можу використовувати з textView.
андроїд розробник

0

Нижче - лавина 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


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 рядки тексту.
андроїд розробник

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

Це здається дуже коротким, але чи можна пройти тест, який я підготував? Крім того, я не отримую, коли ця функція викликається.
андроїд розробник

Ні, це ще більше пропозиція. Я вдосконалю його, щоб він міг пройти тест якнайшвидше.
Сандер

Ну, якщо це пропозиція, чи можете ви опублікувати її прямо в Github?
андроїд розробник

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 developer

setText () - це остаточний метод TextView, тому ви не зможете його замінити. Іншим варіантом було б це зробити за межами користувацького TextView, але це рішення полягає у використанні його всередині TextView.
Іонут Негру

Не зовсім. Деякі з "setText" є приватними, деякі - загальнодоступними, а деякі - не остаточними. Здається, більшість з них може бути оброблена за допомогою перекриття загальнодоступних недійсних setText (текст CharSequence, тип BufferType). Є остаточна функція, однак я не знаю її призначення: public final final void setText (char [] текст, int start, int len). Можливо, глибше подивіться на код.
андроїд розробник

Усі ці варіанти setText () насправді викликають метод setText (текст CharSequence) врешті-решт. Якщо ви хочете забезпечити однакову поведінку, вам потрібно буде замінити цей метод, інакше було б набагато краще просто додати свій власний метод setText ().
Іонут Негру

Так, але, можливо, для більшості випадків це нормально.
андроїд розробник

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);` 

Я не впевнений, про що ти говориш. Чи пропонуєте ви додати це до зразка textView, щоб у нього не було невеликого питання, яке я іноді бачу? якщо так, то чому ви не опублікували про це на веб-сайті github?
андроїд розробник

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


На жаль, згідно з моїми тестами, і навіть у відео Google IO, я помітив, що замість зміни розміру шрифту є такі проблеми, як неправильне загортання частин слів. Про це я повідомляв тут: issueetracker.google.com/isissue/38468964 , і тому я досі не використовую його.
андроїд розробник

0

Після того як я спробував офіційний 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();

і в 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.


1
У більшості випадків надувальник використовує AppCompatTextView, коли ви пишете лише "TextView", тому вам не доведеться цього робити.
андроїд розробник

@androiddeveloper Але коли я використовую TextView, автоматична робота не працює.
weei.ж

@androiddeveloper Спасибі, причина я не використовую AppCompatActivity. Використовуйте AppCompatActivity надувний макет AppCompatViewInflater.
weei.ж

Я не розумію
андроїд розробник
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.