Automatyczne dopasowanie TextView na Androida

231

tło

Wiele razy musimy automatycznie dopasować czcionkę TextView do podanych granic.

Problem

Niestety, chociaż istnieje wiele wątków i postów (i sugerowanych rozwiązań) mówiących o tym problemie (przykład tutaj , tutaj i tutaj ), żaden z nich tak naprawdę nie działa dobrze.

Dlatego postanowiłem przetestować każdy z nich, dopóki nie znajdę prawdziwej oferty.

Myślę, że wymagania takiego textView powinny być:

  1. Powinny zezwalać na użycie dowolnej czcionki, kroju pisma, stylu i zestawu znaków.

  2. Powinien obsługiwać zarówno szerokość, jak i wysokość

  3. Nie obcinamy, chyba że tekst nie może się zmieścić z powodu ograniczenia, które mu daliśmy (przykład: zbyt długi tekst, zbyt mały dostępny rozmiar). Możemy jednak poprosić o poziomy / pionowy pasek przewijania, jeśli tylko chcemy, tylko w takich przypadkach.

  4. Powinny zezwalać na wiele linii lub jedną linię. W przypadku wielu linii, zezwól na maksymalne i minimalne linie.

  5. Nie powinno być powolne w obliczeniach. Używasz pętli do znalezienia najlepszego rozmiaru? Przynajmniej zoptymalizuj to i nie zwiększaj próbkowania o 1 za każdym razem.

  6. W przypadku linii wieloliniowej należy zezwolić na zmianę rozmiaru lub użycie większej liczby linii i / lub pozwolić na samodzielne wybranie linii za pomocą znaku „\ n”.

Co próbowałem

Próbowałem tak wielu próbek (w tym tych linków, o których pisałem), a także próbowałem je zmodyfikować, aby obsługiwały przypadki, o których mówiłem, ale żadne z nich naprawdę nie działa.

Zrobiłem przykładowy projekt, który pozwala mi wizualnie sprawdzić, czy TextView automatycznie dopasowuje się poprawnie.

Obecnie mój przykładowy projekt losowo losuje tylko tekst (alfabet angielski plus cyfry) i rozmiar textView, i niech pozostanie z jedną linią, ale nawet to nie działa dobrze na żadnej próbce, której próbowałem.

Oto kod (dostępny również tutaj ):

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

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

Pytanie

Czy ktoś wie o rozwiązaniu tego powszechnego problemu, który faktycznie działa?

Nawet rozwiązanie, które ma o wiele mniej funkcji niż to, o czym pisałem, na przykład takie, które ma tylko stałą liczbę wierszy tekstu i dostosowuje czcionkę zgodnie z jego rozmiarem, ale nigdy nie ma dziwnych trzasków i zbyt dużej ilości tekstu duży / mały w porównaniu do dostępnej przestrzeni.


Projekt GitHub

Ponieważ jest to tak ważny TextView, postanowiłem opublikować bibliotekę, aby każdy mógł z niego łatwo korzystać i wnosić coś do tego tutaj .

programista Androida
źródło
Próbowałeś już tego? androidviews.net/2012/12/autoscale-textview
Thrakbad
@Thrakbad to jeden z linków, o których wspomniałem. To także nie przechodzi testu.
programista Androida
Ach, przepraszam, jakoś przegapiłem ostatni przykład
Thrakbad
Tak, proszę mi wierzyć, próbowałem wiele próbek, a także próbowałem je zmodyfikować, aby rozwiązać problemy, które znalazłem, ale nigdy nie udało się. Jeśli znajdziesz coś, co Twoim zdaniem może działać, przetestuj to. Właśnie dla tego opublikowałem przykładowy kod.
programista Androida
1
@ reguła jest to jeden z postów, które już przeczytałem i przetestowałem wszystkie próbki kodu. również myślę, że napisałeś podwójnie.
programista Androida

Odpowiedzi:

147

Dzięki prostym fix MartinH jest tutaj kod ten zajmuje się również android:drawableLeft, android:drawableRight, android:drawableTopi android:drawableBottomtagów.


Moja odpowiedź tutaj powinna sprawić, że będziesz zadowolony z automatycznego skalowania tekstu Zobacz tekst, aby zmieścił się w granicach

Zmodyfikowałem twój przypadek testowy:

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

Wysyłam kod tutaj na żądanie dewelopera Androida :

Efekt końcowy:

Wpisz opis zdjęcia tutaj

Przykładowy plik układu:

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

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

Ostrzeżenie:

Uważaj jednak na ten rozwiązany błąd w Androidzie 3.1 (Honeycomb).

M-WaJeEh
źródło
ok, przetestowałem kod i wydaje się w porządku. jednakże użyłeś getMaxLines (), który jest dla API 16, ale możesz przechowywać maxLines i uzyskać je, zastępując setMaxLines i przechowując jego wartość. zmieniłem to i teraz działa dobrze. Ponadto, ponieważ czasami tekst może być nawet zbyt długi dla najmniejszego rozmiaru, próbowałem użyć setEllipsize i to też zadziałało! myślę, że mamy zwycięzcę. prześlij swój kod tutaj, aby mógł oznaczyć go jako poprawny.
programista Androida
nie usunąłeś metody getMaxLines () i użyłeś własnej. nadal będzie działać tylko z API16 ... zmień go, aby każdy mógł z niego korzystać. proponuję przesłonić setMaxLines, aby zapisać parametr w polu, a następnie uzyskać dostęp do pola zamiast korzystania z getMaxLines.
programista Androida
sprawdź teraz, dodano obsługę maks. linii.
M-WaJeEh
wydaje się w porządku. powinieneś dodać „@TargetApi (Build.VERSION_CODES.JELLY_BEAN)” do metody onTestSize (), aby Lint nie dał o tym błędu / ostrzeżenia, i zamiast tego zainicjować go w CTOR. ustawianie wyszukiwania binarnego na statyczne jest również dobrą rzeczą, i możesz wykonać inicjalizację na największym CTOR i wywołać go z innych CTOR. jest to jednak naprawdę najlepsza odpowiedź i zasługujesz na V. to jest wreszcie dobra odpowiedź na to stare pytanie. dobra robota!
programista Androida
@ M-WaJeEh sprawdź nowy post w tym wątku. użytkownik o nazwie „MartinH” twierdzi, że ma poprawkę do twojego kodu.
programista Androida
14

Zmodyfikowałem nieco odpowiedź M-WaJeEh, aby uwzględnić złożone szuflady po bokach.

Te getCompoundPaddingXXXX()metody powrotu padding of the view + drawable space. Zobacz na przykład: TextView.getCompoundPaddingLeft ()

Problem: to naprawia pomiar szerokości i wysokości przestrzeni TextView dostępnej dla tekstu. Jeśli nie weźmiemy pod uwagę rozmiaru do rysowania, zostanie on zignorowany, a tekst będzie nakładał się na ten rysunek.


Zaktualizowany segment 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
źródło
zaktualizowałeś źródło M-WaJeEh. dlaczego podałeś to jako nową odpowiedź? wiem, że masz dobrą wolę, ale czy dobrze jest ująć to jako odpowiedź? nie jestem pewien, czy może zobaczyć twoją odpowiedź ...
deweloper Androida
1
Właśnie się zarejestrowałem, aby móc udostępnić tę aktualizację. Moja reputacja ma tylko 1, co uniemożliwia mi komentowanie postów, które nie są moje (minimum 15).
MartinH
Nie martw się, być może chciałbyś przekazać moją odpowiedź M-WajeEh, ponieważ w tej chwili nie mogę się nawet z nim skontaktować: /
MartinH
Czy możesz spróbować lepiej opisać, co zrobiłeś? naprawiłeś błąd? czy możesz pokazać zrzut ekranu, aby zademonstrować błąd?
programista Androida
1
Cześć @ MartinH, Witamy w SO. Z grubsza przyjrzę się temu i zaktualizuję moją odpowiedź, wspominając twoje imię i zamieszczając link do twojego postu po testach . Daj mi tylko kilka dni. Utknąłem gdzie indziej.
M-WaJeEh,
7

Ok, wykorzystałem ostatni tydzień do masowego przepisania mojego kodu, aby dokładnie pasował do twojego testu. Możesz teraz skopiować ten 1: 1 i od razu zadziała - w tym setSingleLine(). Pamiętaj o dostosowaniu MIN_TEXT_SIZEi MAX_TEXT_SIZEjeśli szukasz ekstremalnych wartości.

Algorytm konwergencji wygląda następująco:

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

Całą klasę można znaleźć tutaj.

Rezultat jest teraz bardzo elastyczny. Działa to tak samo zadeklarowane w XML, jak:

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

... a także zbudowany programowo jak w teście.

Naprawdę mam nadzieję, że możesz teraz tego użyć. Możesz setText(CharSequence text)teraz zadzwonić, by użyć go przy okazji. Klasa zajmuje się niezwykle rzadkimi wyjątkami i powinna być solidna. Jedyne, czego algorytm jeszcze nie obsługuje, to:

  • Połączenia do setMaxLines(x)gdziex >= 2

Ale dodałem obszerne komentarze, które pomogą Ci to zbudować, jeśli chcesz!


Proszę zanotować:

Jeśli po prostu użyjesz tego normalnie, nie ograniczając go do jednej linii, może dojść do podziału słów, jak wspomniałeś wcześniej. To funkcja Androida , a nie wina AutoFitText. Android zawsze rozbija słowa, które są zbyt długie dla TextView, i jest to całkiem wygodna funkcja. Jeśli chcesz zainterweniować tutaj, zapoznaj się z moimi komentarzami i kodem rozpoczynającym się od wiersza 203. Napisałem już odpowiedni podział i uznanie dla ciebie, wszystko, co musisz zrobić odtąd, to podzielić słowa, a następnie zmodyfikować, jak chcesz .

Podsumowując: powinieneś rozważyć przepisanie testu, aby obsługiwał także znaki spacji, na przykład:

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

To da bardzo piękne (i bardziej realistyczne) wyniki.
Znajdziesz tu także komentarze, które pomogą Ci zacząć w tej sprawie.

Powodzenia i pozdrawiam

Patrick
źródło
są pewne problemy, ale ogólnie działa tak dobrze. problemy: nie obsługuje wielu linii (jak napisałeś) i złamie słowo, jeśli spróbujesz to zrobić, dołącz funkcję API16 (addOnGlobalLayoutListener), która moim zdaniem można całkowicie uniknąć (lub zamiast tego użyć OnPreDrawListener zamiast), używa wyszukiwanie binarne, aby mogło przetestować rozmiary, które w ogóle nie pasują (i bez użycia więcej pamięci, co może powodować wyjątki, jeśli jest zbyt duże), nie obsługuje obsługi, gdy cokolwiek się zmieni (np. sam tekst).
programista Androida
btw, nie musisz używać zmiennej mTestView i przeprowadzać testowania bezpośrednio w bieżącym widoku, dzięki czemu nie będziesz musiał brać pod uwagę wszystkich specjalnych rzeczy. myślę też, że zamiast MAX_TEXT_SIZE możesz użyć targetFieldHeight.
programista Androida
myślę, że zamiast czystego wyszukiwania binarnego (zwłaszcza 2 z nich) można lepiej zgadnąć na podstawie stosunku celu do zmierzonego rozmiaru. są też inne metody (newton i coś innego, czego nie pamiętam), ale nie sądzę, żeby pasowały tutaj. ten, który zasugerowałem teraz, jest lepszy, ponieważ możesz zacząć od rozmiaru minimalnego zamiast testować rozmiar maksymalny. faktem jest, że nie potrzebujesz rozmiaru maksymalnego, ponieważ ciągle zgadujesz.
programista Androida
Myślę również, że 2 wyszukiwania binarne można połączyć w jedną linię, gdy warunkiem może być po prostu: if (getMeasuredWidth ()> = targetFieldWidth || getMeasuredHeight ()> = targetFieldHeight)
programista Androida
1
innym znalezionym przeze mnie problemem jest grawitacja tekstu. myślę, że nie można go wyśrodkować w pionie.
programista Androida
4

Moje wymaganie to

  • Kliknij ScalableTextView
  • Otwórz listę Aktywność i wyświetl elementy łańcucha o różnych długościach.
  • Wybierz tekst z listy.
  • Ustaw tekst z powrotem do ScalableTextView w innym działaniu.

Odsyłam link: Automatyczne skalowanie tekstu Zobacz tekst, aby zmieścił się w granicach (w tym komentarze), a także DialogTitle.java

Przekonałem się, że dostarczone rozwiązanie jest ładne i proste, ale nie zmienia dynamicznie rozmiaru pola tekstowego. Działa świetnie, gdy wybrana długość tekstu z widoku listy jest większa niż istniejąca długość tekstu w ScalableTextView . Po wybraniu tekstu o długości mniejszej niż istniejący tekst ScalableTextViewnie zwiększa rozmiaru tekstu, pokazując tekst w mniejszym rozmiarze.

Zmodyfikowałem ScalableTextView.java, aby ponownie dopasować rozmiar tekstu na podstawie długości tekstu. Tutaj jest mójScalableTextView.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);
            }
        }
    }
}
}

Happy Coding ....

Devendra Vaja
źródło
Czy nie powinieneś tego robić za pomocą wyszukiwania binarnego zamiast zmniejszania o jeden dla każdej iteracji? Wygląda też na to, że ten kod zmusza TextView do umieszczenia jednego wiersza tekstu, zamiast pozwalać mu mieć wiele wierszy.
programista Androida
To było jedyne rozwiązanie, które działało dla mnie. Mój problem związany był z tym, że TExtView nie pasował do widoku tylko w wersji 4.1
FOliveira
wygląda na to, że nie pasuje do rodzica w 100%. patrz zrzut ekranu: s14.postimg.org/93c2xgs75/Screenshot_2.png
user25
4

Wyjaśnię krok po kroku, jak działa ten atrybut w niższych wersjach Androida:

1- Zaimportuj bibliotekę pomocy Androida 26.xx do pliku oceny projektu. Jeśli nie ma biblioteki wsparcia w IDE, pliki zostaną pobrane automatycznie.

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- Otwórz plik XML układu i refaktoryzuj tak, jak ten tag TextView. Ten scenariusz wygląda następująco: po zwiększeniu rozmiaru czcionki w systemie dopasuj tekst do dostępnej szerokości, a nie zawijanie słów.

<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" />
Sinan Ergin
źródło
Ich implementacja automatycznego dopasowania TextView nie działa jednak dobrze. Są chwile, że zamiast zmieniać rozmiar czcionki, tekst po prostu zawija się do następnej linii, źle ... Jest nawet wyświetlany na ich filmach, jakby to była dobra rzecz: youtu.be/fjUdJ2aVqE4?t=14 . Zwróć uwagę, jak „w” w „TextView” przechodzi do następnego wiersza ... W każdym razie utworzyłem tutaj nowe żądanie na ten temat: issueetracker.google.com/issues/68787108 . Proszę rozważyć wystąpienie w roli głównej.
programista Androida
@androiddeveloper Masz rację. Nie wyjaśniłem, że implementacja tego tagu działa w jednym wierszu. Jeśli potrzebujesz linii zawijania, musisz zmienić ten atrybut: android: layout_height = "wrap_content". Ale android.support.v7.widget.AppCompatTextView i gwarancja implementacji stopni działa na ten atrybut.
Sinan Ergin
Nie sądzę, że rozumiesz. Nie chcę, aby części słowa zawijały się do następnego wiersza, chyba że nie ma innego sposobu na rozwiązanie tego. Na filmie możesz łatwo zobaczyć, że czcionki mogły się po prostu zmniejszyć. To jest problem. Mówisz, że wiesz, jak to naprawić? Czyli nie będzie zawijania słów, a zmieni się tylko rozmiar czcionki?
programista Androida
@androiddeveloper Istnieje wiele scenariuszy. Mój scenariusz to: automatyczne dopasowanie tekstu do poprawionego z zawijaniem, a nie zawijaniem słów. Podczas zwiększania rozmiaru czcionki systemowej tekst musi pokazywać się bez zawijania tekstu w widoku tekstu. Jeśli zawijanie słów nie ma dla Ciebie znaczenia, nie przejmuj się ustawieniem stałej wysokości. Ta właściwość nie używa tylko zawijania wyrazów, możesz automatycznie dopasować tekst, aby mieć stałą szerokość TextView.
Sinan Ergin
Według moich testów rozmiary czcionek nie zmieniają się poprawnie i może wystąpić problem zawijania się słów.
programista Androida
3

Ostrzeżenie, błąd w systemie Android 3 (Honeycomb) i Android 4.0 (Ice Cream Sandwich)

Wersje Android: 3.1 - 4.04 mają błąd, który ustawił TextText () w TextView tylko po raz pierwszy (pierwsze wywołanie).

Błąd jest opisany w numerze 22493: Błąd wysokości TextView w Androidzie 4.0 i numerze 17343: wysokość przycisku i tekst nie wraca do pierwotnego stanu po zwiększeniu i zmniejszeniu rozmiaru tekstu w HoneyComb .

Obejściem tego problemu jest dodanie znaku nowej linii do tekstu przypisanego do TextView przed zmianą rozmiaru:

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

Używam go w kodzie w następujący sposób:

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

Dodaję ten znak „\ u3000” po lewej i prawej stronie mojego tekstu, aby zachować wyśrodkowanie. Jeśli masz wyrównany do lewej, to dołącz tylko do prawej. Oczywiście można go również osadzić z widżetem AutoResizeTextView, ale chciałem zachować poprawiony kod na zewnątrz.

Malachiasz
źródło
czy możesz dokładnie pokazać, w których liniach (przed / po których liniach) iw jakiej funkcji?
programista Androida
ok dzięki. mam nadzieję, że to naprawi wszystko i że ktokolwiek to przeczyta, uzna to za pomocne. obecnie nie mogę tego sprawdzić. może następnym razem go użyję.
programista Androida
Ten kod jest wywoływany spoza kodu widoku. Czy znasz alternatywny sposób radzenia sobie z tym w kodzie widoku zamiast tego?
programista Androida
możesz zastąpić textView.setText () i wstawić ten kod do klasy
TextView
Czy nie oznacza to również, że „getText ()” zwróci tekst z dodatkowymi znakami?
programista Androida
3

Istnieje teraz oficjalne rozwiązanie tego problemu. Automatyczne powiększanie TextViews wprowadzone w Androidzie O są dostępne w Bibliotece pomocy 26 i są kompatybilne wstecz aż do Androida 4.0.

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

Nie jestem pewien, dlaczego https://stackoverflow.com/a/42940171/47680, który również zawierał te informacje, został usunięty przez administratora.

Artem Russakovskii
źródło
1
Tak, też o tym słyszałem, ale jak dobrze? Czy jest jakaś próbka użycia tego? Nawet w ich filmie zauważyłem błąd: list z jednego słowa dotarł do wiersza po (owinięty): youtu.be/1N9KveJ-FU8?t=781 (zauważ „W”)
programista Androida
3

Od czerwca 2018 roku Android oficjalnie obsługuje tę funkcję dla Androida 4.0 (API poziom 14) i wyższych.
W systemie Android 8.0 (poziom API 26) i nowszym:

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

Wersje Androida wcześniejsze niż Android 8.0 (poziom API 26):

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

Sprawdź moją szczegółową odpowiedź .

Pomyśl raz kod dwa razy
źródło
1

Konwertuj widok tekstu na obraz i skaluj obraz w granicach.

Oto przykład, jak przekonwertować widok na obraz: Konwersja widoku do mapy bitowej bez wyświetlania go w systemie Android?

Problem w tym, że twój tekst nie będzie dostępny do wyboru, ale powinien załatwić sprawę. Nie próbowałem tego, więc nie jestem pewien, jak by to wyglądało (ze względu na skalowanie).

Dr NotSoKind
źródło
To fajny pomysł, ale spowoduje to piksele tekstu i usunie wszelkie funkcje, których mogę używać z textView.
programista Androida
0

Poniżej znajduje się avalancha TextView z dodatkową funkcjonalnością dla niestandardowej czcionki.

Stosowanie:

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

Nie zapomnij dodać: xmlns: foo = "http://schemas.android.com/apk/res-auto". Czcionka powinna znajdować się w katalogu zasobów

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

znane błędy: nie działa z Androidem 4.03 - czcionki są niewidoczne lub bardzo małe (oryginalna avalancha też nie działa) poniżej znajduje się obejście tego błędu: https://stackoverflow.com/a/21851239/2075875

Malachiasz
źródło
0

Spróbuj tego

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) { }
        };
Zapomniałem nazwy
źródło
Nie sądzę, żeby to zadziałało, ponieważ za każdym razem, gdy zmieniasz rozmiar tekstu, spowoduje to rysowanie dopiero po zakończeniu bloku (co oznacza, że ​​zostanie dodany do kolejki zdarzeń). Oznacza to, że pętla będzie działać najwyżej raz przy każdej zmianie tekstu i nie gwarantuje, że liczba linii nie przekroczy 3 linii tekstu.
programista Androida
0

Jeśli szukasz czegoś łatwiejszego:

 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);
}
Szlifierka
źródło
Wydaje się bardzo krótki, ale czy może przejść test, który przygotowałem? Nie dostaję też, kiedy ta funkcja zostanie wywołana.
programista Androida
Nie, to jeszcze bardziej sugestia. Ulepszę to, aby mógł przejść test jak najszybciej.
Sander,
Cóż, jeśli to sugestia, czy możesz wysłać ją bezpośrednio do Github?
programista Androida
0

Szybka naprawa problemu opisanego przez @Malachiasz

Rozwiązałem problem, dodając niestandardową obsługę tego w klasie automatycznej zmiany rozmiaru:

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

Po prostu zadzwoń yourView.setTextCompat(newTextValue)zamiastyourView.setText(newTextValue)

Ionut Negru
źródło
Po co tworzyć nową funkcję setTextCompat zamiast nadpisywania setText? Również jaki problem?
programista Androida
setText () jest ostatnią metodą TextView, więc nie będziesz w stanie go przesłonić. Inną opcją byłoby zrobienie tego poza niestandardowym TextView, ale to rozwiązanie służy do używania go wewnątrz TextView.
Ionut Negru,
Nie dokładnie. Niektóre z „setText” są prywatne, niektóre są publiczne, a niektóre nie są ostateczne. Wydaje się, że większość można rozwiązać, zastępując public void setText (tekst CharSequence, typ BufferType). Istnieje jednak funkcja końcowa, o której nie wiem, jaki jest jej cel: public final void setText (char [] text, int start, int len). Może rzuć okiem na kod.
deweloper Androida
Wszystkie te warianty setText () faktycznie wywoływałyby metodę setText (tekst CharSequence) na końcu. Jeśli chcesz zapewnić takie samo zachowanie, musisz zastąpić tę metodę, w przeciwnym razie znacznie lepiej byłoby po prostu dodać własną niestandardową metodę setText ().
Ionut Negru,
Tak, ale może w większości przypadków jest w porządku.
deweloper Androida
0

Spróbuj dodać LayoutParamsa MaxWidthi MaxHeightdo TextView. Zmusi układ do przestrzegania kontenera nadrzędnego, a nie przepełnienia.

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

int GeneralApproxWidthOfContainer = 400;
int GeneralApproxHeightOfContainer = 600;
textview.setMaxWidth(400);
textview.setMaxHeight(600);` 
użytkownik3891647
źródło
Nie jestem pewien, o czym mówisz. Czy sugerujesz dodanie tego do próbki textView, aby nie miała małego problemu, który czasem widzę? jeśli tak, to dlaczego nie napisałeś o tym na stronie github?
programista Androida
0

Od Androida O można automatycznie zmieniać rozmiar tekstu w formacie 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 pozwala na instruowanie TextView, aby rozmiar tekstu automatycznie się powiększał lub kurczył, aby wypełnić swój układ w oparciu o cechy i granice TextView. To ustawienie ułatwia optymalizację rozmiaru tekstu na różnych ekranach z dynamiczną zawartością.

Biblioteka pomocy technicznej 26.0 Beta zapewnia pełne wsparcie funkcji automatycznej zmiany rozmiaru TextView na urządzeniach z systemem Android w wersjach wcześniejszych niż Android O. Biblioteka zapewnia obsługę Androida 4.0 (API poziom 14) i wyższych. Pakiet android.support.v4.widget zawiera klasę TextViewCompat umożliwiającą dostęp do funkcji w sposób kompatybilny wstecz.

Javatar
źródło
Niestety, według moich testów, a nawet w filmie Google IO, zauważyłem, że ma problemy, takie jak niewłaściwe zawijanie części słów, zamiast zmiany rozmiaru czcionki. Zgłosiłem to tutaj: Issuetracker.google.com/issues/38468964 i dlatego nadal go nie używam.
programista Androida
0

Po wypróbowaniu oficjalnego automatycznego sortowania tekstu w systemie Android okazało się , że jeśli twoja wersja Androida jest wcześniejsza niż Android 8.0 (poziom API 26), musisz z niej skorzystać android.support.v7.widget.AppCompatTextViewi upewnij się, że twoja wersja biblioteki wsparcia jest wyższa niż 26.0.0. Przykład:

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

aktualizacja :

Zgodnie z odpowiedzią @ android-developer, sprawdzam AppCompatActivitykod źródłowy i znalazłem te dwie linieonCreate

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

oraz w AppCompatDelegateImpl„screateView

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

używa AppCompatViewInflaterwidoku inflatora, podczas AppCompatViewInflatercreateView użyje AppCompatTextView dla „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;
    ...
}

W moim projekcie nie używam AppCompatActivity, więc potrzebuję użyć <android.support.v7.widget.AppCompatTextView>w XML.

weei.zh
źródło
1
W większości przypadków inflator używa AppCompatTextView, gdy piszesz tylko „TextView”, więc nie musisz tego robić.
programista Androida
@androiddeveloper Ale gdy korzystam z TextView, automatyczne dostosowywanie rozmiaru nie działa.
weei.zh
@androiddeveloper Dzięki, powodem jest to, że nie używam AppCompatActivity. AppCompatActivity używa układu inflatora AppCompatViewInflater.
weei.zh
Nie rozumiem
programista Androida