2017-08-11 2 views
8

J'ai passé des heures à chercher une réponse et n'ai vraiment aucune idée de comment la résoudre. Alors passons aux affaires:Flux textview autour de l'image

Il y a une image et un TextView et je dois couler le TextView autour du ImageView comme ceci:

enter image description here

Première solution possible woult d'utiliser https://github.com/deano2390/FlowTextView mais il est ne s'étendant pas TextView donc cette bibliothèque ne me convient pas pour un certain nombre de raisons.

Deuxième solution serait d'utiliser LeadingMarginSpan.LeadingMarginSpan2 durée, mais elle affecte sur chaque paragraphe pour chaque n lignes à l'intérieur du texte (comme dans cette réponse ->How to layout text to flow around an image), donc j'obtenir smth comme ceci:

enter image description here

Mais je voulais définir la marge uniquement pour les premières lignes n! Ensuite, j'ai décidé d'implémenter LeadingMarginSpan.Standart et de créer un compteur et de l'incrémenter en getLeadingMargin(first: Boolean): Int invocation de fonction. Lorsque le compteur atteint la valeur désirée, la fonction renvoie 0 comme largeur de marge. Et il y a encore un échec! Au lieu de remplir les lignes TextView, le texte vient de se déplacer vers la gauche et ne s'est pas propagé à la fin de la vue!

UPD: Oui, je l'ai utilisé onGlobalLayoutListener ici

enter image description here

Eh bien, googler une autre solution que je trouve cette réponse https://stackoverflow.com/a/27064368/7218592 Ok, je l'ai fait tout comme il est décrit et mis en œuvre le 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())))))` 

Mon OnGlobalLayoutListener ressemble à ceci: `

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

`

Eh bien, cela fonctionne. Mais comme c'est terrible! Comme j'utilise le pattern view holder, je dois tenir une variable à l'écouteur et la retirer si elle n'a pas été appelée et supprimée avec succès car la fonction onGlobalLayout n'a pas été appelée à temps! Et il est appelé trop tard, donc vous devez attendre environ 300 ms et ensuite regarder toute la "reconstruction" du TextView et ça a l'air dégoûtant!

Alors, ma question est la suivante: Comment faire des marges pour première n lignes TextView, avant qu'il ne soit été dessiné sur l'interface utilisateur?

+0

Je voudrais essayer une approche différente ici, j'utiliser un WebView ici et définir l'image et le texte y –

+0

Webview est pas une option car il y a plusieurs cliquable sur mesure couvre que j'ai besoin gérer. Je ne peux pas les abandonner :( – koresuniku

+0

Possible copie de [Comment mettre en page le texte pour circuler autour d'une image] (https://stackoverflow.com/questions/2248759/how-to-layout-text-to-flow-around- une image) –

Répondre

0

Enfin, j'ai réussi à trouver la meilleure solution. Il est basé sur la création d'une maquette de la TextView de la largeur désirée, en utilisant StaticLayout

  1. Tout d'abord, nous allons calculer la largeur de notre TextView (juste soustrayez toute la largeur de rembourrages latéraux et conteneur d'image de la largeur d'affichage) en pixels
  2. Deuxièmement, nous aurons besoin de la hauteur du conteneur d'image (en pixels).
  3. En troisième lieu, comme le montre la pratique, StaticLayout ne tient pas compte des sauts de ligne ("\n" ou "\r"), donc nous devons diviser notre chaîne en un seul, des lignes insécables pour maquette avec succès la mise en page TextView: val commentParts = spannable.toString().split("\r")
  4. Ensuite, l'algorithme suivant crée StaticLayout instances pour chaque ligne et vérifie si les lignes dépassent la hauteur du conteneur d'images. Dans le cas où ils le font, nous pouvons enfin obtenir la position du dernier caractère de la dernière ligne, adjacent à la droite du conteneur d'image. Et nous devons créer un saut de ligne par nous-mêmes, afin de laisser LeadingMarginSpanLayout savoir cesser de faire des marges commençant par [end + 1] position de char:

    var endReached: Boolean = false; 
          commentParts.forEach { 
           if (endReached) [email protected] 
    
           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. Et la dernière chose, mis LeadingMarginSpan2 sur la chaîne spannable:

    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. Finalement, l'intervalle est réglé correctement!

    !