Text image stream around the image

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

There is an image and a TextView , and I need to pass a TextView around the ImageView as follows:

enter image description here

The first possible solution would be to use https://github.com/deano2390/FlowTextView , but it does not extend TextView , so this library is not suitable for me for a number of reasons.

The second solution would be to use the LeadingMarginSpan.LeadingMarginSpan2 span, but it affects every paragraph for every n lines inside the text (for example, in this answer -> How to expand text to wrap the image ), so I get something like this:

enter image description here

But I wanted to set margin only for the first n lines! Then I decided to implement LeadingMarginSpan.Standart and create a counter and increase it when calling the getLeadingMargin(first: Boolean): Int function. When the counter reaches the desired value, the function returns 0 as the field width. And again a failure! Instead of filling the TextView lines, the text simply moved to the left and did not spread to the end of the view!

UPD: Yes, I used onGlobalLayoutListener here

enter image description here

Well, googling for another solution, I found this answer https://stackoverflow.com/a/166268/2126121/ Well, I did 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) holder.mCommentTextView!!.viewTreeObserver.removeOnGlobalLayoutListener( holder.commentTextViewOnGlobalLayoutListener) //add onGlobalLayoutListener holder.mCommentTextView!!.viewTreeObserver.addOnGlobalLayoutListener( if (holder.commentTextViewOnGlobalLayoutListener != null) holder.commentTextViewOnGlobalLayoutListener else CommentTextViewOnGlobalLayoutListener(holder, SpannableString(HtmlCompat.fromHtml( mView.getActivity(), commentDocument.html(), 0, null, SpanTagHandlerCompat(mView.getActivity())))))` 

My onGlobalLayoutListener looks like this: `

 class CommentTextViewOnGlobalLayoutListener( val holder: CommentAndFilesListViewViewHolder, val commentSpannable: Spannable) : ViewTreeObserver.OnGlobalLayoutListener { val LOG_TAG: String = CommentTextViewOnGlobalLayoutListener::class.java.simpleName override fun onGlobalLayout() { holder.mCommentTextView!!.viewTreeObserver.removeGlobalOnLayoutListener(this) //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, CommentLeadingMarginSpan.computeLinesToBeSpanned(holder))) if (charCount <= commentSpannable.length) { commentSpannable.setSpan(CommentLeadingMarginSpan(holder), 0, charCount, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } //set the left margin back to zero (holder.mCommentTextView!!.layoutParams as RelativeLayout.LayoutParams).leftMargin = 0 holder.mCommentTextView!!.text = commentSpannable } } 

`

Well, that works. But how awful! Since I use the view holder template, I have to hold the variable in the listener and delete it if it has not been called and successfully deleted, because the onGlobalLayout function onGlobalLayout not called in time! And this is called too late, so you need to wait about 300 ms, and then look at the entire “reconstruction” of the TextView , and it looks disgusting!

So my question is: How do I make fields for the first n lines in a TextView before it is drawn on the user interface?

+8
android user-interface kotlin android-textview
source share
1 answer

Finally, I managed to find the best solution. It is based on creating a TextView layout of the required width using StaticLayout

  • First, let’s calculate the width of our TextView (just align all the side pads and the width of the image container with the width of the screen) in pixels.
  • Secondly, we need the height of the image container (in pixels).
  • Thirdly, as practice shows, StaticLayout ignores line breaks ( "\n" or "\r" ), so we need to split our line into separate non-breaking lines in order to successfully execute the layout TextView layout: val commentParts = spannable.toString().split("\r")
  • Then, the following algorithm creates StaticLayout instances for each row and checks to see if the rows exceed the height of the image container. In this case, we can finally 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 for ourselves, to let LeadingMarginSpanLayout know, to stop creating fields starting at position [end + 1] char:

      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) break } } totalHeightOfLines += layout.lineCount * holder.mCommentTextView!!.lineHeight } } 
  • Lastly, set LeadingMarginSpan2 to the spannable line:

     spannable.setSpan(CommentLeadingMarginSpan2( CommentLeadingMarginSpan2.calculateLeadingMarginWidthInPx(holder)), 0, if (end == 0) spannable.length else end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) holder.mCommentTextView!!.text = spannable holder.mCommentTextView!!.requestLayout() 
  • In the end, the range is set correctly!

    !

0
source share

All Articles