Cómo distribuir texto para que fluya alrededor de una imagen

¿Puedes decirme si hay una forma de distribuir texto alrededor de una imagen? Me gusta esto:

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

Recibí una respuesta de un desarrollador de Android sobre esta pregunta. Pero no estoy seguro de a qué se refiere al hacer mi propia versión de TextView. Gracias por cualquier consejo.

El lunes 8 de febrero de 2010 a las 11:05 PM, Romain Guy escribió:

Hola,

Esto no es posible usando únicamente los widgets y diseños suministrados. Podrías escribir tu propia versión de TextView para hacer esto, no debería ser difícil.

Ahora es posible, pero solo para teléfonos con una versión superior o igual a 2.2 utilizando la interfaz android.text.style.LeadingMarginSpan.LeadingMarginSpan2 que está disponible en API 8.

Aquí está el artículo , no en inglés, pero puedes descargar el código fuente del ejemplo directamente desde aquí .

Si desea que su aplicación sea compatible con dispositivos más antiguos, puede mostrar un diseño diferente sin texto flotante. Aquí hay un ejemplo:

Diseño (predeterminado para versiones anteriores, se cambiará programáticamente para versiones más nuevas)

     

La clase de ayuda

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

La clase MyLeadingMarginSpan2 (actualizada para admitir API 21)

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

Ejemplo del uso

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

Así es como se ve la aplicación en el dispositivo Android 2.2: Android 2.2 el texto fluye alrededor de la imagen

Y esto es para el dispositivo Android 2.1:

Android 2.1 el texto está situado cerca de la imagen

Aquí hay una mejora para FlowTextHelper (de la respuesta de vorrtex). Agregué la posibilidad de agregar relleno extra entre el texto y la imagen y mejoré el cálculo de la línea para tener también en cuenta el relleno. ¡Disfrutar!

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

Esta pregunta parece igual que mi pregunta Cómo llenar los espacios vacíos con contenido debajo de la Imagen en Android

Encontré la solución usando la librería flowtext. Encuentre la primera respuesta que podría ayudarlo hasta ahora

Las respuestas de Vorrtex y Ronen me funcionan a excepción de un detalle: después de envolver el texto alrededor de la imagen, había un extraño margen “negativo” debajo de la imagen y en el lado opuesto. Descubrí que al configurar el lapso en SpannableString cambié

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

a

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

que detuvo el lapso después de la imagen. Puede que no sea necesario en todos los casos, pero pensé que lo compartiría.

Hoy en día puedes usar la biblioteca: https://github.com/deano2390/FlowTextView . Me gusta esto:

    

“¿Pero no estoy seguro de a qué se refiere al hacer mi propia versión de TextView?”

Quiere decir que puede extender la clase android.widget.TextView (o Canvas o alguna otra superficie renderizable) e implementar su propia versión de reemplazo que permite imágenes incrustadas con texto que fluye a su alrededor.

Esto podría ser bastante trabajo dependiendo de qué tan general lo hagas.

Puedo ofrecer un constructor más cómodo para la clase MyLeadingMarginSpan2

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

La respuesta de vorrtex no funcionó, pero tomé mucho de ella y se me ocurrió mi propia solución. Aquí está:

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

y en mi clase real donde se usa el lapso:

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

Necesitaba el oyente de diseño global para obtener los valores correctos para getWidth() y getHeight() .

Aquí está el resultado:

enter image description here