After extending the ScrollView class I was able to easily be notified of the scrolling in realtime.
Now I need to capture the content of this scrollview in a very specific part.
Let's say I want to capture the top of the screen (matching parent width and a defined height, like 100dp). But only the content of the ScrollView and not the rest, if there is anything else on the top but not as part of the ScrollView.
I tried using on the scrollview :
setDrawingCacheEnabled(true);
getDrawingCache(true);
setDrawingCacheEnabled(false);
Then I tried to crop so that I get the part I want :
Bitmap.createBitmap(complete, 0, 0, width, height);
Results are very far from what I want to achieve and performance are very very poor and at some point I would get either a SIGENV or getDrawingCache(true) tries to use a recycled bitmap...
So how can I easily capture the content in the desired area without too much performance hit ?
Note: this process must be done as I am scrolling the content, so inside ScrollView's onScrollChanged(final int x, final int y).
Thanks !
Since the problem was fun I implemented it, it seems to work fine. I guess that you are recreating a Bitmap each time that's why goes slow.
The idea is like this, you create an area in the ScrollView that you want to copy (see Rect cropRect and Bitmap screenshotBitmap), it's full width and you just need to set the height. The view automatically set a scroll listener on itself and on every scroll it will copy that area. Note that setDrawingCacheEanbled(true) is called just once when the view is instantiated, it basically tells the view that you will call getDrawingCache(), which will return the Bitmap on which the view is drawing itself. It then copy the area of interest on screenshotBitmap and that's the Bitmap that you might want to use.
ScreenshottableScrollView.java
package com.example.lelloman.screenshottablescrollview;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.ViewTreeObserver;
import android.widget.ScrollView;
/**
* Created by lelloman on 16-2-16.
*/
public class ScreenshottableScrollView extends ScrollView implements ViewTreeObserver.OnScrollChangedListener {
public interface OnNewScreenshotListener {
void onNewScreenshot(Bitmap bitmap);
}
private Bitmap screenshotBitmap = null;
private Canvas screenshotCanvas = null;
private int screenshotHeightPx = 0;
private OnNewScreenshotListener listener = null;
private Rect cropRect;
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
public ScreenshottableScrollView(Context context) {
super(context);
init();
}
public ScreenshottableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ScreenshottableScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ScreenshottableScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init(){
setDrawingCacheEnabled(true);
getViewTreeObserver().addOnScrollChangedListener(this);
}
public void setOnNewScreenshotListener(OnNewScreenshotListener listener){
this.listener = listener;
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(screenshotHeightPx != 0)
makeScrenshotBitmap(w,h);
}
public void setScreenshotHeightPx(int q){
screenshotHeightPx = q;
makeScrenshotBitmap(getWidth(), getHeight());
}
private void makeScrenshotBitmap(int width, int height){
if(screenshotBitmap != null) screenshotBitmap.recycle();
if(width == 0 || height == 0) return;
screenshotBitmap = Bitmap.createBitmap(width, screenshotHeightPx, Bitmap.Config.ARGB_8888);
screenshotCanvas = new Canvas(screenshotBitmap);
cropRect = new Rect(0,0,width,screenshotHeightPx);
}
#Override
public void onScrollChanged() {
if(listener == null) return;
Bitmap bitmap = getDrawingCache();
screenshotCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
screenshotCanvas.drawBitmap(bitmap,cropRect, cropRect,paint);
listener.onNewScreenshot(screenshotBitmap);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.lelloman.screenshottablescrollview.MainActivity">
<com.example.lelloman.screenshottablescrollview.ScreenshottableScrollView
android:id="#+id/scrollView"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp">
<TextView
android:id="#+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.example.lelloman.screenshottablescrollview.ScreenshottableScrollView>
<View
android:layout_width="match_parent"
android:layout_height="20dp"
android:background="#ff000000"/>
<ImageView
android:id="#+id/imageView"
android:layout_width="match_parent"
android:layout_height="100dp" />
</LinearLayout>
MainActivity.java
package com.example.lelloman.screenshottablescrollview;
import android.graphics.Bitmap;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
StringBuilder builder = new StringBuilder();
Random random = new Random();
String AB = "abcdefghijklmnopqrstuvwxyz ";
for(int i=0;i<100;i++){
builder.append("\n\n"+Integer.toString(i)+"\n\n");
for(int j =0;j<1000;j++){
builder.append(AB.charAt(random.nextInt(AB.length())));
}
}
((TextView) findViewById(R.id.textView)).setText(builder.toString());
final ImageView imageView = (ImageView) findViewById(R.id.imageView);
ScreenshottableScrollView scrollView = (ScreenshottableScrollView) findViewById(R.id.scrollView);
scrollView.setScreenshotHeightPx((int) (getResources().getDisplayMetrics().density * 100));
scrollView.setOnNewScreenshotListener(new ScreenshottableScrollView.OnNewScreenshotListener() {
#Override
public void onNewScreenshot(Bitmap bitmap) {
Log.d("MainActivity","onNewScreenshot");
imageView.setImageBitmap(bitmap);
}
});
}
}
I'm trying to create a custom view with two TextViews inside a vertical LinearLayout, but am totally confused as to how it all works.
Currently nothing is appearing as I my onDraw method isn't being called. I think this is due to the fact that my view (the LinearLayout?) has a width and height of both 0.
I think I should be overwriting my onMeasure, but after trying setMeasuredDimension(100,100) this still isn't working.
I am trying to inflate an xml inside the view and use the two TextViews in that.
An explanation would also be great so I can hopefully get my head around how this all works.
Thanks
size_button.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="#+id/sizeButtonSizeText"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
<TextView
android:id="#+id/sizeButtonSlugText"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
</LinearLayout>
SizeButton.java
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View.MeasureSpec;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.nap.library.NapApplication;
import com.nap.library.R;
public class SizeButton extends LinearLayout {
private TextView mSize;
private TextView mSlug;
private String mSizeText;
private String mSlugText;
private Paint mPaint;
private boolean mSoldOut;
private Context mContext;
/*
public SizeButton(Context context) {
super(context);
setup();
}
public SizeButton(Context context, AttributeSet attrs) {
super(context, attrs);
setup();
}
*/
public SizeButton(Context context, String size, String slug) {
super(context);
mContext = context;
mSizeText = size;
mSlugText = slug;
setup();
}
public void setSoldOut(){
this.mSoldOut = true;
}
public boolean isSoldOut(){
return mSoldOut;
}
public void setSizeText(String size){
mSize.setText(size);
}
public void setSlugText(String slug){
mSlug.setText(slug);
}
public void setup(){
mPaint = new Paint();
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout buttonLayout = (LinearLayout) inflater.inflate(R.layout.size_button, null);
mSize = (TextView) buttonLayout.findViewById(R.id.sizeButtonSizeText);
mSize.setGravity(Gravity.CENTER);
mSize.setTextColor(Color.BLACK);
mSize.setText(mSizeText);
mSize.setTypeface(NapApplication.mPorter);
mSize.setWidth(10);
mSlug = (TextView) buttonLayout.findViewById(R.id.sizeButtonSlugText);
mSlug.setGravity(Gravity.CENTER);
mSlug.setTextColor(Color.BLACK);
mSlug.setText(mSlugText);
mSlug.setTypeface(NapApplication.mPorter);
invalidate();
requestLayout();
LinearLayout.LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
mSize.setLayoutParams(layoutParams);
mSlug.setLayoutParams(layoutParams);
Log.i("button","in setup");
Log.i("button","width = "+this.getWidth()+" height = "+this.getHeight());
Log.i("button","width = "+mSize.getWidth()+" sizeheight = "+mSize.getHeight());
Log.i("button","width = "+mSlug.getWidth()+" slugheight = "+mSlug.getHeight());
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.i("button","in onMeasure");
setMeasuredDimension(100,100);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.i("button","in ondraw");
}
}
ProductFragment.java - where the button is added to the fragment
for (int i = 0; i < mItem.getSizes().length; i++) {
final SizeButton sizeButton = new SizeButton(getActivity(),mItem.getSizes()[i],"hello");
// Each size button has a sku set as its tag
sizeButton.setTag(mItem.getSkus()[i]);
sizeButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if(mItem.getSizes().length > 1) {
boolean selected = !sizeButton.isSelected();
if (selected) {
mCurrentSku = (String) v.getTag();
} else {
mCurrentSku = null;
}
}
configureButtons();
}
});
Log.e("button","Adding button to view");
mSizesWrapper.addView(sizeButton);
}
As your are extending a ViewGroup, you should override dispatchDraw(), not onDraw()
I'm trying as a user to copy paste the content of an editText of my application.
Unfortunately it doesn't work and it seems like it's not a common issue.
I'm suspecting a dumb reason but still can't find the why. Just in case here is my xml code :
<?xml version="1.0" encoding="utf-8"?>
<com.adylitica.customlistview.CustomLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/layoutContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/default_enlightenment"
android:orientation="horizontal" >
<com.adylitica.components.EditTextNotes
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/note"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#null"
android:paddingBottom="25dp"
android:paddingTop="10dp"
android:textColor="#301e1b"
android:textSize="24dp" />
<TextView
android:id="#+id/time"
android:layout_width="82dp"
android:layout_height="65dp"
android:background="#drawable/circle_background"
android:gravity="center"
android:textColor="#drawable/color_text"
android:textSize="18dp" />
Here is the code of the component extending EditText :
package com.adylitica.components;
import com.adylitica.activity.R;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.EditText;
import android.widget.TextView;
/**
* Defines a custom EditText View that draws lines between each line of text that is displayed.
*/
public class EditTextNotes extends EditText {
private Context contextP;
private Rect mRect;
private Bitmap line;
public static int nbLines = 0;
public EditTextNotes(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
contextP = context;
line = BitmapFactory.decodeResource(contextP.getResources(), R.drawable.line_thin);
line = Bitmap.createBitmap(line, 0, 0, 2000, 1);
mRect = new Rect();
}
public EditTextNotes(Context context, AttributeSet attrs) {
super(context, attrs);
contextP = context;
line = BitmapFactory.decodeResource(contextP.getResources(), R.drawable.line_thin);
line = Bitmap.createBitmap(line, 0, 0, 2000, 1);
mRect = new Rect();
}
public EditTextNotes(Context context) {
super(context);
contextP = context;
line = BitmapFactory.decodeResource(contextP.getResources(), R.drawable.line_thin);
line = Bitmap.createBitmap(line, 0, 0, 2000, 1);
mRect = new Rect();
}
#Override
protected void onDraw(Canvas canvas) {
int count = getLineCount();
Rect r = mRect;
int baseline = 0;
int addSize = 0;
canvas.drawBitmap(line, 0, baseline, null);
for (int i = 0; i < count; i++) {
baseline = getLineBounds(i, r) + addSize;
canvas.drawBitmap(line, 0, baseline, null);
nbLines++;
}
super.onDraw(canvas);
}
}
P.S : otherwise the behavior of the editText is ok, like I can get the focus, write in ... I just can't select the whole text and copy it.
Having a problem with Android Custom components. Trying to draw an oval shape but nothing happening.
I have this line in layout xml file
<android.project.realtimedata.DemoView android:id="#+id/demoView"
android:layout_width="fill_parent" android:layout_height="wrap_content" />
Here is the code for my custom component class.
package android.project.realtimedata;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.util.AttributeSet;
import android.view.View;
public class DemoView extends View{
ShapeDrawable thisGauge = null;
public DemoView(Context context){
super(context);
init();
}
public DemoView(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
private void init(){
int x = 10;
int y = 10;
int width = 300;
int height = 50;
thisGauge = new ShapeDrawable(new OvalShape());
thisGauge.getPaint().setColor(0xff74AC23);
thisGauge.setBounds(x, y, x + width, y + height);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
thisGauge.draw(canvas);
}
}
I also have this line in onCreate method of Activity
demoView = (DemoView) findViewById(R.id.demoView);
Whenever I launch the application the custom component is not there.
I tried looking at it from LogCat and it definitely gets created.
What am I missing here?
Thanks in advance.
Make sure that you calling findViewById(R.id.demoView) after calling setContentView(...). To ensure that your view is being inflated, you can call Log.d("DemoView", "Created") from inside your DemoView constructor.
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();
}
}