Is it possible to write vertically in a textview in android?
Asked Answered
D

12

41

Let's say you have a normal TextView, with "Stackoverflow" written in it, Is it possible to rotate the TextView by -90°, to have the S at the bottom and the W at the top of the screen? Of course I could write my text as an image, rotate it and use it that way, but I am interested in the text right now. Thanks.

Dermot answered 22/5, 2010 at 16:3 Comment(1)
It is possible to do this in XML now: #3775270Startling
E
44

You can set your textview as you would normally do

for example:

 <TextView android:id="@+id/txtview"
    android:layout_height="fill_parent"
    android:layout_width="wrap_content" />

and write a function in your activity to

  • reverse the characters in your text
  • insert \n after every characters

and then set the text to the TextView.

If you dont want to insert the \n, you will have to set the size of android:layout_width and play with font size not to have 2 characters fitting on the same line and no truncation

Edit If I have understood you correctly, you can get what you want by using animation.

For example

Under res/anim/myanim.xml:

<rotate  xmlns:android="http://schemas.android.com/apk/res/android"
           android:fromDegrees="0" 
           android:toDegrees="-90"
           android:pivotX="50%"
           android:duration="0" />

You will have to play with this file to define where you want your text view to be placed.

In your activity:

  TextView t = (TextView)findViewById(R.id.txtview);
  String txt = "Stackoverflow";         
  t.setText(txt);

  RotateAnimation ranim = (RotateAnimation)AnimationUtils.loadAnimation(this, R.anim.myanim);
  ranim.setFillAfter(true); //For the textview to remain at the same place after the rotation
  t.setAnimation(ranim);
Ensue answered 22/5, 2010 at 16:44 Comment(5)
Yeah I though about that but that is not what I explained and asked for actually, i don't want each character one on top of the other, but all of them with a -90° rotation... is this possible?Dermot
aha ok - I think I know what you mean now - I have no idea how to do that but I ll think about itEnsue
@Ensue width wise it is taking more space. I've tried myanim.xmlPhan
Nice Sample, really it is helped me.Derinna
However, Onclick listener is not working properly. is there any solution for this.?Impute
P
30

Worked for me:

public class VerticalTextView extends TextView {

    private int _width, _height;
    private final Rect _bounds = new Rect();

    public VerticalTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

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

    public VerticalTextView(Context context) {
        super(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // vise versa
        _height = getMeasuredWidth();
        _width = getMeasuredHeight();
        setMeasuredDimension(_width, _height);
    }

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

        canvas.translate(_width, _height);
        canvas.rotate(-90);

        TextPaint paint = getPaint();
        paint.setColor(getTextColors().getDefaultColor());

        String text = text();

        paint.getTextBounds(text, 0, text.length(), _bounds);
        canvas.drawText(text, getCompoundPaddingLeft(), (_bounds.height() - _width) / 2, paint);

        canvas.restore();
    }

    private String text() {
        return super.getText().toString();
    }
}

xml:

<VerticalTextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="left|center_vertical"
            android:background="@color/feedback_background"
            android:padding="4dip"
            android:text="@string/feedback"
            android:textColor="@color/feedback_text_color"
            android:textSize="@dimen/text_xlarge" />
Pooch answered 8/6, 2013 at 17:22 Comment(3)
I know that's not a big deal, but if someday some n00b like me try to implement it and get some erros, try to use: <com.apppackege.VerticalTextView> instead of just <VerticalTextView>. Anyway, your answer helped me a lot! Thank you!Spurrier
How to rotate to 90 degree instead of -90?Lysis
when i change it to 90 or 270 it goes blankRounder
C
22
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:text="xyz"
            android:rotation="-90"
            android:gravity="fill_vertical"/>
Charr answered 16/9, 2015 at 8:51 Comment(1)
this works but it gets very hard to use the UI editor afterwardsCaesium
L
8

Try this. It works fine for me. It can display one line of text vertically, but just one line. colors, size, paddings, margins and background all work fine.

public class VerticalTextView extends TextView {

    public VerticalTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

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

    public VerticalTextView(Context context) {
        super(context);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        final ColorStateList csl = getTextColors();
        final int color = csl.getDefaultColor();
        final int paddingBottom = getPaddingBottom();
        final int paddingTop = getPaddingTop();
        final int viewWidth = getWidth();
        final int viewHeight = getHeight();
        final TextPaint paint = getPaint();
        paint.setColor(color);
        final float bottom = viewWidth * 9.0f / 11.0f;
        Path p = new Path();
        p.moveTo(bottom, viewHeight - paddingBottom - paddingTop);
        p.lineTo(bottom, paddingTop);
        canvas.drawTextOnPath(getText().toString(), p, 0, 0, paint);
    }
}
Lyonnais answered 15/9, 2012 at 0:10 Comment(4)
I use the class in xml and it does not worked. Could you please tell me how to use it? thanks!Age
Did you get solution herbertD ?Standice
Didn't work for me, the text wasn't properly displayedDejected
If you want neat positioning, please add paint.setTextAlign(Paint.Align.CENTER); to paint initializationFactitive
S
7

If you are using API 11 or later, you may try:

TextView t = (TextView) findViewById(R.id.txtview);
String txt = "Stackoverflow";         
t.setText(txt);
t.setRotation(90); // 90 degree rotation
Snath answered 10/1, 2013 at 18:42 Comment(1)
this will have issues with textview bounds. onMeasure() should be re-defined for it.Commonage
U
2

I'll show for you guys my example of custom vertical button with the rotated TextView in it:

<!--Undo button-->
<LinearLayout
    android:id="@+id/undo_points_pr_a"
    android:layout_width="@dimen/zero_dp"
    android:gravity="center"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:layout_weight="1"
    android:background="@color/timerUndoButton">

    <ImageView
        android:layout_width="@dimen/large"
        android:layout_height="@dimen/large"
        android:src="@drawable/undo_icon"
        android:rotation="-90"
        android:layout_marginBottom="@dimen/medium"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/undo"
        android:textSize="@dimen/small_medium_text"
        android:rotation="-90"/>

</LinearLayout>

And this is how it looks in Android Studio:

And this is how it looks in Android Studio:

And of course you have to modify this code to make it works for you. (in attributes like android:layout_width, android:layout_height, etc.)

Unpretentious answered 18/11, 2018 at 12:32 Comment(0)
I
0

I provided a solution in another StackOverflow question. You can get vertical TextView by extending from View and overriding its onMeasure() and onDraw() methods. However, it will not support all TextView features, rather its main ones like padding, size, color and font.

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);
        }
    }
}
Ivo answered 9/10, 2017 at 13:2 Comment(0)
W
0

I think the simplest answer to your question to write "Stackoverflow" vertically is to use an ordinary TextView, and since the text will wrap to the next line when narrowed, play around with the width of the TextView so there is one letter is on each line and if you need more space on the edge as a buffer increase the "padding" and/or "margin" of the TextView.

Weylin answered 25/10, 2019 at 14:19 Comment(0)
B
0

My initial approach to rendering vertical text inside a vertical LinearLayout was as follows (this is Kotlin, in Java use setRoatation etc.):

val tv = TextView(context)
tv.gravity = Gravity.CENTER
tv.rotation = 90F
tv.height = calcHeight(...)
linearLabels.addView(tv)

approach #1

As you can see the problem is that the TextView goes vertically but still treats its width as if it were oriented horizontally! =/

Thus approach #2 consisted of additionally switching width and height manually to account for this:

tv.measure(0, 0)
// tv.setSingleLine()
tv.width = tv.measuredHeight
tv.height = calcHeight(...)

approach #2

This however resulted in the labels wrapping around to the next line (or being cropped if you setSingleLine) after the relatively short width. Again, this boils down to confusing x with y.

My approach #3 was thus to wrap the TextView in a RelativeLayout. The idea is to allow the TextView any width it wants by extending it far to the left and the right (here, 200 pixels in both directions). But then I give the RelativeLayout negative margins to ensure it is drawn as a narrow column. Here is my full code for this screenshot:

val tv = TextView(context)
tv.text = getLabel(...)
tv.gravity = Gravity.CENTER
tv.rotation = 90F

tv.measure(0, 0)
tv.width = tv.measuredHeight + 400  // 400 IQ
tv.height = calcHeight(...)

val tvHolder = RelativeLayout(context)
val lp = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
    LinearLayout.LayoutParams.WRAP_CONTENT)
lp.setMargins(-200, 0, -200, 0)
tvHolder.layoutParams = lp
tvHolder.addView(tv)
linearLabels.addView(tvHolder)

val iv = ImageView(context)
iv.setImageResource(R.drawable.divider)
linearLabels.addView(iv)

approach #3

As a general tip, this strategy of having a view "hold" another view has been really useful for me in positioning things in Android! For example, the info window below the ActionBar uses the same tactic!

For text starting at the bottom just rotate it by -90F instead of 90F degrees.

Beiderbecke answered 8/7, 2020 at 20:31 Comment(0)
M
0
public class VerticalTextView extends AppCompatTextView {
    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();
    }

}
Mai answered 16/7, 2020 at 10:37 Comment(0)
R
0

Try this: works for me

public class VerticalTextView extends AppCompatTextView {

public final static int ORIENTATION_UP_TO_DOWN = 0;
public final static int ORIENTATION_DOWN_TO_UP = 1;
public final static int ORIENTATION_LEFT_TO_RIGHT = 2;
public final static int ORIENTATION_RIGHT_TO_LEFT = 3;

Rect text_bounds = new Rect();
private int direction;

public VerticalTextView(Context context) {
    super(context);
}

public VerticalTextView(Context context, AttributeSet attrs) {
    super(context, attrs);

    TypedArray a = context.obtainStyledAttributes(attrs,
            R.styleable.verticaltextview);
    direction = a.getInt(R.styleable.verticaltextview_direction, 0);
    a.recycle();

    requestLayout();
    invalidate();

}

public void setDirection(int direction) {
    this.direction = direction;

    requestLayout();
    invalidate();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    getPaint().getTextBounds(getText().toString(), 0, getText().length(),
            text_bounds);
    if (direction == ORIENTATION_LEFT_TO_RIGHT
            || direction == ORIENTATION_RIGHT_TO_LEFT) {
        setMeasuredDimension(measureHeight(widthMeasureSpec),
                measureWidth(heightMeasureSpec));
    } else if (direction == ORIENTATION_UP_TO_DOWN
            || direction == ORIENTATION_DOWN_TO_UP) {
        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) {
        result = specSize;
    } else {
        result = text_bounds.height() + getPaddingTop()
                + getPaddingBottom();
        // result = text_bounds.height();
        if (specMode == MeasureSpec.AT_MOST) {
            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);

    if (specMode == MeasureSpec.EXACTLY) {
        result = specSize;
    } else {
        result = text_bounds.width() + getPaddingLeft() + getPaddingRight();
        if (specMode == MeasureSpec.AT_MOST) {
            result = Math.min(result, specSize);
        }
    }
    return result;
}

@Override
protected void onDraw(Canvas canvas) {

    canvas.save();

    int startX = 0;
    int startY = 0;
    int stopX = 0;
    int stopY = 0;
    Path path = new Path();
    if (direction == ORIENTATION_UP_TO_DOWN) {
        startX = (getWidth() - text_bounds.height() >> 1);
        startY = (getHeight() - text_bounds.width() >> 1);
        stopX = (getWidth() - text_bounds.height() >> 1);
        stopY = (getHeight() + text_bounds.width() >> 1);
        path.moveTo(startX, startY);
        path.lineTo(stopX, stopY);
    } else if (direction == ORIENTATION_DOWN_TO_UP) {
        startX = (getWidth() + text_bounds.height() >> 1);
        startY = (getHeight() + text_bounds.width() >> 1);
        stopX = (getWidth() + text_bounds.height() >> 1);
        stopY = (getHeight() - text_bounds.width() >> 1);
        path.moveTo(startX, startY);
        path.lineTo(stopX, stopY);
    } else if (direction == ORIENTATION_LEFT_TO_RIGHT) {
        startX = (getWidth() - text_bounds.width() >> 1);
        startY = (getHeight() + text_bounds.height() >> 1);
        stopX = (getWidth() + text_bounds.width() >> 1);
        stopY = (getHeight() + text_bounds.height() >> 1);
        path.moveTo(startX, startY);
        path.lineTo(stopX, stopY);
    } else if (direction == ORIENTATION_RIGHT_TO_LEFT) {
        startX = (getWidth() + text_bounds.width() >> 1);
        startY = (getHeight() - text_bounds.height() >> 1);
        stopX = (getWidth() - text_bounds.width() >> 1);
        stopY = (getHeight() - text_bounds.height() >> 1);
        path.moveTo(startX, startY);
        path.lineTo(stopX, stopY);
    }

    this.getPaint().setColor(this.getCurrentTextColor());
    canvas.drawTextOnPath(getText().toString(), path, 0, 0, this.getPaint());

    canvas.restore();
}
}
Redeemer answered 16/5, 2023 at 7:11 Comment(0)
A
-1

I create a bitmap to draw 90° text, You can try it: https://github.com/xueluwei1/VerticalLandscapeTextView

Alula answered 7/4 at 1:29 Comment(3)
Welcome to Stack Overflow! A link to a solution is welcome, but please ensure your answer is useful without it. Add context around the link so your fellow users will have some idea what it is and why it is there, then quote the most relevant part of the page you are linking to in case the target page is unavailable. Answers that are little more than a link may be deleted.. See also, Your answer is in another castle: when is an answer not an answer?.Prime
@Prime technically it could be said that this is not link-only. though I agree some inlining would be nice.Schooner
@Schooner Agreed - I'm not sure it's "link only", but it would likely need some additional info from the link source in order to be a useful answer, so I went with the "normal" recommendation to add context.Prime

© 2022 - 2024 — McMap. All rights reserved.