在图像周围流动textview

我花了几个小时寻找答案,真的不知道如何解决它。 那么让我们来谈谈业务:

有一个图像和一个TextView ,我需要像这样在ImageView周围流动TextView

在这里输入图像描述

第一个可能的解决方案是使用https://github.com/deano2390/FlowTextView,但它不扩展TextView所以这个库不适合我的原因很多。

第二种解决方案是使用LeadingMarginSpan.LeadingMarginSpan2跨度,但是它会影响文本中每个n行的每个段落(就像在这个答案 – > 如何排列文本以在图像周围流动一样 ),所以我得到这样的结果:

在这里输入图像描述

但是我只想为前n行设置保证金! 然后我决定实现LeadingMarginSpan.Standart并创建一个计数器,并在getLeadingMargin(first: Boolean): Int增加它getLeadingMargin(first: Boolean): Int函数的调用。 当计数器达到期望值时,函数返回0作为余量宽度。 又有一个失败! 而不是填充TextView线,文本只是左移,并没有传播到视图的末尾!

UPD:是的,我在这里使用onGlobalLayoutListener

在这里输入图像描述

那么,谷歌搜索另一个解决方案,我发现这个答案https://stackoverflow.com/a/27064368/7218592好吧,我已经做了一切描述和实施的代码:

  //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())))))` 

我的OnGlobalLayoutListener看起来像这样:`

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

`

那么,它的工作。 但它有多可怕! 正如我使用视图持有人模式,我必须保持一个变量的侦听器,并删除,如果它没有被调用,并成功地删除,因为onGlobalLayout函数没有及时调用! 它被称为太晚,所以你需要等待大约300毫秒,然后观看TextView所有“重建”,它看起来令人作呕!

所以,我的问题是: 在UI上绘制之前,如何在TextView前n行创建边距?

最后,我设法找到了最好的解决方案。 它基于使用StaticLayout创建所需宽度的TextViewStaticLayout

  1. 首先,我们来计算我们的TextView的宽度(只是从显示宽度中减去所有边填充和图像容器宽度)。
  2. 其次,我们需要图像容器的高度(以像素为单位)。
  3. 第三,正如实践所示, StaticLayout忽略换行符( "\n""\r" ),所以我们需要将我们的字符串拆分为单个非断行,以便成功模拟TextView布局: val commentParts = spannable.toString().split("\r")
  4. 然后,下面的算法为每一行创建StaticLayout实例,并检查这些行是否超过图像容器高度。 如果是这样,我们终于可以得到最后一行的最后一个字符的位置,在图像容器的右边。 然后我们需要自己创建一个换行符,让LeadingMarginSpanLayout知道停止以[end + 1]字符位置开始页边距:

      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 } } 
  5. 最后,在可扩展字符串上设置LeadingMarginSpan2

     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() 
  6. 最终,跨度设置正确!

    !