So legen Sie Text so an, dass er um ein Bild fließt

74

Können Sie mir bitte sagen, ob es eine Möglichkeit gibt, Text um ein Bild herum zu gestalten? So was:

------  text text text
|    |  text text text
-----   text text text
text text text text
text text text text

Ich habe eine Antwort von einem Android-Entwickler zu dieser Frage erhalten. Aber ich bin mir nicht sicher, was er mit meiner eigenen Version von TextView meint? Vielen Dank für alle Tipps.

Am Montag, den 8. Februar 2010 um 23:05 Uhr schrieb Romain Guy:

Hallo,

Dies ist nicht nur mit den mitgelieferten Widgets und Layouts möglich. Sie können dazu Ihre eigene Version von TextView schreiben, es sollte nicht schwer sein.

Silverburgh
quelle
Silverburgh: Haben Sie eine Lösung dafür gefunden, die Sie teilen könnten?
ZnQ
4
stackoverflow.com/questions/13526949/… ist wahrscheinlich die Lösung
Victor
Dies ist im Web so einfach zu bewerkstelligen. Ich werde diese Funktion vorerst überspringen.
danny117
Sie können ImageSpan verwenden . Schauen Sie sich diesen Link an
Treffen Sie Vora

Antworten:

65

Jetzt ist es möglich, aber nur für Telefone mit einer Version höher oder gleich 2.2, die android.text.style.LeadingMarginSpan.LeadingMarginSpan2in API 8 verfügbare Schnittstelle zu verwenden.

Hier ist der Artikel , allerdings nicht auf Englisch, aber Sie können den Quellcode des Beispiels direkt von hier herunterladen .

Wenn Sie Ihre Anwendung mit älteren Geräten kompatibel machen möchten, können Sie ein anderes Layout ohne schwebenden Text anzeigen. Hier ist ein Beispiel:

Layout (Standard für ältere Versionen, wird für neuere Versionen programmgesteuert geändert)

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content">
    <ImageView 
            android:id="@+id/thumbnail_view"
            android:src="@drawable/icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    <TextView android:id="@+id/message_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/thumbnail_view"
            android:textSize="18sp"
            android:text="@string/text" />
</RelativeLayout>

Die Helferklasse

class FlowTextHelper {

    private static boolean mNewClassAvailable;

    static {
        if (Integer.parseInt(Build.VERSION.SDK) >= 8) { // Froyo 2.2, API level 8
           mNewClassAvailable = true;
        }
    }

    public static void tryFlowText(String text, View thumbnailView, TextView messageView, Display display){
        // There is nothing I can do for older versions, so just return
        if(!mNewClassAvailable) return;

        // Get height and width of the image and height of the text line
        thumbnailView.measure(display.getWidth(), display.getHeight());
        int height = thumbnailView.getMeasuredHeight();
        int width = thumbnailView.getMeasuredWidth();
        float textLineHeight = messageView.getPaint().getTextSize();

        // Set the span according to the number of lines and width of the image
        int lines = (int)FloatMath.ceil(height / textLineHeight);
        //For an html text you can use this line: SpannableStringBuilder ss = (SpannableStringBuilder)Html.fromHtml(text);
        SpannableString ss = new SpannableString(text);
        ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        messageView.setText(ss);

        // Align the text with the image by removing the rule that the text is to the right of the image
        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)messageView.getLayoutParams();
        int[]rules = params.getRules();
        rules[RelativeLayout.RIGHT_OF] = 0;
    }
}

Die MyLeadingMarginSpan2-Klasse (aktualisiert, um API 21 zu unterstützen)

public class MyLeadingMarginSpan2 implements LeadingMarginSpan2 {
    private int margin;
    private int lines;
    private boolean wasDrawCalled = false;
    private int drawLineCount = 0;

    public MyLeadingMarginSpan2(int lines, int margin) {
        this.margin = margin;
        this.lines = lines;
    }

    @Override
    public int getLeadingMargin(boolean first) {
        boolean isFirstMargin = first;
        // a different algorithm for api 21+
        if (Build.VERSION.SDK_INT >= 21) {
            this.drawLineCount = this.wasDrawCalled ? this.drawLineCount + 1 : 0;
            this.wasDrawCalled = false;
            isFirstMargin = this.drawLineCount <= this.lines;
        }

        return isFirstMargin ? this.margin : 0;
    }

    @Override
    public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
        this.wasDrawCalled = true;
    }

    @Override
    public int getLeadingMarginLineCount() {
        return this.lines;
    }
}

Anwendungsbeispiel

ImageView thumbnailView = (ImageView) findViewById(R.id.thumbnail_view);
TextView messageView = (TextView) findViewById(R.id.message_view);
String text = getString(R.string.text);

Display display = getWindowManager().getDefaultDisplay();
FlowTextHelper.tryFlowText(text, thumbnailView, messageView, display);

So sieht die Anwendung auf dem Android 2.2-Gerät aus: Android 2.2 Der Text fließt um das Bild herum

Und das ist für das Android 2.1-Gerät:

Android 2.1 Der Text befindet sich in der Nähe des Bildes

Wirbelwolf
quelle
2
Anstelle des Class.forName-Tricks können Sie eine einfache Bedingung verwenden: if (Build.VERSION.SDK_INT <Build.VERSION_CODES.FROYO) {...
Wojciech Górski
Ich verwende dies auch. Aber wenn Daten mit HTML-Tags für HTML.fromHtml (HTML-Inhalt) nicht unterstützt werden, helfen Sie mir bitte, ich muss die Liste mit dem Adapter von wrapText wie oben anzeigen
Harsha
@Harsha Die Methode Html.fromHtmlfunktioniert nicht mit HTML, sondern unterstützt nur einfaches HTML mit wenigen Tags.
Wirbelwolf
Tolle Arbeit, ich habe meinen Tag fast verschwendet ... Vielen Dank !!
Palak Darji
1
Dies fügt jedoch auch einen rechten Rand für die Linien hinter dem Bild hinzu (so dass der Text nie vollständig durchläuft). Irgendeine Idee, wie man diesen Fehler behebt?
Parker
7

Heutzutage können Sie die Bibliothek verwenden: https://github.com/deano2390/FlowTextView . So was:

<uk.co.deanwild.flowtextview.FlowTextView
    android:id="@+id/ftv"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:padding="10dip"
            android:src="@drawable/android"/>
</uk.co.deanwild.flowtextview.FlowTextView>
Nikita Shaposhnik
quelle
6

Hier ist eine Verbesserung für den FlowTextHelper (aus der Antwort von vorrtex). Ich habe die Möglichkeit hinzugefügt, zusätzliche Auffüllungen zwischen Text und Bild hinzuzufügen, und die Linienberechnung verbessert, um auch die Auffüllungen zu berücksichtigen. Genießen!

public class FlowTextHelper {
   private static boolean mNewClassAvailable;

   /* class initialization fails when this throws an exception */
   static {
       try {
           Class.forName("android.text.style.LeadingMarginSpan$LeadingMarginSpan2");
           mNewClassAvailable = true;
       } catch (Exception ex) {
           mNewClassAvailable = false;
       }
   }

   public static void tryFlowText(String text, View thumbnailView, TextView messageView, Display display, int addPadding){
       // There is nothing I can do for older versions, so just return
       if(!mNewClassAvailable) return;



       // Get height and width of the image and height of the text line
        thumbnailView.measure(display.getWidth(), display.getHeight());
        int height = thumbnailView.getMeasuredHeight();
        int width = thumbnailView.getMeasuredWidth() + addPadding;
        messageView.measure(width, height); //to allow getTotalPaddingTop
        int padding = messageView.getTotalPaddingTop();
        float textLineHeight = messageView.getPaint().getTextSize();

        // Set the span according to the number of lines and width of the image
        int lines =  (int)Math.round((height - padding) / textLineHeight);
        SpannableString ss = new SpannableString(text);
        //For an html text you can use this line: SpannableStringBuilder ss = (SpannableStringBuilder)Html.fromHtml(text);
        ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, ss.length(), 0);
        messageView.setText(ss);

        // Align the text with the image by removing the rule that the text is to the right of the image
        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)messageView.getLayoutParams();
        int[]rules = params.getRules();
        rules[RelativeLayout.RIGHT_OF] = 0;
   }


}
Ronen Yacobi
quelle
Hallo Ronen. Ich habe große Probleme, die ganze Idee des Problems "Text Wrap Around Image" zu verstehen. Könnten Sie mir bitte sagen, wo ich Informationen zum Schreiben eines solchen Codes erhalten kann? Ich möchte lernen, wie man den Code selbst schreibt, anstatt ihn nur zu kopieren.
Ramona
Hallo @ Ramona Vielleicht werfen Sie einen Blick auf diese Bibliothek: github.com/deano2390/FlowTextView
Ronen Yacobi
Hallo, vielen Dank für Infos. Wissen Sie, wie Sie dies mit dem Bild auf der rechten Seite des Bildschirms erreichen können?
Ramona
3

Die Antworten von Vorrtex und Ronen funktionieren bis auf ein Detail für mich: Nach dem Umwickeln des Bildes mit Text gab es einen seltsamen "negativen" Rand unter dem Bild und auf der gegenüberliegenden Seite. Ich habe herausgefunden, dass ich mich beim Einstellen der Spanne auf dem SpannableString geändert habe

ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, ss.length(), 0);

zu

ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, lines, 0);

das stoppte die Spanne nach dem Bild. Könnte nicht in allen Fällen notwendig sein, dachte aber, ich würde teilen.

johosher
quelle
1

"Aber ich bin nicht sicher, was er mit meiner eigenen Version von TextView meint?"

Er bedeutet, dass Sie die Klasse android.widget.TextView (oder Canvas oder eine andere renderbare Oberfläche) erweitern und Ihre eigene überschreibende Version implementieren können, die eingebettete Bilder mit umlaufendem Text ermöglicht.

Dies kann eine Menge Arbeit sein, je nachdem, wie allgemein Sie es machen.

Peter vdL
quelle
0

Ich kann einen komfortableren Konstruktor für die MyLeadingMarginSpan2-Klasse anbieten

    MyLeadingMarginSpan2(Context cc,int textSize,int height,int width) {                
    int pixelsInLine=(int) (textSize*cc.getResources().getDisplayMetrics().scaledDensity);              
    if (pixelsInLine>0 && height>0) {
        this.lines=height/pixelsInLine;          
    } else  {
        this.lines=0;
    }
    this.margin=width; 
}
Evgeny Karavashkin
quelle
Hallo Evgeny, wie stelle ich das text flow around imageBild auf der rechten Seite des Bildschirms ein? Antwort sehr geschätzt.
Ramona
0

Die Antwort von vorrtex hat bei mir nicht funktioniert, aber ich habe viel davon genommen und meine eigene Lösung gefunden. Hier ist es:

package ie.moses.keepitlocal.util;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.IntRange;
import android.text.Layout;
import android.text.style.LeadingMarginSpan;
import android.view.View;
import android.widget.TextView;

import ie.moses.keepitlocal.util.MeasurementUtils;
import ie.moses.keepitlocal.util.TextUtils;

import static com.google.common.base.Preconditions.checkArgument;

public class WrapViewSpan implements LeadingMarginSpan.LeadingMarginSpan2 {

    private final Context _context;
    private final int _lineCount;
    private int _leadingMargin;
    private int _padding;

    public WrapViewSpan(View wrapeeView, TextView wrappingView) {
        this(wrapeeView, wrappingView, 0);
    }

    /**
     * @param padding Padding in DIP.
     */
    public WrapViewSpan(View wrapeeView, TextView wrappingView, @IntRange(from = 0) int padding) {
        _context = wrapeeView.getContext();
        setPadding(padding);

        int wrapeeHeight = wrapeeView.getHeight();
        float lineHeight = TextUtils.getLineHeight(wrappingView);

        int lineCnt = 0;
        float linesHeight = 0F;
        while ((linesHeight += lineHeight) <= wrapeeHeight) {
            lineCnt++;
        }

        _lineCount = lineCnt;
        _leadingMargin = wrapeeView.getWidth();
    }

    public void setPadding(@IntRange(from = 0) int paddingDp) {
        checkArgument(paddingDp >= 0, "padding cannot be negative");
        _padding = (int) MeasurementUtils.dpiToPixels(_context, paddingDp);
    }

    @Override
    public int getLeadingMarginLineCount() {
        return _lineCount;
    }

    @Override
    public int getLeadingMargin(boolean first) {
        if (first) {
            return _leadingMargin + _padding;
        } else {
            return _padding;
        }
    }

    @Override
    public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline,
                                  int bottom, CharSequence text, int start, int end,
                                  boolean first, Layout layout) {

    }

}

und in meiner eigentlichen Klasse, in der die Spanne verwendet wird:

ViewTreeObserver headerViewTreeObserver = _headerView.getViewTreeObserver();
headerViewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        String descriptionText = _descriptionView.getText().toString();
        SpannableString spannableDescriptionText = new SpannableString(descriptionText);
        LeadingMarginSpan wrapHeaderSpan = new WrapViewSpan(_headerView, _descriptionView, 12);
        spannableDescriptionText.setSpan(
                wrapHeaderSpan,
                0,
                spannableDescriptionText.length(),
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
        );
        _descriptionView.setText(spannableDescriptionText);
        ViewTreeObserver headerViewTreeObserver = _headerView.getViewTreeObserver();
        headerViewTreeObserver.removeOnGlobalLayoutListener(this);
    }
});

Ich brauchte den globalen Layout-Listener, um die richtigen Werte für getWidth()und zu erhalten getHeight().

Hier ist das Ergebnis:

Geben Sie hier die Bildbeschreibung ein

Moses
quelle
-1

Versuchen Sie diese einfache Implementierung mit Kotlin und AndroidX. Erstellen Sie zunächst eine führende Span-Helferklasse:

class LeadingSpan(private val line: Int, private val margin: Int) : LeadingMarginSpan.LeadingMarginSpan2 {

    override fun drawLeadingMargin(canvas: Canvas?, paint: Paint?, x: Int, dir: Int, top: Int, baseline: Int, bottom: Int, text: CharSequence?, start: Int, end: Int, first: Boolean, layout: Layout?) {}

    override fun getLeadingMargin(first: Boolean): Int =  if (first) margin else 0

    override fun getLeadingMarginLineCount(): Int = line
}

Erstellen Sie dann ein Layout mit RelativeLayout:

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/about_desc"
        android:text="@string/about_desc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/logo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</RelativeLayout>

und schließlich in Ihrem activityoder fragmentwie einrichten:

val about = view.findViewById<TextView>(R.id.about_desc)
val logoImage = ContextCompat.getDrawable(view.context, R.mipmap.ic_launcher) as Drawable
@Suppress("DEPRECATION")
view.findViewById<AppCompatImageView>(R.id.logo).setBackgroundDrawable(logoImage)
val spannableString = SpannableString(about.text)
spannableString.setSpan(Helpers.LeadingSpan(5, logoImage.intrinsicWidth + 10), 0, spannableString.length, 0)
about.text = spannableString

Ändern Sie die Nummer 5 Helpers.LeadingSpan(5, logoImage.intrinsicWidth + 10)entsprechend Ihrer Zeichenhöhe.

Gilbert Arafat
quelle