Sephy
Sephy

Reputation: 50392

Is it possible to write vertically in a textview in android?

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.

Upvotes: 41

Views: 72813

Answers (12)

薛鲁委
薛鲁委

Reputation: 1

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

Upvotes: -1

madhu527
madhu527

Reputation: 4772

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

Upvotes: 0

AHSSAN
AHSSAN

Reputation: 59

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

}

Upvotes: 0

xjcl
xjcl

Reputation: 15299

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.

Upvotes: 0

Amarjit
Amarjit

Reputation: 4357

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:text="xyz"
            android:rotation="-90"
            android:gravity="fill_vertical"/>

Upvotes: 22

Cameron A
Cameron A

Reputation: 159

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.

Upvotes: 0

ccheneson
ccheneson

Reputation: 49410

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

Upvotes: 44

Andrij
Andrij

Reputation: 228

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

Upvotes: 2

Ayaz Alifov
Ayaz Alifov

Reputation: 8588

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

Upvotes: 0

Yura Shinkarev
Yura Shinkarev

Reputation: 5354

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" />

Upvotes: 30

Chris623
Chris623

Reputation: 2532

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

Upvotes: 7

alexhilton
alexhilton

Reputation: 576

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

Upvotes: 8

Related Questions