Vertical (rotated) label in Android

后端 未结 10 973
礼貌的吻别
礼貌的吻别 2020-11-22 08:04

I need 2 ways of showing vertical label in Android:

  1. Horizontal label turned 90 degrees counterclockwise (letters on the side)
  2. Horizontal label with l
相关标签:
10条回答
  • 2020-11-22 08:34

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

    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).

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

    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

    0 讨论(0)
  • 2020-11-22 08:43

    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.

    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.

    0 讨论(0)
  • 2020-11-22 08:45

    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.

    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.

    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.

    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.

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

    EDIT Kotlin 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)
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 08:45

    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 Path and draw text using drawTextOnPath(...)
    0 讨论(0)
提交回复
热议问题