I want to make a rotated and flipped EditText view that has all of the properties of a normal EditText view.
I have successfully made (with much help from SO users) a custom EditText view that is both rotated and flipped. This was done by overriding the onDraw method. However, the cursor and highlighting are gone and a longtouch event still indicates the original text position. Basically, the view was redrawn but the touch events were not.
How do I get the touch events, highlighting, and cursor to also be rotated and flipped?
EditText scale with selection (A similar problem but not quite the same.)
How to make a custom Edittext,so that it will look like as 45 degree rotated in android (@CommonsWare noted for one solution that addition work would need to be done with touch events. What is that work?)
http://developer.android.com/training/graphics/opengl/touch.html (Helpful, but I don't understand how to apply it in this situation.)
I made a custom view that extends EditText. In it overrode the onDraw method to rotate and flip the canvas. I overrode onMeasure to make the view have the right dimensions for the layout.
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Typeface;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.widget.EditText;
public class MongolEditText extends EditText {
// Constructors
public MongolEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public MongolEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MongolEditText(Context context) {
super(context);
init();
}
// This class requires the mirrored Mongolian font to be in the assets/fonts folder
private void init() {
Typeface tf = Typeface.createFromAsset(getContext().getAssets(),
"fonts/MongolChagaanMirrored.ttf");
setTypeface(tf);
}
@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();
canvas.translate(getWidth(), 0);
canvas.rotate(90);
canvas.translate(0, getWidth());
canvas.scale(1, -1);
canvas.translate(getCompoundPaddingLeft(), getExtendedPaddingTop());
getLayout().draw(canvas);
canvas.restore();
}
}
There is nothing special for the layout xml.
(Update) This question is another attempt at it but in the end I couldn't get it to work: Does invalidateDrawable() need to be overridden in addition to onDraw()?
In case you are wondering why in the world I want to rotate and flip an EditText view, here is the reason. Traditional Mongolian is written vertically in left to right columns. In combination with a vertically mirrored Mongolian font, rotating the text 90 degrees clockwise and flipping it produces readable output with correct line wrapping.
This is not an obscure or isolated problem. There are millions of users of traditional Mongolian but very few Android apps. Of these, I haven't found any that are open source. If I can get this to work, I want to make the code available to other developers.
I was thinking about creating a custom view (that extends View
) from scratch to create something like a TextView
. This TextView
could be updated from apps to act like an EditText
view. In this case I would only need to rotate the text 90 degrees with a normal font but not flip it. However, I would have to do my own line wrapping.
However, after reading @Chitrang's answer I think I can do something similar by just extending a TextView. Then I can avoid the trouble of doing my own line wrapping.
Picture Update
Mongolian is written from top to bottom and left to right. Right now I am using this key pad to move a cursor around the text, but I would like to be able to touch the screen to move the cursor to a position.
I ended up developing a vertical script MongolEditText
from scratch. It is available as a part of mongol-library
.
Here it is being used with two different third party keyboards.
This is still a work in progress so I won't mark this as solved yet, but let me post what I have so far. It does most of what I wanted to do. Basically, I'm using TextView rather than EditText because EditText was doing to many strange things when rotated.
I have an unblinking cursor that responds to touch events but highlighting is still not supported. Here is the code:
public class MongolTextView extends TextView {
private TextPaint textPaint;
private Paint cursorPaint = new Paint();
private boolean mCursorIsVisible;
private CursorTouchLocationListener listener;
// Naming is based on pre-rotated/mirrored values
private float mCursorBaseY;
private float mCursorBottomY;
private float mCursorAscentY; // This is a negative number
private float mCursorX;
private static final float CURSOR_THICKNESS = 2f;
// Constructors
public MongolTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public MongolTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MongolTextView(Context context) {
super(context);
init();
}
// This class requires the mirrored Mongolian font to be in the assets/fonts folder
private void init() {
//Typeface tf = Typeface.createFromAsset(getContext().getAssets(),
// "fonts/ChimeeWhiteMirrored.ttf");
//setTypeface(tf);
// Use the above commented code is using a single font in another application
Typeface tf = FontCache.get(SettingsActivity.FONT_DEFAULT, getContext());
if(tf != null) {
setTypeface(tf);
}
this.mCursorIsVisible = true;
cursorPaint.setStrokeWidth(CURSOR_THICKNESS);
cursorPaint.setColor(Color.BLACK); // TODO should be same as text color
}
// This interface may be deleted if touch functionality is not needed
public interface CursorTouchLocationListener {
/**
* Returns the touch location to be used for the cursor so you can update the insert
* location in a text string.
*
* @param glyphIndex
* You will need to translate glyphIndex into a Unicode index if you are using a
* Unicode string.
*/
public void onCursorTouchLocationChanged(int glyphIndex);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// swap the height and width
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
@Override
protected void onDraw(Canvas canvas) {
textPaint = getPaint();
textPaint.setColor(getCurrentTextColor());
textPaint.drawableState = getDrawableState();
canvas.save();
// flip and rotate the canvas
canvas.translate(getWidth(), 0);
canvas.rotate(90);
canvas.translate(0, getWidth());
canvas.scale(1, -1);
canvas.translate(getCompoundPaddingLeft(), getExtendedPaddingTop());
// draw the cursor
if (mCursorIsVisible) {
canvas.drawLine(mCursorX, mCursorBottomY, mCursorX, mCursorBaseY + mCursorAscentY,
cursorPaint);
}
getLayout().draw(canvas);
canvas.restore();
}
public void showCursor(boolean visible) {
mCursorIsVisible = visible;
this.invalidate();
// TODO make the cursor blink
}
public void setCursorColor(int color) {
cursorPaint.setColor(color);
}
public void setCursorLocation(int characterOffset) {
Layout layout = this.getLayout();
if (layout!=null){
try {
// This method is giving a lot of crashes so just surrounding with
// try catch for now
int line = layout.getLineForOffset(characterOffset);
mCursorX = layout.getPrimaryHorizontal(characterOffset);
mCursorBaseY = layout.getLineBaseline(line);
mCursorBottomY = layout.getLineBottom(line);
mCursorAscentY = layout.getLineAscent(line);
this.invalidate();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class InputWindowTouchListener implements OnTouchListener {
@Override
public boolean onTouch(View view, MotionEvent event) {
Layout layout = ((TextView) view).getLayout();
// swapping x and y for touch events
int y = (int) event.getX();
int x = (int) event.getY();
if (layout != null) {
int line = layout.getLineForVertical(y);
int offset = layout.getOffsetForHorizontal(line, x);
mCursorX = layout.getPrimaryHorizontal(offset);
mCursorBaseY = layout.getLineBaseline(line);
mCursorBottomY = layout.getLineBottom(line);
mCursorAscentY = layout.getLineAscent(line);
//mCursorHeightY = layout.getLineTop(line);
view.invalidate();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//handler.postDelayed(mLongPressed, 1000);
listener.onCursorTouchLocationChanged(offset);
break;
case MotionEvent.ACTION_UP:
//handler.removeCallbacks(mLongPressed);
// notify the host activity of the new cursor location
break;
}
}
return false;
}
}
public void setCursorTouchLocationListener(CursorTouchLocationListener listener) {
this.listener = listener;
}
}
Feel free to add your own answer if you have something better or make a comment if you have something to add for improving this (adding highlighting, getting the cursor to blink, etc.). The most recent version of this code should be on github.