2016-08-09 1 views
4

J'essaye de formater des hashtags à l'intérieur d'un TextView/EditText (Dire comme des puces mentionnées dans les spécifications matérielles de conception). Je suis en mesure de formater l'arrière-plan en utilisant ReplacementSpan. Mais le problème est que je ne suis pas capable d'augmenter l'interligne dans TextView/EditText. Voir l'image ci-dessous enter image description hereAndroid - Ajouter une marge pour SpannableStringBuilder en utilisant ReplacementSpan

La question est de savoir comment ajouter une marge supérieure et inférieure pour les hashtags?

Voici le code où j'ajoute l'arrière-plan du texte:

/** 
    * First draw a rectangle 
    * Then draw text on top 
    */ 
    @Override 
    public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { 
     RectF rect = new RectF(x, top, x + measureText(paint, text, start, end), bottom); 
     paint.setColor(backgroundColor); 
     canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, paint); 
     paint.setColor(textColor); 
     canvas.drawText(text, start, end, x, y, paint); 
    } 
+0

Ainsi at-il finissent par travailler pour vous? –

Répondre

4

J'ai eu un problème similaire il y a quelque temps et c'est la solution que je suis venu avec:

Le TextView hébergement en xml:

<TextView 
     android:layout_width="match_parent" 
     android:layout_height="wrap_content" 
     android:paddingTop="18dp" 
     android:paddingBottom="18dp" 
     android:paddingLeft="8dp" 
     android:paddingRight="8dp" 
     android:gravity="fill" 
     android:textSize="12sp" 
     android:lineSpacingExtra="10sp" 
     android:textStyle="bold" 
     android:text="@{viewModel.renderedTagBadges}"> 

Une version personnalisée de ReplacementSpan

public class TagBadgeSpannable extends ReplacementSpan implements LineHeightSpan { 

    private static int CORNER_RADIUS = 30; 
    private final int textColor; 
    private final int backgroundColor; 
    private final int lineHeight; 

    public TagBadgeSpannable(int lineHeight, int textColor, int backgroundColor) { 
     super(); 
     this.textColor = textColor; 
     this.backgroundColor = backgroundColor; 
     this.lineHeight = lineHeight; 
    } 

    @Override 
    public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { 
     final float textSize = paint.getTextSize(); 
     final float textLength = x + measureText(paint, text, start, end); 
     final float badgeHeight = textSize * 2.25f; 
     final float textOffsetVertical = textSize * 1.45f; 

     RectF badge = new RectF(x, y, textLength, y + badgeHeight); 
     paint.setColor(backgroundColor); 
     canvas.drawRoundRect(badge, CORNER_RADIUS, CORNER_RADIUS, paint); 

     paint.setColor(textColor); 
     canvas.drawText(text, start, end, x, y + textOffsetVertical, paint); 
    } 

    @Override 
    public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { 
     return Math.round(paint.measureText(text, start, end)); 
    } 

    private float measureText(Paint paint, CharSequence text, int start, int end) { 
     return paint.measureText(text, start, end); 
    } 

    @Override 
    public void chooseHeight(CharSequence charSequence, int i, int i1, int i2, int i3, Paint.FontMetricsInt fontMetricsInt) { 
     fontMetricsInt.bottom += lineHeight; 
     fontMetricsInt.descent += lineHeight; 
    } 
} 

Et enfin un constructeur qui crée le Spannable

public class AndroidTagBadgeBuilder implements TagBadgeBuilder { 

    private final SpannableStringBuilder stringBuilder; 
    private final String textColor; 
    private final int lineHeight; 

    public AndroidTagBadgeBuilder(SpannableStringBuilder stringBuilder, int lineHeight, String textColor) { 
     this.stringBuilder = stringBuilder; 
     this.lineHeight = lineHeight; 
     this.textColor = textColor; 
    } 

    @Override 
    public void appendTag(String tagName, String badgeColor) { 
     final String nbspSpacing = "\u202F\u202F"; // none-breaking spaces 

     String badgeText = nbspSpacing + tagName + nbspSpacing; 
     stringBuilder.append(badgeText); 
     stringBuilder.setSpan(
      new TagBadgeSpannable(lineHeight, Color.parseColor(textColor), Color.parseColor(badgeColor)), 
      stringBuilder.length() - badgeText.length(), 
      stringBuilder.length()- badgeText.length() + badgeText.length(), 
      Spanned.SPAN_EXCLUSIVE_EXCLUSIVE 
     ); 
     stringBuilder.append(" "); 
    } 

    @Override 
    public CharSequence getTags() { 
     return stringBuilder; 
    } 

    @Override 
    public void clear() { 
     stringBuilder.clear(); 
     stringBuilder.clearSpans(); 
    } 
} 

Le résultat ressemblera à quelque chose comme ceci: Rendered badges in TextView

Tweak les mesures TagBadgeSpannable à votre goût.

J'ai téléchargé un exemple de projet très minimal en utilisant ce code à github alors n'hésitez pas à le vérifier.

REMARQUE: L'exemple utilise Android Databinding et est écrit style MVVM

4

Balisage du texte dans Android est si mal documenté, écrit ce code est comme sentir votre chemin à travers l'obscurité. J'ai fait un peu de ça, donc je vais partager ce que je sais.

Vous pouvez gérer l'interlignage en enveloppant vos puces dans un LineHeightSpan. LineHeightSpan est une interface qui étend l'interface du marqueur ParagraphStyle, ce qui vous indique que cela affecte l'apparence au niveau du paragraphe. Peut-être un bon moyen de l'expliquer est de comparer votre sous-classe ReplacementSpan à un HTML <span>, alors qu'un ParagraphStyle span comme LineHeightSpan est comme un HTML <div>.

L'interface LineHeightSpan se compose d'une méthode:

public void chooseHeight(CharSequence text, int start, int end, 
         int spanstartv, int v, 
         Paint.FontMetricsInt fm); 

Cette méthode est appelée pour chaque ligne dans votre paragraphe

  • text est votre chaîne Spanned.
  • start est l'indice du caractère au début de la ligne actuelle
  • end est l'indice du caractère à la fin de la ligne courante
  • spanstartv est (IDE), le décalage vertical de la durée entière elle-même
  • v est (IDE), le décalage vertical de la ligne courante
  • fm est l'objet FontMetrics, qui est en fait un paramètre de retour (entrée/sortie). Votre code apportera des modifications à fm et TextView les utilisera lors du dessin.

Donc, ce que le TextView fera est d'appeler cette méthode une fois pour chaque ligne qu'il traite. En fonction des paramètres, avec votre chaîne Spanned, vous définissez le FontMetrics pour rendre la ligne avec les valeurs de votre choix.

Voici un exemple, je l'ai fait pour un élément de balle dans une liste (pensez <ol><li>) où je voulais une certaine séparation entre chaque élément de la liste:

@Override 
    public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm) { 

     int incr = Math.round(.36F * fm.ascent); // note: ascent is negative 

     // first line: add space to the top 
     if (((Spanned) text).getSpanStart(this) == start) { 
      fm.ascent += incr; 
      fm.top = fm.ascent + 1; 
     } 

     // last line: add space to the bottom 
     if (((Spanned) text).getSpanEnd(this) == end) { 
      fm.bottom -= incr; 
     } 

    } 

Votre version sera probablement encore plus simple, juste changer le FontMetrics la de la même manière pour chaque ligne que l'on appelle.

Lorsqu'il s'agit de déchiffrer le FontMetrics, l'enregistreur et le débogueur sont vos amis. Vous devrez juste garder les valeurs de réglage jusqu'à ce que vous obteniez quelque chose que vous aimez.

+0

Vraiment bonne explication. Merci pour votre gentille explication. –

0

Est-ce que BackgroundColorSpan ne fonctionne pas?

Pour votre cas spécifique, vous pouvez également définir lineSpacing pour TextView.

Une dernière option (ne l'a pas testée), serait de calculer la hauteur de l'étendue pour être plus grande que celle que vous dessinez. Vous pouvez vérifier l'implémentation de getSize dans DynamicDrawableSpan pour voir comment définir la hauteur de l'étendue en utilisant l'instance FontMetrics donnée en tant que paramètre.