koresuniku koresuniku -3 years ago 135
Android Question

Flow textview around image

I've spent hours looking for answer and have really no idea how to solve it. So let's get down to business:

There is an image and a

and I need to flow the
around the
like this:

enter image description here

First possible solution woult be to use https://github.com/deano2390/FlowTextView but it's not extending
so this library is not suitable for me for number of reasons.

Second solution would be to use
span but it affects on each paragraph for each n lines inside the text (like in this answer -> How to layout text to flow around an image), so I get smth like this:

enter image description here

But I wanted to set margin only for first n lines! Then I decided to implement
and create a counter and increment it in
getLeadingMargin(first: Boolean): Int
function invocation. When the counter reach the desirable value, the function returns 0 as a margin width. And there is a fail again! Instead of filling the
lines, the text just moved left and didn't spread to the end of the view!

UPD: Yes, I've used
in here

enter image description here

Well, googling for another solution I found this answer https://stackoverflow.com/a/27064368/7218592
Ok, I've done everything as described and implemented the code:

//set left margin of desirable width
val params: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
params.leftMargin = holder.imageContainerHeight!!
params.addRule(RelativeLayout.BELOW, holder.mNumberAndTimeInfo!!.id)
holder.mCommentTextView!!.layoutParams = params
if (holder.commentTextViewOnGlobalLayoutListener != null)

//add onGlobalLayoutListener
if (holder.commentTextViewOnGlobalLayoutListener != null)
else CommentTextViewOnGlobalLayoutListener(holder,
mView.getActivity(), commentDocument.html(), 0,
null, SpanTagHandlerCompat(mView.getActivity())))))`

looks like this: `

class CommentTextViewOnGlobalLayoutListener(
val holder: CommentAndFilesListViewViewHolder, val commentSpannable: Spannable) :
ViewTreeObserver.OnGlobalLayoutListener {
val LOG_TAG: String = CommentTextViewOnGlobalLayoutListener::class.java.simpleName

override fun onGlobalLayout() {

//when textview layout is drawn, get the line end to spanify only the needed text
val charCount = holder.mCommentTextView!!.layout.getLineEnd(Math.min(
holder.mCommentTextView!!.layout.lineCount - 1,
if (charCount <= commentSpannable.length) {

//set the left margin back to zero
(holder.mCommentTextView!!.layoutParams as RelativeLayout.LayoutParams).leftMargin = 0
holder.mCommentTextView!!.text = commentSpannable


Well, it works. But how terrible it works! As I'm using view holder pattern I have to hold a variable to the listener and remove if it is not been called and successfully removed because
function wasn't called in time! And it is called too late, so you need to wait about 300 ms and then watch all the "reconstruction" of the
and it looks disgustingly!

So, my question is:
How to make margins for first n lines in
, before it's been drawn on UI?

Answer Source

Finally, I've managed to find the best solution. It is based on creating a mock-up of the TextView of the desired width, using StaticLayout

  1. Firstly, let's calculate the width of our TextView (just substract all the side paddings and image container width from the display width) in pixels.
  2. Secondly, we'll need the height of the image container (in pixels).
  3. Thirdly, as practice shows, StaticLayout ignores line breaks ("\n" or "\r"), so we need to split our string into single, non-breaking lines in order to successfully mock-up the TextView layout: val commentParts = spannable.toString().split("\r")
  4. Then, the following algorithm creates StaticLayout instances for each line, and checks if the lines exceed the image container height. In case they do, we finally can get the position of the last char of the last line, adjacent to the right of the image container. And then we need to create a line break by ourselves, in order to let LeadingMarginSpanLayout know to stop making margins starting with [end + 1] char position:

     var endReached: Boolean = false;
                commentParts.forEach {
                    if (endReached) return@forEach
                    val layout: StaticLayout = StaticLayout(it, holder.mCommentTextView!!.paint,
                            textViewWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0f, false)
                    if (layout.lineCount > 0) {
                        var localHeight: Int
                        for (lineIndex in 0..layout.lineCount - 1) {
                            localHeight = layout.getLineBottom(lineIndex)
                            if (localHeight + totalHeightOfLines > imageContainerHeight) {
                                endReached = true
                                end = layout.getLineEnd(lineIndex)
                                val spannableStringBuilder = SpannableStringBuilder(spannable)
                                if (spannable.substring(end - 1, end) != "\n" &&
                                        spannable.substring(end - 1, end) != "\r") {
                                    if (spannable.substring(end - 1, end) == " ") {
                                        spannableStringBuilder.replace(end - 1, end, "\n")
                                    } else {
                                        spannableStringBuilder.insert(end, "\n")
                                spannable = SpannableString(spannableStringBuilder)
                        totalHeightOfLines += layout.lineCount * holder.mCommentTextView!!.lineHeight
  5. And the last thing, set LeadingMarginSpan2 on the spannable string:

                        0, if (end == 0) spannable.length else end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
    holder.mCommentTextView!!.text = spannable
  6. Eventually, the span is set correctly!


Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download