Automatische Anpassung von TextView für Android

231

Hintergrund

Oft müssen wir die Schriftart der Textansicht automatisch an die ihr zugewiesenen Grenzen anpassen.

Das Problem

Obwohl es viele Threads und Beiträge (und Lösungsvorschläge) gibt, die über dieses Problem sprechen (Beispiel hier , hier und hier ), funktioniert leider keiner von ihnen wirklich gut.

Deshalb habe ich beschlossen, jeden von ihnen zu testen, bis ich das richtige Angebot gefunden habe.

Ich denke, dass die Anforderungen an eine solche Textansicht sein sollten:

  1. Sollte die Verwendung von Schriftarten, Schriftarten, Stilen und Zeichensätzen ermöglichen.

  2. Sollte sowohl Breite als auch Höhe handhaben

  3. Keine Kürzung, es sei denn, Text kann aufgrund der von uns angegebenen Einschränkung nicht passen (Beispiel: zu langer Text, zu kleine verfügbare Größe). Wir können jedoch eine horizontale / vertikale Bildlaufleiste anfordern, wenn wir dies wünschen, nur für diese Fälle.

  4. Sollte mehrzeilig oder einzeilig sein. Lassen Sie bei Mehrzeiligkeit die Zeilen max und min zu.

  5. Sollte nicht langsam in der Berechnung sein. Verwenden Sie eine Schleife, um die beste Größe zu finden? Optimieren Sie es zumindest und erhöhen Sie Ihre Stichprobe nicht jedes Mal um 1.

  6. Bei mehrzeiligen Zeilen sollten Sie die Größe ändern oder mehr Zeilen verwenden und / oder die Zeilen selbst auswählen können, indem Sie das Zeichen "\ n" verwenden.

Was ich versucht habe

Ich habe so viele Beispiele ausprobiert (einschließlich der Links, über die ich geschrieben habe), und ich habe auch versucht, sie zu ändern, um die Fälle zu behandeln, über die ich gesprochen habe, aber keines funktioniert wirklich.

Ich habe ein Beispielprojekt erstellt, mit dem ich visuell sehen kann, ob die TextView automatisch richtig passt.

Derzeit randomisiert mein Beispielprojekt nur den Text (das englische Alphabet plus Ziffern) und die Größe der Textansicht nach dem Zufallsprinzip und lässt sie in einer Zeile bleiben, aber selbst dies funktioniert bei keinem der Beispiele, die ich ausprobiert habe, gut.

Hier ist der Code (auch hier verfügbar ):

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

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

Die Frage

Kennt jemand eine Lösung für dieses häufig auftretende Problem, die tatsächlich funktioniert?

Sogar eine Lösung, die viel weniger Funktionen hat als das, worüber ich geschrieben habe, zum Beispiel eine, die nur eine konstante Anzahl von Textzeilen hat und ihre Schriftart entsprechend ihrer Größe anpasst, aber niemals seltsame Störungen aufweist und den Text auch bekommt groß / klein im Vergleich zu seinem verfügbaren Platz.


GitHub-Projekt

Da dies ein so wichtiger ist Textview, habe ich beschlossen , eine Bibliothek zu veröffentlichen, so dass jeder sie leicht nutzen könnten, und dazu beitragen, hier .

Android-Entwickler
quelle
Hast du es versucht? androidviews.net/2012/12/autoscale-textview
Thrakbad
@ Thrakbad es ist einer der Links, die ich erwähnt habe. Es besteht auch den Test nicht.
Android-Entwickler
Tut mir leid, ich habe das letzte Beispiel irgendwie verpasst
Thrakbad,
Ja, bitte glauben Sie mir, ich habe viele Beispiele ausprobiert und auch versucht, sie zu ändern, um die gefundenen Probleme zu beheben, aber es ist mir nie gelungen. Wenn Sie etwas finden, von dem Sie glauben, dass es funktioniert, testen Sie es bitte. Ich habe nur dafür einen Beispielcode gepostet.
Android-Entwickler
1
@ Regel Dies ist einer der Beiträge, die ich bereits gelesen habe, und ich habe alle Codebeispiele getestet. Ich denke auch, dass du doppelt gepostet hast.
Android-Entwickler

Antworten:

147

Dank martinh die einfache Lösung hier , dieser Code kümmert sich auch um android:drawableLeft, android:drawableRight, android:drawableTopund android:drawableBottomTags.


Meine Antwort hier sollte Sie glücklich machen. TextView-Text automatisch skalieren, damit er in Grenzen passt

Ich habe Ihren Testfall geändert:

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

Ich poste den Code hier auf Anfrage eines Android-Entwicklers :

Endeffekt:

Geben Sie hier die Bildbeschreibung ein

Beispiellayoutdatei:

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

Und der Java-Code:

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

Warnung:

Beachten Sie jedoch diesen behobenen Fehler in Android 3.1 (Honeycomb).

M-WaJeEh
quelle
ok, ich habe den Code getestet und es scheint in Ordnung zu sein. Sie haben jedoch getMaxLines () für API 16 verwendet, aber Sie können die maxLines speichern und abrufen, indem Sie setMaxLines überschreiben und ihren Wert speichern. Ich habe sie geändert und jetzt funktioniert sie einwandfrei. Da der Text für die kleinste Größe manchmal sogar zu lang ist, habe ich versucht, setEllipsize zu verwenden, und das hat auch funktioniert! Ich denke, wir haben einen Gewinner. Bitte poste deinen Code hier, damit ich ihn als den richtigen markieren kann.
Android-Entwickler
Sie haben getMaxLines () nicht entfernt und Ihre eigenen verwendet. es funktioniert immer noch nur ab API16 ... bitte ändern Sie es so, dass jeder es verwenden kann. Ich schlage vor, setMaxLines zu überschreiben, um den Parameter in einem Feld zu speichern und dann auf das Feld zuzugreifen, anstatt getMaxLines zu verwenden.
Android-Entwickler
Jetzt prüfen, Unterstützung für maximale Zeilen hinzugefügt.
M-WaJeEh
Sieht gut aus. Sie sollten der Methode onTestSize () "@TargetApi (Build.VERSION_CODES.JELLY_BEAN)" hinzufügen, damit Lint keinen Fehler / keine Warnung darüber ausgibt, und sie stattdessen im CTOR initialisieren. Das Setzen der binären Suche auf statisch ist ebenfalls eine gute Sache, und Sie können die Initialisierung auf dem größten CTOR durchführen und von den anderen CTORs aus aufrufen. Es ist jedoch wirklich die beste Antwort und Sie verdienen ein V. Dies ist endlich eine gute Antwort auf diese alte Frage. Gut gemacht!
Android-Entwickler
@ M-WaJeEh überprüfe den neuen Beitrag in diesem Thread. Ein Benutzer namens "MartinH" gibt an, dass er einen Fix für Ihren Code hat.
Android-Entwickler
14

Ich habe die Antwort von M-WaJeEh ein wenig geändert, um zusammengesetzte Zeichnungen an den Seiten zu berücksichtigen.

Die getCompoundPaddingXXXX()Methoden kehren zurück padding of the view + drawable space. Siehe zum Beispiel: TextView.getCompoundPaddingLeft ()

Problem : Dadurch wird die Messung der Breite und Höhe des für den Text verfügbaren TextView-Bereichs behoben. Wenn wir die Zeichengröße nicht berücksichtigen, wird sie ignoriert und der Text überlappt die Zeichengröße.


Aktualisiertes 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
quelle
Sie haben die Quelle von M-WaJeEh aktualisiert. Warum hast du es als neue Antwort gesetzt? Ich weiß, dass Sie einen guten Willen haben, aber ist es eine gute Sache, ihn als Antwort zu formulieren? Ich bin nicht sicher, ob er Ihre Antwort bemerken kann ...
Android-Entwickler
1
Ich habe mich gerade angemeldet, um dieses Update bereitstellen zu können. Meine Reputationszahl ist nur 1, was mich daran hindert, Beiträge zu kommentieren, die nicht meine sind (mindestens 15).
MartinH
Keine Sorge, vielleicht möchten Sie meine Antwort an M-WajeEh senden, da ich ihn momentan nicht einmal kontaktieren kann: /
MartinH
Können Sie bitte versuchen, besser zu beschreiben, was Sie getan haben? Hast du einen Fehler behoben? Können Sie einen Screenshot zeigen, um den Fehler zu demonstrieren?
Android-Entwickler
1
Hallo @MartinH, Willkommen bei SO. Ich werde dies sicherlich prüfen und meine Antwort aktualisieren, indem ich Ihren Namen erwähne und den Link zu Ihrem Beitrag nach dem Testen poste . Gib mir bitte ein paar Tage. Ich stecke heutzutage woanders fest.
M-WaJeEh
7

Ok, ich habe die letzte Woche genutzt, um meinen Code massiv umzuschreiben, damit er genau zu Ihrem Test passt. Sie können dieses jetzt 1: 1 kopieren und es wird sofort funktionieren - auch setSingleLine(). Bitte denken Sie daran, sich anzupassen MIN_TEXT_SIZEund MAX_TEXT_SIZEwenn Sie extreme Werte anstreben.

Der konvergierende Algorithmus sieht folgendermaßen aus:

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

Und die ganze Klasse finden Sie hier.

Das Ergebnis ist jetzt sehr flexibel. Dies funktioniert genauso wie in XML deklariert wie folgt:

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

... sowie programmgesteuert wie in Ihrem Test erstellt.

Ich hoffe wirklich, dass Sie dies jetzt verwenden können. Sie können setText(CharSequence text)jetzt anrufen , um es übrigens zu verwenden. Die Klasse kümmert sich um erstaunlich seltene Ausnahmen und sollte absolut solide sein. Das einzige, was der Algorithmus noch nicht unterstützt, ist:

  • Ruft setMaxLines(x)wohinx >= 2

Aber ich habe umfangreiche Kommentare hinzugefügt, um Ihnen beim Aufbau zu helfen, wenn Sie möchten!


Bitte beachten Sie:

Wenn Sie dies nur normal verwenden, ohne es auf eine einzelne Zeile zu beschränken, kann es zu Wortbrüchen kommen, wie Sie bereits erwähnt haben. Dies ist eine Android-Funktion , nicht die Schuld der AutoFitText. Android wird immer Wörter brechen, die für eine Textansicht zu lang sind, und es ist eigentlich eine ziemliche Bequemlichkeit. Wenn Sie hier eingreifen möchten, lesen Sie bitte meine Kommentare und meinen Code ab Zeile 203. Ich habe bereits eine angemessene Aufteilung und die Anerkennung für Sie geschrieben. Alles, was Sie von nun an tun müssen, ist, die Wörter zu teilen und sie dann nach Ihren Wünschen zu ändern .

Fazit: Sie sollten unbedingt in Betracht ziehen, Ihren Test neu zu schreiben, um auch Leerzeichen zu unterstützen:

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

Dies führt zu sehr schönen (und realistischeren) Ergebnissen.
Sie werden auch Kommentare finden, um Ihnen den Einstieg in diese Angelegenheit zu erleichtern.

Viel Glück und viele Grüße

Patrick
quelle
Es gibt einige Probleme, aber insgesamt funktioniert es so gut. die Probleme: unterstützt nicht mehrzeilig (wie Sie geschrieben haben) und wird das Wort brechen, falls Sie es versuchen, enthalten API16-Funktion (addOnGlobalLayoutListener), die meiner Meinung nach vollständig vermieden werden kann (oder zumindest OnPreDrawListener verwenden), verwendet Die binäre Suche, mit der möglicherweise Größen getestet werden, die überhaupt nicht passen (und ohne Grund mehr Speicher verwenden, was zu Ausnahmen führen kann, wenn sie zu groß ist), unterstützt die Behandlung nicht, wenn sich etwas ändert (wie der Text selbst).
Android-Entwickler
Übrigens müssen Sie nicht die Variable mTestView verwenden und die Tests direkt in der aktuellen Ansicht durchführen, und auf diese Weise müssen Sie nicht alle besonderen Dinge berücksichtigen. Ich denke auch, dass Sie anstelle von MAX_TEXT_SIZE das targetFieldHeight verwenden können.
Android-Entwickler
Ich denke, dass Sie anstelle einer reinen binären Suche (insbesondere 2 von ihnen) eine bessere Vermutung durch das Verhältnis des Ziels zur gemessenen Größe erhalten können. Es gibt auch andere Methoden (Newton und etwas anderes, an das ich mich nicht erinnere), aber ich denke nicht, dass sie hierher passen. Die, die ich jetzt vorgeschlagen habe, ist besser, da Sie von der minimalen Größe ausgehen können, anstatt die maximale Größe zu testen. Fakt ist, dass Sie keine maximale Größe benötigen, da Sie immer besser raten.
Android-Entwickler
Ich denke auch, dass die 2 binären Suchen zu einer einzigen Zeile zusammengeführt werden könnten, wenn die Bedingung einfach sein könnte: if (getMeasuredWidth ()> = targetFieldWidth || getMeasuredHeight ()> = targetFieldHeight)
Android-Entwickler
1
Ein weiteres Problem, das ich gefunden habe, ist die Textgravitation. Ich denke, es kann nicht vertikal zentriert werden.
Android-Entwickler
4

Meine Anforderung ist zu

  • Klicken Sie auf die ScalableTextView
  • Öffnen Sie eine listActivity und zeigen Sie Zeichenfolgenelemente unterschiedlicher Länge an.
  • Wählen Sie einen Text aus der Liste.
  • Setzen Sie den Text in einer anderen Aktivität wieder auf ScalableTextView.

Ich habe auf den Link verwiesen: TextView-Text automatisch skalieren, damit er in Grenzen passt (einschließlich Kommentare), sowie auf die Datei DialogTitle.java

Ich fand, dass die bereitgestellte Lösung nett und einfach ist, aber die Größe des Textfelds nicht dynamisch ändert. Es funktioniert hervorragend, wenn die in der Listenansicht ausgewählte Textlänge größer ist als die vorhandene Textlänge in der ScalableTextView . Wenn der Text ausgewählt wird, dessen Länge kleiner als der vorhandene Text in ist ScalableTextView, wird der Text nicht vergrößert, und der Text wird in der kleineren Größe angezeigt.

Ich habe die Datei ScalableTextView.java geändert, um die Textgröße basierend auf der Textlänge anzupassen. Hier ist meinScalableTextView.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);
            }
        }
    }
}
}

Viel Spaß beim Codieren ....

Devendra Vaja
quelle
Sollten Sie es nicht mit der binären Suche tun, anstatt es für jede Iteration um eins zu reduzieren? Es scheint auch, dass dieser Code die Textansicht zwingt, aus einer einzigen Textzeile zu bestehen, anstatt zuzulassen, dass sie mehrere Zeilen enthält.
Android-Entwickler
Dies war die einzige Lösung, die für mich funktioniert hat. Mein Problem hing damit zusammen, dass TExtView nicht nur in 4.1 zu der Ansicht passte
FOliveira
Es scheint, dass es nicht zu 100% mit dem Elternteil übereinstimmt. siehe Screenshot: s14.postimg.org/93c2xgs75/Screenshot_2.png
user25
4

Ich werde Schritt für Schritt erklären, wie dieses Attribut funktioniert.

1- Importieren Sie die Android-Unterstützungsbibliothek 26.xx in Ihre Projekt-Gradle-Datei. Wenn die IDE keine Support-Bibliothek enthält, werden sie automatisch heruntergeladen.

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- Öffnen Sie Ihre Layout-XML-Datei und gestalten Sie Ihre TextView wie folgt um. Dieses Szenario lautet: Wenn die Schriftgröße im System erhöht wird, passen Sie den Text an die verfügbare Breite an, nicht an den Zeilenumbruch.

<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
quelle
Die Implementierung von TextView mit automatischer Anpassung funktioniert jedoch nicht gut. Es gibt Zeiten, in denen der Text nicht in der Größe der Schriftgröße geändert wird, sondern nur in der nächsten Zeile angezeigt wird. Er wird sogar in den Videos angezeigt, als wäre es eine gute Sache: youtu.be/fjUdJ2aVqE4?t=14 . Beachten Sie, wie das "w" von "TextView" in die nächste Zeile wechselt ... Wie auch immer, hier wurde eine neue Anfrage dazu erstellt: issuetracker.google.com/issues/68787108 . Bitte erwägen Sie, es zu markieren.
Android-Entwickler
@androiddeveloper Du hast recht. Ich habe nicht erklärt, dass diese Tag-Implementierung einzeilig funktioniert. Wenn Sie Zeilenumbrüche benötigen, müssen Sie dieses Attribut ändern: android: layout_height = "wrap_content". Aber android.support.v7.widget.AppCompatTextView und die Gradle-Implementierungsgarantie funktionieren mit diesem Attribut.
Sinan Ergin
Ich glaube nicht, dass du verstehst. Ich möchte nicht, dass Teilwörter in die nächste Zeile umgebrochen werden, es sei denn, es gibt keinen anderen Weg, dies zu lösen. Im Video können Sie leicht erkennen, dass die Schriftarten stattdessen möglicherweise kleiner geworden sind. Das ist das Problem. Wollen Sie damit sagen, dass Sie wissen, wie man es behebt? Bedeutet das, dass es keine Zeilenumbrüche gibt und sich nur die Schriftgröße ändert?
Android-Entwickler
@androiddeveloper Es gibt viele Szenarien. Mein Szenario ist: Text automatisch anpassen und nicht mit Zeilenumbruch versehen. Während der Schriftgröße des Inkrasationssystems muss der Text in der Textansicht keinen Zeilenumbruch enthalten. Wenn der Zeilenumbruch für Sie keine Rolle spielt, müssen Sie keine feste Höhe einstellen. Diese Eigenschaft verwendet nicht nur Zeilenumbruch, Sie können den Text automatisch anpassen, wenn die Textansicht eine feste Breite hat.
Sinan Ergin
Laut meinen Tests ändern sich die Schriftgrößen nicht richtig und es kann zu Problemen mit dem Zeilenumbruch kommen.
Android-Entwickler
3

Warnung, Fehler in Android 3 (Honeycomb) und Android 4.0 (Ice Cream Sandwich)

Androids-Versionen: 3.1 - 4.04 haben einen Fehler, dass setTextSize () in TextView nur zum ersten Mal funktioniert (erster Aufruf).

Der Fehler wird in Problem 22493: TextView-Höhenfehler in Android 4.0 und Problem 17343 beschrieben: Die Höhe und der Text der Schaltfläche kehren nach dem Erhöhen und Verringern der Textgröße in HoneyComb nicht in den ursprünglichen Zustand zurück .

Die Problemumgehung besteht darin, dem Text, der TextView zugewiesen ist, ein Zeilenumbruchzeichen hinzuzufügen, bevor Sie die Größe ändern:

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

Ich benutze es in meinem Code wie folgt:

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

Ich füge dieses "\ u3000" -Zeichen links und rechts von meinem Text hinzu, um ihn zentriert zu halten. Wenn Sie es nach links ausgerichtet haben, hängen Sie es nur nach rechts an. Natürlich kann es auch in das AutoResizeTextView-Widget eingebettet werden, aber ich wollte den festen Code draußen lassen.

Malachiasz
quelle
Können Sie bitte genau angeben, in welchen Zeilen (vor / nach welchen Zeilen) und in welcher Funktion?
Android-Entwickler
OK danke. Ich hoffe, dass dies alles behebt und wer es liest, wird es hilfreich finden. Derzeit kann ich es nicht überprüfen. Vielleicht werde ich es das nächste Mal benutzen.
Android-Entwickler
Dieser Code wird von außerhalb des Ansichtscodes aufgerufen. Kennen Sie vielleicht eine gute Alternative, um dies stattdessen im Code der Ansicht zu handhaben?
Android-Entwickler
Sie könnten textView.setText () überschreiben und diesen Code in die TextView-Klasse einfügen
Malachiasz
Würde es nicht auch bedeuten, dass "getText ()" den Text mit den zusätzlichen Zeichen zurückgibt?
Android-Entwickler
3

Es gibt jetzt eine offizielle Lösung für dieses Problem. Mit Android O eingeführte automatische Größenanpassung von TextViews ist in der Support Library 26 verfügbar und bis hin zu Android 4.0 abwärtskompatibel.

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

Ich bin nicht sicher, warum https://stackoverflow.com/a/42940171/47680, das auch diese Informationen enthielt, von einem Administrator gelöscht wurde.

Artem Russakovskii
quelle
1
Ja, ich habe auch davon gehört, aber wie gut ist es? Gibt es ein Beispiel für die Verwendung? Sogar in ihrem Video habe ich einen Fehler bemerkt: Ein Brief von einem Wort kam in die Zeile nach (umbrochen): youtu.be/1N9KveJ-FU8?t=781 (beachte das "W")
Android-Entwickler
3

Ab Juni 2018 unterstützt Android diese Funktion offiziell für Android 4.0 (API Level 14) und höher.
Mit Android 8.0 (API Level 26) und höher:

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

Android-Versionen vor Android 8.0 (API-Level 26):

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

Schauen Sie sich meine detaillierte Antwort an .

Denken Sie zweimal an Code
quelle
1

Konvertieren Sie die Textansicht in ein Bild und skalieren Sie das Bild innerhalb der Grenzen.

Hier ist ein Beispiel zum Konvertieren einer Ansicht in ein Bild: Konvertieren einer Ansicht in Bitmap, ohne sie in Android anzuzeigen?

Das Problem ist, dass Ihr Text nicht auswählbar ist, aber es sollte den Trick tun. Ich habe es nicht ausprobiert, daher bin ich mir nicht sicher, wie es aussehen würde (aufgrund der Skalierung).

Dr. NotSoKind
quelle
Es ist eine gute Idee, aber dadurch wird der Text auch pixelig und alle Funktionen, die ich mit textView verwenden kann, werden entfernt.
Android-Entwickler
0

Unten finden Sie Avalancha TextView mit zusätzlichen Funktionen für benutzerdefinierte Schriftarten.

Verwendung:

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

Vergessen Sie nicht hinzuzufügen: xmlns: foo = "http://schemas.android.com/apk/res-auto". Die Schriftart sollte sich im Asset-Firectory befinden

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

Bekannte Fehler: Funktioniert nicht mit Android 4.03 - Schriftarten sind unsichtbar oder sehr klein (Original-Avalancha funktioniert auch nicht). Die folgende Problemumgehung ist für diesen Fehler: https://stackoverflow.com/a/21851239/2075875

Malachiasz
quelle
0

Versuche dies

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) { }
        };
Name vergessen
quelle
Ich denke nicht, dass dies funktionieren wird, da jedes Mal, wenn Sie die Textgröße ändern, die Zeichnung erst nach dem Ende des Blocks erstellt wird (was bedeutet, dass sie der Ereigniswarteschlange hinzugefügt wird). Dies bedeutet, dass die Schleife bei jeder Textänderung höchstens einmal funktioniert und nicht garantiert, dass die Anzahl der Zeilen 3 Textzeilen nicht überschreitet.
Android-Entwickler
0

Wenn Sie etwas einfacheres suchen:

 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);
}
Sander
quelle
Es scheint sehr kurz zu sein, aber kann es den von mir vorbereiteten Test bestehen? Außerdem verstehe ich nicht, wann diese Funktion aufgerufen wird.
Android-Entwickler
Nein, es ist noch mehr ein Vorschlag. Ich werde es verbessern, damit es Ihren Test so schnell wie möglich bestehen kann.
Sander
Wenn es ein Vorschlag ist, können Sie ihn bitte direkt in Github posten?
Android-Entwickler
0

Schnelle Lösung für das von @Malachiasz beschriebene Problem

Ich habe das Problem behoben, indem ich in der Klasse für die automatische Größenänderung benutzerdefinierte Unterstützung hinzugefügt habe:

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

Rufen Sie einfach an yourView.setTextCompat(newTextValue)stattyourView.setText(newTextValue)

Ionut Negru
quelle
Warum eine neue Funktion von setTextCompat erstellen, anstatt setText zu überschreiben? Auch welches Problem?
Android-Entwickler
setText () ist eine endgültige Methode von TextView, daher können Sie sie nicht überschreiben. Eine andere Möglichkeit wäre, dies außerhalb der benutzerdefinierten Textansicht zu tun. Diese Lösung dient jedoch zur Verwendung innerhalb der Textansicht.
Ionut Negru
Nicht genau. Einige der "setText" sind privat, andere öffentlich und andere nicht endgültig. Es scheint, dass die meisten Probleme durch Überschreiben von public void setText (CharSequence-Text, Typ BufferType) gelöst werden können. Es gibt eine letzte Funktion, deren Zweck ich allerdings nicht kenne: public final void setText (char [] text, int start, int len). Vielleicht schauen Sie sich den Code genauer an.
Android-Entwickler
Alle diese Varianten von setText () würden am Ende tatsächlich die Methode setText (CharSequence text) aufrufen. Wenn Sie dasselbe Verhalten sicherstellen möchten, müssen Sie diese Methode überschreiben. Andernfalls ist es viel besser, einfach Ihre eigene benutzerdefinierte setText () -Methode hinzuzufügen.
Ionut Negru
Ja, aber vielleicht ist es in den meisten Fällen in Ordnung.
Android-Entwickler
0

Versuchen Sie, LayoutParamsund MaxWidthund MaxHeightzum hinzuzufügen TextView. Dadurch wird das Layout gezwungen, den übergeordneten Container zu berücksichtigen und nicht überzulaufen.

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

int GeneralApproxWidthOfContainer = 400;
int GeneralApproxHeightOfContainer = 600;
textview.setMaxWidth(400);
textview.setMaxHeight(600);` 
user3891647
quelle
Ich bin mir nicht sicher, wovon du sprichst. Schlagen Sie vor, dies dem textView-Beispiel hinzuzufügen, damit es nicht das kleine Problem gibt, das ich manchmal sehe? Wenn ja, warum haben Sie nicht auf der Github-Website darüber gepostet?
Android-Entwickler
0

Seit Android O ist es möglich, die Größe von Text in XML automatisch zu ändern:

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

Mit Android O können Sie eine TextView anweisen, die Textgröße automatisch zu erweitern oder zu verkleinern, um das Layout basierend auf den Eigenschaften und Grenzen der TextView zu füllen. Diese Einstellung erleichtert die Optimierung der Textgröße auf verschiedenen Bildschirmen mit dynamischem Inhalt.

Die Support Library 26.0 Beta bietet vollständige Unterstützung für die automatische TextView-Funktion auf Geräten, auf denen Android-Versionen vor Android O ausgeführt werden. Die Bibliothek bietet Unterstützung für Android 4.0 (API-Stufe 14) und höher. Das Paket android.support.v4.widget enthält die TextViewCompat-Klasse, um abwärtskompatibel auf Funktionen zuzugreifen.

Javatar
quelle
Leider habe ich laut meinen Tests und sogar im Video von Google IO festgestellt, dass es Probleme gibt, z. B. das falsche Umbrechen von Teilwörtern, anstatt die Schriftgröße zu ändern. Ich habe hier darüber berichtet: issuetracker.google.com/issues/38468964 , und deshalb benutze ich es immer noch nicht.
Android-Entwickler
0

Nachdem ich Android Official Autosizing TextView ausprobiert hatte , stellte ich fest, dass Sie eine Android-Version vor Android 8.0 (API-Level 26) verwenden android.support.v7.widget.AppCompatTextViewmüssen und sicherstellen müssen , dass Ihre Support-Bibliotheksversion über 26.0.0 liegt. Beispiel:

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

Update :

Laut der Antwort von @ android-developer überprüfe ich den AppCompatActivityQuellcode und finde diese beiden Zeilen inonCreate

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

und in AppCompatDelegateImpl'screateView

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

Es wird die AppCompatViewInflaterInflater-Ansicht verwendet. Bei AppCompatViewInflatercreateView wird AppCompatTextView für "TextView" verwendet.

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

In meinem Projekt verwende ich nicht AppCompatActivity, also muss ich <android.support.v7.widget.AppCompatTextView>in XML verwenden.

weei.zh
quelle
1
In den meisten Fällen verwendet der Inflater AppCompatTextView, wenn Sie nur "TextView" schreiben, sodass Sie dies nicht tun müssen.
Android-Entwickler
@androiddeveloper Aber wenn ich TextView benutze, funktioniert die automatische Größenanpassung nicht.
weei.zh
@androiddeveloper Danke, der Grund ist, dass ich nicht benutze AppCompatActivity. AppCompatActivity verwendet das AppCompatViewInflater-Inflater-Layout.
weei.zh
Ich verstehe nicht
Android-Entwickler