web-dev-qa-db-fra.com

Étiquette verticale (tournée) dans Android

J'ai besoin de 2 façons de montrer une étiquette verticale dans Android:

  1. Étiquette horizontale tournée à 90 degrés dans le sens antihoraire (lettres sur le côté)
  2. Étiquette horizontale avec des lettres l'une en dessous de l'autre (comme un signe de magasin)

Dois-je développer des widgets personnalisés pour les deux cas (un cas), puis-je utiliser TextView pour rendre le rendu de cette manière, et quel serait un bon moyen de faire quelque chose comme ça si je devais devenir complètement personnalisé?

106
Bostone

Voici mon implémentation de texte vertical élégante et simple, qui étend TextView. Cela signifie que tous les styles standard de TextView peuvent être utilisés, car il s'agit de TextView étendu.

public class VerticalTextView extends TextView{
   final boolean topDown;

   public VerticalTextView(Context context, AttributeSet attrs){
      super(context, attrs);
      final int gravity = getGravity();
      if(Gravity.isVertical(gravity) && (gravity&Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
         setGravity((gravity&Gravity.HORIZONTAL_GRAVITY_MASK) | Gravity.TOP);
         topDown = false;
      }else
         topDown = true;
   }

   @Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
      super.onMeasure(heightMeasureSpec, widthMeasureSpec);
      setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
   }

   @Override
   protected boolean setFrame(int l, int t, int r, int b){
      return super.setFrame(l, t, l+(b-t), t+(r-l));
   }

   @Override
   public void draw(Canvas canvas){
      if(topDown){
         canvas.translate(getHeight(), 0);
         canvas.rotate(90);
      }else {
         canvas.translate(0, getWidth());
         canvas.rotate(-90);
      }
      canvas.clipRect(0, 0, getWidth(), getHeight(), Android.graphics.Region.Op.REPLACE);
      super.draw(canvas);
   }
}

Par défaut, le texte pivoté est de haut en bas. Si vous définissez Android: gravity = "bottom", il est tracé de bas en haut.

Techniquement, il trompe TextView sous-jacent de penser que sa rotation est normale (permutation largeur/hauteur à quelques endroits), tout en la dessinant en rotation. Cela fonctionne bien également lorsqu'il est utilisé dans une mise en page XML.

EDIT: poster une autre version, ci-dessus a des problèmes avec les animations. Cette nouvelle version fonctionne mieux, mais perd certaines fonctionnalités de TextView, telles que Marquee et des spécialités similaires.

public class VerticalTextView extends TextView{
   final boolean topDown;

   public VerticalTextView(Context context, AttributeSet attrs){
      super(context, attrs);
      final int gravity = getGravity();
      if(Gravity.isVertical(gravity) && (gravity&Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
         setGravity((gravity&Gravity.HORIZONTAL_GRAVITY_MASK) | Gravity.TOP);
         topDown = false;
      }else
         topDown = true;
   }

   @Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
      super.onMeasure(heightMeasureSpec, widthMeasureSpec);
      setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
   }

   @Override
   protected void onDraw(Canvas canvas){
      TextPaint textPaint = getPaint(); 
      textPaint.setColor(getCurrentTextColor());
      textPaint.drawableState = getDrawableState();

      canvas.save();

      if(topDown){
         canvas.translate(getWidth(), 0);
         canvas.rotate(90);
      }else {
         canvas.translate(0, getHeight());
         canvas.rotate(-90);
      }


      canvas.translate(getCompoundPaddingLeft(), getExtendedPaddingTop());

      getLayout().draw(canvas);
      canvas.restore();
  }
}
232
Pointer Null

Je l'ai implémenté pour mon projet ChartDroid . Créer VerticalLabelView.Java:

public class VerticalLabelView extends View {
    private TextPaint mTextPaint;
    private String mText;
    private int mAscent;
    private Rect text_bounds = new Rect();

    final static int DEFAULT_TEXT_SIZE = 15;

    public VerticalLabelView(Context context) {
        super(context);
        initLabelView();
    }

    public VerticalLabelView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initLabelView();

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.VerticalLabelView);

        CharSequence s = a.getString(R.styleable.VerticalLabelView_text);
        if (s != null) setText(s.toString());

        setTextColor(a.getColor(R.styleable.VerticalLabelView_textColor, 0xFF000000));

        int textSize = a.getDimensionPixelOffset(R.styleable.VerticalLabelView_textSize, 0);
        if (textSize > 0) setTextSize(textSize);

        a.recycle();
    }

    private final void initLabelView() {
        mTextPaint = new TextPaint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize(DEFAULT_TEXT_SIZE);
        mTextPaint.setColor(0xFF000000);
        mTextPaint.setTextAlign(Align.CENTER);
        setPadding(3, 3, 3, 3);
    }

    public void setText(String text) {
        mText = text;
        requestLayout();
        invalidate();
    }

    public void setTextSize(int size) {
        mTextPaint.setTextSize(size);
        requestLayout();
        invalidate();
    }

    public void setTextColor(int color) {
        mTextPaint.setColor(color);
        invalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        mTextPaint.getTextBounds(mText, 0, mText.length(), text_bounds);
        setMeasuredDimension(
                measureWidth(widthMeasureSpec),
                measureHeight(heightMeasureSpec));
    }

    private int measureWidth(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            // We were told how big to be
            result = specSize;
        } else {
            // Measure the text
            result = text_bounds.height() + getPaddingLeft() + getPaddingRight();

            if (specMode == MeasureSpec.AT_MOST) {
                // Respect AT_MOST value if that was what is called for by measureSpec
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    private int measureHeight(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        mAscent = (int) mTextPaint.ascent();
        if (specMode == MeasureSpec.EXACTLY) {
            // We were told how big to be
            result = specSize;
        } else {
            // Measure the text
            result = text_bounds.width() + getPaddingTop() + getPaddingBottom();

            if (specMode == MeasureSpec.AT_MOST) {
                // Respect AT_MOST value if that was what is called for by measureSpec
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        float text_horizontally_centered_Origin_x = getPaddingLeft() + text_bounds.width()/2f;
        float text_horizontally_centered_Origin_y = getPaddingTop() - mAscent;

        canvas.translate(text_horizontally_centered_Origin_y, text_horizontally_centered_Origin_x);
        canvas.rotate(-90);
        canvas.drawText(mText, 0, 0, mTextPaint);
    }
}

Et en attrs.xml:

<resources>
     <declare-styleable name="VerticalLabelView">
        <attr name="text" format="string" />
        <attr name="textColor" format="color" />
        <attr name="textSize" format="dimension" />
    </declare-styleable>
</resources>
31
kostmo

Un moyen d'y parvenir serait:

  1. Ecrivez votre propre personnalisé afficher et remplacer onDraw (Canvas). Vous pouvez dessiner le texte sur le canevas, puis faire pivoter le canevas.
  2. Identique à 1. sauf que cette fois, utilisez un Path et dessinez le texte à l'aide de drawTextOnPath (...)
9
Prashast

J'ai essayé les deux classes de VerticalTextView dans la réponse approuvée et elles ont fonctionné raisonnablement bien.

Mais peu importe ce que j'ai essayé, je ne pouvais pas positionner ces VerticalTextViews au centre de la disposition contenant (un RelativeLayout qui fait partie d'un élément gonflé pour un RecyclerView).

FWIW, après avoir regardé autour de moi, j'ai trouvé la classe VerticalTextView de yoog568 sur GitHub:

https://github.com/yoog568/VerticalTextView/blob/master/src/com/yoog/widget/VerticalTextView.Java

que j'ai pu positionner comme souhaité. Vous devez également inclure la définition d'attributs suivante dans votre projet:

https://github.com/yoog568/VerticalTextView/blob/master/res/values/attr.xml

7
albert c braun
check = (TextView)findViewById(R.id.check);
check.setRotation(-90);

Cela a fonctionné pour moi, très bien. Quant aux lettres descendant verticalement - je ne sais pas.

3
ERJAN

Il y a quelques petites choses auxquelles il faut faire attention.

Cela dépend du jeu de caractères lors du choix de la rotation ou du chemin. par exemple, si le jeu de caractères cible est anglais et que l'effet attendu ressemble à,

a
b
c
d

vous pouvez obtenir cet effet en dessinant chaque caractère un par un, sans rotation ni chemin.

enter image description here

vous aurez peut-être besoin d'une rotation ou d'un chemin pour obtenir cet effet.

la partie la plus délicate est lorsque vous essayez de rendre un jeu de caractères tel que mongol. le glyphe dans la police de caractères doit être pivoté de 90 degrés; drawTextOnPath () sera donc un bon candidat à utiliser.

2
Zephyr

Suite à la réponse de Pointer Null , j'ai pu centrer le texte horizontalement en modifiant la méthode onDraw de cette façon:

@Override
protected void onDraw(Canvas canvas){
    TextPaint textPaint = getPaint();
    textPaint.setColor(getCurrentTextColor());
    textPaint.drawableState = getDrawableState();
    canvas.save();
    if(topDown){
        canvas.translate(getWidth()/2, 0);
        canvas.rotate(90);
    }else{
        TextView temp = new TextView(getContext());
        temp.setText(this.getText().toString());
        temp.setTypeface(this.getTypeface());
        temp.measure(0, 0);
        canvas.rotate(-90);
        int max = -1 * ((getWidth() - temp.getMeasuredHeight())/2);
        canvas.translate(canvas.getClipBounds().left, canvas.getClipBounds().top - max);
    }
    canvas.translate(getCompoundPaddingLeft(), getExtendedPaddingTop());
    getLayout().draw(canvas);
    canvas.restore();
}

Vous devrez peut-être ajouter une partie de TextView mesurée à largeur pour centrer un texte multiligne.

1
dequec64

Vous pouvez simplement ajouter à votre TextView ou à une autre valeur de rotation View xml. C’est le moyen le plus simple et, pour moi, de travailler correctement.

<LinearLayout
    Android:rotation="-90"
    Android:layout_below="@id/image_view_qr_code"
    Android:layout_above="@+id/text_view_savva_club"
    Android:layout_marginTop="20dp"
    Android:gravity="bottom"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:orientation="vertical">

   <TextView
       Android:textColor="@color/colorPrimary"
       Android:layout_marginStart="40dp"
       Android:textSize="20sp"
       Android:layout_width="wrap_content"
       Android:layout_height="wrap_content"
       Android:text="Дмитриевский Дмитрий Дмитриевич"
       Android:maxLines="2"
       Android:id="@+id/vertical_text_view_name"/>
    <TextView
        Android:textColor="#B32B2A29"
        Android:layout_marginStart="40dp"
        Android:layout_marginTop="15dp"
        Android:textSize="16sp"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:id="@+id/vertical_text_view_phone"
        Android:text="+38 (000) 000-00-00"/>

</LinearLayout>

Result

1
Marina Budkovets

J'ai bien aimé l'approche de @ kostmo. Je l'ai un peu modifié, car j'avais un problème: couper une étiquette tournée verticalement lorsque je définissais ses paramètres comme WRAP_CONTENT. Ainsi, un texte n'était pas entièrement visible.

Voici comment je l'ai résolu:

import Android.annotation.TargetApi;
import Android.content.Context;
import Android.graphics.Canvas;
import Android.graphics.Paint;
import Android.graphics.Rect;
import Android.graphics.Typeface;
import Android.os.Build;
import Android.text.TextPaint;
import Android.util.AttributeSet;
import Android.util.Log;
import Android.util.TypedValue;
import Android.view.View;
import Android.view.ViewGroup;
import Android.widget.TextView;

public class VerticalLabelView extends View
{
    private final String LOG_TAG           = "VerticalLabelView";
    private final int    DEFAULT_TEXT_SIZE = 30;
    private int          _ascent           = 0;
    private int          _leftPadding      = 0;
    private int          _topPadding       = 0;
    private int          _rightPadding     = 0;
    private int          _bottomPadding    = 0;
    private int          _textSize         = 0;
    private int          _measuredWidth;
    private int          _measuredHeight;
    private Rect         _textBounds;
    private TextPaint    _textPaint;
    private String       _text             = "";
    private TextView     _tempView;
    private Typeface     _typeface         = null;
    private boolean      _topToDown = false;

    public VerticalLabelView(Context context)
    {
        super(context);
        initLabelView();
    }

    public VerticalLabelView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        initLabelView();
    }

    public VerticalLabelView(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        initLabelView();
    }

    @TargetApi(Build.VERSION_CODES.Lollipop)
    public VerticalLabelView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
    {
        super(context, attrs, defStyleAttr, defStyleRes);
        initLabelView();
    }

    private final void initLabelView()
    {
        this._textBounds = new Rect();
        this._textPaint = new TextPaint();
        this._textPaint.setAntiAlias(true);
        this._textPaint.setTextAlign(Paint.Align.CENTER);
        this._textPaint.setTextSize(DEFAULT_TEXT_SIZE);
        this._textSize = DEFAULT_TEXT_SIZE;
    }

    public void setText(String text)
    {
        this._text = text;
        requestLayout();
        invalidate();
    }

    public void topToDown(boolean topToDown)
    {
        this._topToDown = topToDown;
    }

    public void setPadding(int padding)
    {
        setPadding(padding, padding, padding, padding);
    }

    public void setPadding(int left, int top, int right, int bottom)
    {
        this._leftPadding = left;
        this._topPadding = top;
        this._rightPadding = right;
        this._bottomPadding = bottom;
        requestLayout();
        invalidate();
    }

    public void setTextSize(int size)
    {
        this._textSize = size;
        this._textPaint.setTextSize(size);
        requestLayout();
        invalidate();
    }

    public void setTextColor(int color)
    {
        this._textPaint.setColor(color);
        invalidate();
    }

    public void setTypeFace(Typeface typeface)
    {
        this._typeface = typeface;
        this._textPaint.setTypeface(typeface);
        requestLayout();
        invalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        try
        {
            this._textPaint.getTextBounds(this._text, 0, this._text.length(), this._textBounds);

            this._tempView = new TextView(getContext());
            this._tempView.setPadding(this._leftPadding, this._topPadding, this._rightPadding, this._bottomPadding);
            this._tempView.setText(this._text);
            this._tempView.setTextSize(TypedValue.COMPLEX_UNIT_PX, this._textSize);
            this._tempView.setTypeface(this._typeface);

            this._tempView.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);

            this._measuredWidth = this._tempView.getMeasuredHeight();
            this._measuredHeight = this._tempView.getMeasuredWidth();

            this._ascent = this._textBounds.height() / 2 + this._measuredWidth / 2;

            setMeasuredDimension(this._measuredWidth, this._measuredHeight);
        }
        catch (Exception e)
        {
            setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
            Log.e(LOG_TAG, Log.getStackTraceString(e));
        }
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);

        if (!this._text.isEmpty())
        {
            float textHorizontallyCenteredOriginX = this._measuredHeight / 2f;
            float textHorizontallyCenteredOriginY = this._ascent;

            canvas.translate(textHorizontallyCenteredOriginY, textHorizontallyCenteredOriginX);

            float rotateDegree = -90;
            float y = 0;

            if (this._topToDown)
            {
                rotateDegree = 90;
                y = this._measuredWidth / 2;
            }

            canvas.rotate(rotateDegree);
            canvas.drawText(this._text, 0, y, this._textPaint);
        }
    }
}

Si vous voulez avoir un texte de haut en bas, utilisez la méthode topToDown(true).

0
Ayaz Alifov