How to move text across an image

Could you tell me if there is a way to layout the text? around the image? Like this:

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

I got an answer from an Android developer on this. But I'm not sure what it means by running my own version of TextView? Thanks for any advice.

On Monday, February 8, 2010 at 23:05, Romain Guy wrote:

Hello,

This is not possible using only the supplied widgets and layouts. You can write your own version of TextView for this, it should not be difficult.

+64
android android-layout
Feb 12 2018-10-12T00
source share
9 answers

Now it is possible, but only for phones with a version higher than or equal to 2.2 using the android.text.style.LeadingMarginSpan.LeadingMarginSpan2 interface, which is available in API 8.

Here is an article , but not in English, but you can download the sample source code directly from here .

If you want your application to be compatible with older devices, you can display a different layout without floating text. Here is an example:

Layout (the default for older versions will be changed programmatically for newer versions)

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

Helper Class

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

MyLeadingMarginSpan2 class (updated to support 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; } } 

Usage example

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

This is what the application looks like on an Android 2.2 device: Android 2.2 the text flows around the image

And this is for Android 2.1 device:

Android 2.1 the text is located near the image

+64
Dec 11 '11 at 10:09
source share

This is where FlowTextHelper improves (from vorrtex answer). I added the ability to add an extra padding between the text and the image and improve the calculation of the line to also take into account the indentation. Enjoy it!

 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; } } 
+6
Apr 19 2018-12-12T00:
source share

You can currently use the library: https://github.com/deano2390/FlowTextView . Like this:

 <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> 
+5
Mar 23 '17 at 13:12
source share

This question seems to be the same as my question How to fill empty spaces with content under Image in android

I found a solution using the flowtext library, find the first answer that it can help you.

+3
Nov 28
source share

The answers of Vorrtex and Ronen work for me, with one exception. After wrapping the text around the image, there was a strange β€œnegative” edge under the image and on the opposite side. I realized that when setting the range to SpannableString I changed

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

to

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

which stopped the interval after the image. Maybe not necessary in all cases, but thought that I would share.

+3
Nov 12 '13 at 18:55
source share

"But I'm not sure what it means by running my own version of TextView?"

It means that you can extend the android.widget.TextView class (or Canvas or some other display surface) and implement your own overriding version, which allows inline images with text passing around them.

This can be quite a bit of work depending on how much you do it.

+1
Mar 09 '10 at 22:11
source share

I can offer a more convenient constructor for MyLeadingMarginSpan2 Class

  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; } 
0
Oct 14 '14 at 9:53 on
source share

Vorrtex's answer did not work for me, but I took a lot from it and came up with my own solution. Here:

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

and in my real class where the range is used:

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

I need a global layout listener to get the correct values ​​for getWidth() and getHeight() .

Here is the result:

enter image description here

0
Aug 20 '18 at 14:45
source share

Try this simple implementation using kotlin and androidx. first, create a helper class of leading range:

 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 } 

Then create the layout using 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> 

and finally setting in your activity or fragment as:

 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 

change the number 5 in Helpers.LeadingSpan(5, logoImage.intrinsicWidth + 10) according to your drawing height.

0
Jul 14 '19 at 6:51
source share



All Articles