I want to have something where half of the text in a textView or any other kind of text display is highlighted while the other half is not. For example
in "textView". the "text" would be in a red font and "view" would be in black.
I was thinking about doing this by putting 2 textViews directly on top of each other but I am not exactly sure how to do this. (What kind of parameters, etc?) Any ideas?
Thank you
You can stack multiple textViews by using a Layout that supports this, such as a FrameLayout or RelativeLayout. That said, I think the better approach (which would provide better control over the appearance) would be to create a custom view, and draw the text to the canvas yourself. You can do this with something like this (I have not tested this, but it should work - might need a few tweaks though):
public class TwoPartTextView extends View
{
private CharSequence part1 = "", part2 = "";
private Paint mPaint;
private int color1 = Color.BLACK, color2 = Color.BLACK;
private Context context;
private float part1Size = 12f, part2Size = 12f;
private int strokeWidth = 2;
public TwoPartTextView(Context context) {
super(context);
init(context);
}
public TwoPartTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public TwoPartTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(context) {
this.context = context;
mPaint = new Paint();
}
public void setText(CharSequence part1, int color1, part1Size, CharSequence part2, int color2, part2Size, strokeWidth) {
this.part1 = part1;
this.color1 = color1;
this.part1Size = part1Size;
this.part2 = part2;
this.color2 = color2;
this.part2Size = part2Size;
this.strokeWidth = strokeWidth;
mPaint.setStrokeWidth(strokeWidth);
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(color1);
mPaint.setTextSize(part1Size);
canvas.drawText(part1.toString(), 0, paint.getTextSize(), paint);
mPaint.setColor(color2);
mPaint.setTextSize(part2Size);
canvas.drawText(part2.toString(), getWidth()/2 /* tweak as needed*/, paint.getTextSize(), paint);
}
}
Then to use it, add it to XML or create it in code, then call:
myTwoPartTextView.setText("text", Color.RED, 12f, "View", Color.BLACK, 14f, 2);
myTwoPartTextView.setText.invalidate();
Related
I need an advise on optimizing a custom indicator, that shows progress of downloading file in multiple chunks, in concurrent threads. I couldn't find a correct name for that type - pieces, fragments, chunks? But it should look like bittorrent progress bar or defrag progress from Win XP. And it looks like this:
My custom ProgressBar class as following:
public class FragmentedProgressBar extends ProgressBar {
private int height;
private float fragWidth;
private final ArrayList<Integer> stateColors = new ArrayList<>();
private final Paint progressPaint = new Paint();
private ConcurrentHashMap<Integer, Integer> barData;
public FragmentedProgressBar(Context context) {
super(context);
this.init(context);
}
public FragmentedProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
this.init(context);
}
public FragmentedProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.init(context);
}
#SuppressWarnings("deprecation")
private void init(Context context) {
barData = new ConcurrentHashMap<>();
stateColors.addAll(
Arrays.asList(
context.getResources().getColor(android.R.color.holo_blue_dark),
context.getResources().getColor(android.R.color.holo_green_light),
context.getResources().getColor(android.R.color.holo_orange_light),
context.getResources().getColor(android.R.color.holo_red_light)
)
);
}
public synchronized void setProgress(int progress, int state) {
/* state serves to indicate "started", "ready", "retry", "error" by color */
if(barData != null ) {
barData.put(progress, state);
}
super.setProgress(progress);
}
#Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
height = getMeasuredHeight();
setMeasuredDimension(width, height);
fragWidth = (float) width / getMax();
}
#Override
protected synchronized void onDraw(Canvas canvas) {
for (Map.Entry<Integer, Integer> ent : barData.entrySet()) {
int id = ent.getKey();
int state = ent.getValue();
float xleft = fragWidth * ( id - 1 );
progressPaint.setColor(stateColors.get(state));
canvas.drawRect(xleft, 0.0f, xleft + fragWidth, 0.0f + height, progressPaint);
}
}
}
However, in this approach, it redraws whole bar on every progress tick, and, I think, it's quite inefficient.
I've done formerly same bar in javafx, extending Canvas and drawing each chunk separately on it.
What will be a better solution for android, desirably extending and reusing the ProgressBar class?
Thanks
I have added a seekbar to one of my activities.
Its max value is 5. Now, I want to display the divider values (with increment 1, like 0, 1, 2, 3, 4 and 5) below my seekbar. How can I do that?
Is there any system method to achieve this which I am not able to put my hands on? Any inputs are welcomed.
NOTE : I want to apply any changes programatically, not from xml. The numbers should be separated at equal intervals. I could not edit it that precisely though.
I am supposing you want to display view like below in picture.
if that is the case you have to create your own customSeekbar like give code.
CustomSeekBar.java
public class CustomSeekBar extends SeekBar {
private Paint textPaint;
private Rect textBounds = new Rect();
private String text = "";
public CustomSeekBar(Context context) {
super(context);
textPaint = new Paint();
textPaint.setColor(Color.WHITE);
}
public CustomSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
textPaint = new Paint();
textPaint.setTypeface(Typeface.SANS_SERIF);
textPaint.setColor(Color.BLACK);
}
public CustomSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
textPaint = new Paint();
textPaint.setColor(Color.BLACK);
}
#Override
protected synchronized void onDraw(Canvas canvas) {
// First draw the regular progress bar, then custom draw our text
super.onDraw(canvas);
int progress = getProgress();
text = progress + "";
// Now get size of seek bar.
float width = getWidth();
float height = getHeight();
// Set text size.
textPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
textPaint.setTextSize(40);
// Get size of text.
textPaint.getTextBounds(text, 0, text.length(), textBounds);
// Calculate where to start printing text.
float position = (width / getMax()) * getProgress();
// Get start and end points of where text will be printed.
float textXStart = position - textBounds.centerX();
float textXEnd = position + textBounds.centerX();
// Check does not start drawing outside seek bar.
if (textXStart <= 1) textXStart = 20;
if (textXEnd > width) {
textXStart -= (textXEnd - width + 30);
}
// Calculate y text print position.
float yPosition = height;
canvas.drawText(text, textXStart, yPosition, textPaint);
}
public synchronized void setTextColor(int color) {
super.drawableStateChanged();
textPaint.setColor(color);
drawableStateChanged();
}
}
In your Xml file use your custom file like below
<com.waleedsarwar.customseekbar.CustomSeekBar
android:id="#+id/seekbar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:max="5"
android:paddingBottom="16dp" />
This is another approach. I am extending a linearlayout. I put seekbar and another linearlayout(layout_5) which contains 6 textviews with 0-1-2-3-4-5. Better option would be creating a dynamic image(get width from seekBar) which has these numbers according to segment count.
I force seekbar's indicator to stop at specific points(6 points in your case). Instead of doing this, it is possible to set seekBar's maximum progress value to 5. It will work, but it will not give a good user experience.
public class SegmentedSeekBar extends LinearLayout {
private int[] preDefinedValues;
private int currentProgressIndex;
private SeekBar seekBar;
private int segmentCount = 5:
public SegmentedSeekBar(Context context) {
this(context, null, 0);
}
public SegmentedSeekBar(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.seekBarStyle);
}
public SegmentedSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.SegmentedSeekBar,
0, 0);
try {
segmentCount =
a.getInt(R.styleable.SegmentedSeekBar_segmentCount, -1);
} finally {
a.recycle();
}
init();
}
public void init() {
//this values will be used when you need to set progress
preDefinedValues = new int[segmentCount];
for(int i = 0; i < preDefinedValues.length; i++) {
preDefinedValues[i] = (100/(segmentCount-1)) * i;
}
//Get layout_5
//which is linearlayout with 6 textviews
LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View sliderView = inflater.inflate(
getSliderId(segmentCount), null);
//seekbar already inside the linearlayout
seekBar = (SeekBar)sliderView.findViewById(R.id.seek_bar);
//linear layout is vertically align
//so add your 6 textview linearlayout
addView(sliderView);
seekBar.setOnTouchListener(seekBarTouchListener);
}
private int getSliderId(int size) {
return R.layout.layout_5;
}
//this method sets progress which is seen in UI not actual progress
//It uses the conversion that we did in beginning
public synchronized void setProgress(int progress) {
if(preDefinedValues != null && progress < preDefinedValues.length && progress >= 0) {
seekBar.setProgress(preDefinedValues[progress]);
currentProgressIndex = progress;
}
}
//this listener make sure the right progress is seen in ui
//take action when user finish with changing progress
SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
int index = 0;
for(int i = 0; i < preDefinedValues.length; i++) {
//try to find closest preDefinedvalues by comparing with latest value
if(Math.abs(seekBar.getProgress() - preDefinedValues[i]) < Math.abs(seekBar.getProgress() - preDefinedValues[index])) {
index = i;
}
}
setProgress(index);
}
};
}
What I want to do? (blue will be changed as white)
What I did?
I have found a class which extends TextView that able to outline textview very close to what I want. The problem is that I could not change stroke color to any color, it draws always as black. How to set border color as white?
What is my output:
Where are my codes?
public class TypeFaceTextView extends TextView {
private static Paint getWhiteBorderPaint(){
Paint p = new Paint(Color.WHITE);
return p;
}
private static final Paint BLACK_BORDER_PAINT = getWhiteBorderPaint();
static {
BLACK_BORDER_PAINT.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
}
#Override
public void setText(CharSequence text, BufferType type) {
super.setText(String.format(text.toString()), type);
}
private static final int BORDER_WIDTH = 1;
private Typeface typeface;
public TypeFaceTextView(Context context) {
super(context);
}
public TypeFaceTextView(Context context, AttributeSet attrs) {
super(context, attrs);
setDrawingCacheEnabled(false);
setTypeface(attrs);
}
private void setTypeface(AttributeSet attrs) {
final String typefaceFileName = attrs.getAttributeValue(null, "typeface");
if (typefaceFileName != null) {
typeface = Typeface.createFromAsset(getContext().getAssets(), typefaceFileName);
}
setTypeface(typeface);
}
public TypeFaceTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setTypeface(attrs);
}
#Override
public void draw(Canvas aCanvas) {
aCanvas.saveLayer(null, BLACK_BORDER_PAINT, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
| Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.MATRIX_SAVE_FLAG);
drawBackground(aCanvas, -BORDER_WIDTH, -BORDER_WIDTH);
drawBackground(aCanvas, BORDER_WIDTH + BORDER_WIDTH, 0);
drawBackground(aCanvas, 0, BORDER_WIDTH + BORDER_WIDTH);
drawBackground(aCanvas, -BORDER_WIDTH - BORDER_WIDTH, 0);
aCanvas.restore();
super.draw(aCanvas);
}
private void drawBackground(Canvas aCanvas, int aDX, int aDY) {
aCanvas.translate(aDX, aDY);
super.draw(aCanvas);
}
}
I found simple way to outline view without inheritance from TextView.
I had wrote simple library that use Android's Spannable for outlining text.
This solution gives possibility to outline only part of text.
Library: OutlineSpan
Class (you can copy only class):OutlineSpan
1) create your textview object extends TextView
public class YourTextView extends TextView { .........
2) Do this on its draw method
#Override
public void draw(Canvas canvas) {
for (int i = 0; i < 5; i++) {
super.draw(canvas);
}
}
3) set textview's xml side as below
android:shadowColor="#color/white"
android:shadowRadius="5"
My solution based on custom TextView.
public class TextViewOutline extends TextView{
int outline_color;
float outline_width; //relative to font size
public TextViewOutline( Context context, int outline_color, float outline_width ){
super( context );
this.outline_color = outline_color;
this.outline_width = outline_width;
}
#Override
protected void onDraw( Canvas canvas) {
//draw standard text
super.onDraw( canvas );
//draw outline
Paint paint = getPaint();
paint.setStyle( Paint.Style.STROKE );
paint.setStrokeWidth( paint.getTextSize()*outline_width );
int color_tmp = paint.getColor();
setTextColor( outline_color );
super.onDraw( canvas );
//restore
setTextColor( color_tmp );
paint.setStyle( Paint.Style.FILL );
}
}
Code for test.
TextViewOutline tv = new TextViewOutline( this, 0xff0080ff, 0.04f);
tv.setText( "Simple TEST" );
tv.setTypeface( Typeface.create( Typeface.SERIF, Typeface.BOLD ) );
tv.setTextColor( 0xff000000 );
tv.setTextSize( 128 );
setContentView(tv);
tv.setOnClickListener( new View.OnClickListener(){
#Override
public void onClick( View view ){
view.invalidate();
}
} );
Result.
Couldn't this this but try experimenting with: PorterDuff.Mode
http://developer.android.com/reference/android/graphics/PorterDuff.Mode.html
Try changing it to 'ADD' or 'CLEAR', hope this helps.
You need to change your getWhiteBorderPaint() method to the following:
private static Paint getWhiteBorderPaint(){
Paint p = new Paint();
p.setColor(Color.WHITE);
return p;
}
The Paint constructor only takes bitmasked flags and doesn't support arbitrary ints as parameters.
Investigated into the original problem stated by this question. Found the solution.
First, change DST_OUT to DARKEN
static {
BLACK_BORDER_PAINT.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DARKEN));
}
Secondly, save the original text color, and put the intended outline color up, draw the outline, and then restore the original text color.
#Override
public void draw(Canvas aCanvas) {
int originalColor = this.getCurrentTextColor();
this.setTextColor(0xff000000); //set it to white.
aCanvas.saveLayer(null, borderPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
| Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.MATRIX_SAVE_FLAG);
drawBackground(aCanvas, -BORDER_WIDTH, -BORDER_WIDTH);
drawBackground(aCanvas, BORDER_WIDTH + BORDER_WIDTH, 0);
drawBackground(aCanvas, 0, BORDER_WIDTH + BORDER_WIDTH);
drawBackground(aCanvas, -BORDER_WIDTH - BORDER_WIDTH, 0);
this.setTextColor(originalColor);
aCanvas.restore();
super.draw(aCanvas);
}
text outline with transparent background
this is one way to do it with no background color
public class CustomTextView extends androidx.appcompat.widget.AppCompatTextView {
float mStroke;
public CustomTextView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CustomTextView);
mStroke=a.getFloat(R.styleable.CustomTextView_stroke,1.0f);
a.recycle();
}
#Override
protected void onDraw(Canvas canvas) {
TextPaint paint = this.getPaint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(mStroke);
super.onDraw(canvas);
}
}
then you only need to add the following to the attrs.xml file
<declare-styleable name="CustomTextView">
<attr name="stroke" format="float"/>
</declare-styleable>
and now you will be able to set the stroke widht by app:stroke while retaining all other desirable properties of TextView. my solution only draws the stroke w/o a fill. this makes it a bit simpler than the others. bellow a screencapture with the result while setting a custom font to my customtextview on a dark background.
I facing problem to draw rectangle at subclass of my android custom view class. Each time super class onDraw method works.But subclass onDraw method never executed. Super class will draw a rectangle and subclass will draw 4 rectangle within the super-class drawn rectangle.I can't fixed this problem.please help me.
Here is my sample code.
SuperClass:
public class ColorFanView extends View{
public ShapeDrawable[] mDrawables;
public ColorFanView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ColorFanView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ColorFanView(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvasObject) {
super.onDraw(canvasObject);
int x = 100;
int y = 100;
int width = 80;
int height = 200;
Paint thePaint = new Paint();
thePaint.setColor(Color.WHITE);
RectF rectnagle1 = new RectF(x,y,x+width,y+height);
canvasObject.drawRoundRect(rectnagle1, 10.0f, 10.0f, thePaint);
}
}
Subclass:
public class ColorFanStack extends ColorFanView{
public ColorFanStack(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
public ColorFanStack(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public ColorFanStack(Context context) {
super(context);
initView();
}
public void initView() {
mDrawables = new ShapeDrawable[4];
float[] outerR1 = new float[] { 12, 12, 12, 12, 0, 0, 0, 0 };
mDrawables[0] = new ShapeDrawable(new RoundRectShape(outerR1, null, null));
mDrawables[0].getPaint().setColor(Color.RED);
mDrawables[1] = new ShapeDrawable(new RectShape());
mDrawables[1].getPaint().setColor(Color.WHITE);
mDrawables[2] = new ShapeDrawable(new RectShape());
mDrawables[2].getPaint().setColor(Color.BLUE);
mDrawables[3] = new ShapeDrawable(new RectShape());
mDrawables[3].getPaint().setColor(Color.YELLOW);
}
#Override
protected void onDraw(Canvas canvasObj) {
super.onDraw(canvasObj);
int x = 100;
int y = 100;
int width = 80;
int height = 40;
int canvasSpace =5;
for (Drawable dr : mDrawables) {
dr.setBounds(x, y, x + width, y + height);
dr.draw(canvasObj);
y += height + canvasSpace;
}
}
}
XML
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/myViewGroup" android:orientation="vertical"
android:layout_width="fill_parent" android:layout_height="fill_parent">
<com.test.colorfan.ColorFanView
android:layout_width="200dip" android:layout_height="400dip"
android:id="#+id/firstView" />
</RelativeLayout>
Please help me regarding this issue. Hopefully, I will get a reply soon.
My guess is that your layout (please edit the question to include your layout), is defining your ColorFanView instances in such a way that they have 0 height or width; therefore, the parent View does not draw them.
EDIT 7/27/2011: Habibur Rahman added his layout XML to the question. This is the new answer:
Your two classes work, but you added the wrong one to your layout (you should have used ColorFanStack instead of ColorFanView). An instance of ColorFanStack will inherit the drawing of ColorFanView (by virtue of the fact that your ColorFanStack.onDraw() method calls super.onDraw()). I think that that was the behavior that you were trying to achieve.
Here is the XML that I used with the classes as you defined them:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.habecker.demo.ColorFanStack
android:layout_width="200dip" android:layout_height="400dip"
android:id="#+id/secondView" />
</RelativeLayout>
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.
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);
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" />
<TextView
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:text="xyz"
android:rotation="-90"
android:gravity="fill_vertical"/>
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);
}
}
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
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 of course you have to modify this code to make it works for you. (in attributes like android:layout_width, android:layout_height, etc.)
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);
}
}
}
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.
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)
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(...)
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)
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.
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();
}
}