Android 中的垂直(旋转)标签

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1258275/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 02:51:51  来源:igfitidea点击:

Vertical (rotated) label in Android

androidlabeltextviewvertical-alignment

提问by Bostone

I need 2 ways of showing vertical label in Android:

我需要两种在 Android 中显示垂直标签的方法:

  1. Horizontal label turned 90 degrees counterclockwise (letters on the side)
  2. Horizontal label with letters one under the other (like a store sign)
  1. 水平标签逆时针旋转 90 度(侧面有字母)
  2. 字母一个下一个的水平标签(如商店标志)

Do I need to develop custom widgets for both cases (one case), can I make TextView to render that way, and what would be a good way to do something like that if I need to go completely custom?

我是否需要为这两种情况(一种情况)开发自定义小部件,我可以让 TextView 以这种方式呈现,如果我需要完全自定义,有什么好方法可以做这样的事情?

回答by Pointer Null

Here is my elegant and simple vertical text implementation, extending TextView. This means that all standard styles of TextView may be used, because it is extended TextView.

这是我优雅而简单的垂直文本实现,扩展了 TextView。这意味着 TextView 的所有标准样式都可以使用,因为它是扩展的 TextView。

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

By default, rotated text is from top to bottom. If you set android:gravity="bottom", then it's drawn from bottom to top.

默认情况下,旋转文本从上到下。如果你设置 android:gravity="bottom",那么它是从下到上绘制的。

Technically, it fools underlying TextView to think that it's normal rotation (swapping width/height in few places), while drawing it rotated. It works fine also when used in an xml layout.

从技术上讲,它愚弄底层 TextView 认为它是正常旋转(在几个地方交换宽度/高度),同时绘制它旋转。在 xml 布局中使用时它也能正常工作。

EDIT:posting another version, above has problems with animations. This new version works better, but loses some TextView features, such as marquee and similar specialties.

编辑:发布另一个版本,上面有动画问题。这个新版本效果更好,但失去了一些 TextView 功能,例如选取框和类似的特性。

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();
  }
}

EDITKotlin version:

编辑科特林版本:

import android.content.Context
import android.graphics.Canvas
import android.text.BoringLayout
import android.text.Layout
import android.text.TextUtils.TruncateAt
import android.util.AttributeSet
import android.view.Gravity
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.graphics.withSave

class VerticalTextView(context: Context, attrs: AttributeSet) : AppCompatTextView(context, attrs) {
    private val topDown = gravity.let { g ->
        !(Gravity.isVertical(g) && g.and(Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM)
    }
    private val metrics = BoringLayout.Metrics()
    private var padLeft = 0
    private var padTop = 0

    private var layout1: Layout? = null

    override fun setText(text: CharSequence, type: BufferType) {
        super.setText(text, type)
        layout1 = null
    }

    private fun makeLayout(): Layout {
        if (layout1 == null) {
            metrics.width = height
            paint.color = currentTextColor
            paint.drawableState = drawableState
            layout1 = BoringLayout.make(text, paint, metrics.width, Layout.Alignment.ALIGN_NORMAL, 2f, 0f, metrics, false, TruncateAt.END, height - compoundPaddingLeft - compoundPaddingRight)
            padLeft = compoundPaddingLeft
            padTop = extendedPaddingTop
        }
        return layout1!!
    }

    override fun onDraw(c: Canvas) {
        //      c.drawColor(0xffffff80); // TEST
        if (layout == null)
            return
        c.withSave {
            if (topDown) {
                val fm = paint.fontMetrics
                translate(textSize - (fm.bottom + fm.descent), 0f)
                rotate(90f)
            } else {
                translate(textSize, height.toFloat())
                rotate(-90f)
            }
            translate(padLeft.toFloat(), padTop.toFloat())
            makeLayout().draw(this)
        }
    }
}

回答by kostmo

I implemented this for my ChartDroidproject. Create VerticalLabelView.java:

我为我的ChartDroid项目实现了这个。创建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);
    }
}

And in attrs.xml:

并在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>

回答by Prashast

One way to achieve these would be:

实现这些的一种方法是:

  1. Write your own custom view and override onDraw(Canvas).You can draw the text on the canvas and then rotate the canvas.
  2. Same as 1. except this time use a Pathand draw text using drawTextOnPath(...)
  1. 编写您自己的自定义视图并覆盖 onDraw(Canvas)。您可以在画布上绘制文本,然后旋转画布。
  2. 与 1 相同。除了这次使用Path并使用drawTextOnPath(...)绘制文本

回答by albert c braun

Tried both of the VerticalTextView classes in the approved answer, and they worked reasonably well.

在批准的答案中尝试了两个 VerticalTextView 类,它们运行得相当好。

But no matter what I tried, I was unable to position those VerticalTextViews in the center of the containing layout (a RelativeLayout which is part of an item inflated for a RecyclerView).

但无论我尝试什么,我都无法将这些 VerticalTextViews 放置在包含布局的中心(一个 RelativeLayout,它是为 RecyclerView 膨胀的项目的一部分)。

FWIW, after looking around, I found yoog568's VerticalTextView class on GitHub:

FWIW,找了一圈,在GitHub上找到yoog568的VerticalTextView类:

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

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

which I was able to position as desired. You also need to include the following attributes definition in your project:

我能够根据需要定位。您还需要在项目中包含以下属性定义:

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

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

回答by ERJAN

check = (TextView)findViewById(R.id.check);
check.setRotation(-90);

This worked for me, just fine. As for the vertically going down letters - I dont' know.

这对我有用,很好。至于垂直向下的字母 - 我不知道。

回答by Zephyr

There are some minor things need to be pay attention on.

有一些小事需要注意。

It depends on the charset when choosing the rotate or the path ways. for example, if the target charset is English like, and the expected effect looks like,

选择旋转或路径方式时,这取决于字符集。例如,如果目标字符集是English like,并且预期的效果是这样的,

a
b
c
d

you can get this effect by drawing each character one by one, no rotate or path needed.

您可以通过一个一个地绘制每个字符来获得这种效果,不需要旋转或路径。

enter image description here

在此处输入图片说明

you may need rotate or path to get this effect.

您可能需要旋转或路径才能获得此效果。

the tricky part is when you try to render charset such like Mongolian. the glyph in the Typeface need to be rotated 90 degree, so drawTextOnPath() will be a good candidate to use.

棘手的部分是当您尝试渲染像蒙古语这样的字符集时。字体中的字形需要旋转 90 度,因此 drawTextOnPath() 将是一个很好的候选者。

回答by dequec64

Following Pointer Null's answer, I've been able to center the text horizontally by modifying the onDrawmethod this way:

按照Pointer Null的回答,我已经能够通过以onDraw这种方式修改方法使文本水平居中:

@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();
}

You might need to add a portion of the TextView measuredWidth to center a multilined text.

您可能需要添加 TextView measureWidth 的一部分以将多行文本居中。

回答by Marina Budkovets

You can just add to your TextView or other View xml rotation value. This is the easiest way and for me working correct.

您可以添加到您的 TextView 或其他 View xml 旋转值。这是最简单的方法,对我来说工作正常。

<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

结果

回答by Ayaz Alifov

I liked @kostmo's approach. I modified it a bit, because I had an issue - cutting off vertically rotated label when I set its params as WRAP_CONTENT. Thus, a text was not fully visible.

我喜欢@kostmo 的方法。我对其进行了一些修改,因为我遇到了一个问题 - 当我将其参数设置为WRAP_CONTENT. 因此,文本不是完全可见的。

This is how I solved it:

我是这样解决的:

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

If you want to have a text from top to down, then use topToDown(true)method.

如果你想从上到下有一个文本,那么使用topToDown(true)方法。