10

I'm trying to rotate my subclass of TextView using canvas.rotate():

canvas.save();

final int w = getWidth();
final int h = getHeight();

float px = w/2f;
float py = h/2f;
canvas.rotate(mAngle, px, py);

super.draw(canvas);

canvas.restore();

TextView is rotated, however bounds of my view are clipped:

illustration

I understand that this is because of my view's size - it is not changed during rotation while it should. But if I'll change width\height in onMeasure problem will remain - I'm using LayoutParams.WRAP_CONTENT, so TextView just change it's size according to values provided in setMeasuredDimensions.

How can I solve this problem?

Dmitry Zaytsev
  • 23,650
  • 14
  • 92
  • 146
  • 2
    try to set android:clipChildren=false; and android:clipToPadding=false; to TextView's parent. – Leonidos Jan 18 '13 at 13:58
  • @Leonidos it causes strange artifacts while rotating. Also, I have to clip some views, so I can't just set `clipChildren=false`. – Dmitry Zaytsev Jan 18 '13 at 14:06

4 Answers4

4

I think that the entire problem here, is that you are using WRAP_CONTENT. The clip rect for the view, when you do that, is the size of the text content. The simplest way to fix the problem is to use padding. Something like this, works fine for me:

<com.example.twistedtext.TwistedTextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="30dp"
    android:gravity="center"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    twistedtext:rotation="30.0f"
    android:text="@string/hello_world" />

Of course, if you do it this way, you'll have to choose a slightly different padding for each content. If you can't do that, override onMeasure, so that does exactly what TextView's onMeasure does, then adds corresponding padding, as necessary for the rotation.

Added later: Actually, this was kinda fun to figure out. I have the following onMeasure, that works pretty well:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int w = getMeasuredWidth();
    int h = getMeasuredHeight();
    w = (int) Math.round(w * cosA + h * sinA);
    h = (int) Math.round(h * cosA + w * sinA);
    setMeasuredDimension(w, h);
}

The only remaining problem is that the text gets wrapped according to the pre-rotation dimensions. You'd have to fix that too...

sinA and cosA are computed when mAngle is set.

G. Blake Meike
  • 6,615
  • 3
  • 24
  • 40
2

Ok, if you cant rotate textView in your current view hierarch, you should animate it in another one.

Create a bitmap represantation of your textView. Use a holder for this bitmap like ImageView. Connect the imageView to another layout which is on top and where it won't be cropped. Hide your textView. Run animation on ImageView. When animation finished, update textView representation, show textView and remove ImageView.

I cant find the exact code example of what you want, but take a look at this one. The interecting place is:

mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWindowManager.addView(YOUR_VIEW_WITH_ANIMATION, mWindowParams);
Leonidos
  • 10,482
  • 2
  • 28
  • 37
2

At the first look solution provided by G. Blake Meike is a good way to start, however there is a lot of problems upcome.

To overcome problems there is an another way. It's quite simple, but generally shouldn't be used (since I've just deleted unneccesary for me stuff from original android source). To take control over drawing of concrete View you can override drawChild method of it's parent (yes, you need your own subclass of ViewGroup or something more concrete like FrameLayout). Here is an example of my solution:

@Override
protected boolean drawChild(Canvas canvas, View child, long time) {
    //TextBaloon - is view that I'm trying to rotate
    if(!(child instanceof TextBaloon)) {
        return super.drawChild(canvas, child, time);
    }

    final int width = child.getWidth();
    final int height = child.getHeight();

    final int left = child.getLeft();
    final int top = child.getTop();
    final int right = left + width;
    final int bottom = top + height;

    int restoreTo = canvas.save();

    canvas.translate(left, top);

    invalidate(left - width, top - height, right + width, bottom + height);
    child.draw(canvas);

    canvas.restoreToCount(restoreTo);

    return true;
}

Main idea is that I'm granting non-clipped canvas to child.

Dmitry Zaytsev
  • 23,650
  • 14
  • 92
  • 146
  • 1
    Ah! I've been robbed! That's not a solution, that's a hack! Oh well, it was fun working it out. – G. Blake Meike Jan 21 '13 at 17:04
  • @G.BlakeMeike not exactly a hack :) This is a whole point of `drawChild` method - take control over drawing. However, your answer will eventually lead to more "correct" solution, so I'll give you a bounty ) – Dmitry Zaytsev Jan 22 '13 at 06:00
  • Much appreciated Dmitry. It turns out that the method that you need to override, to fix the text layout, TextView.makeNewLayout, is protected but @hide. It is going to be hard to keep the text from being re-wrapped, based on the new bounds. Bummer. – G. Blake Meike Jan 22 '13 at 16:22
  • Wait I think I just threw up in my mouth. So you cannot rotate an ImageView without it clipping unless you rotate about the center point. There must be a good reason but it just seems dumb. I don't want to override onDraw, add padding or any other such foolishness. C'mon Android just rotate the bitmap and move/resize as necessary on the parent view! – Batdude Nov 08 '13 at 17:11
  • I wanted to rotate an ImageView without clipping and this is the only solution that worked for me. It's incredible. Android should be easier. – Ferran Maylinch Mar 15 '14 at 04:13
1

You could check out https://github.com/grantland/android-autofittextview Especially its refitText() method.

Rotationg your Textview should after all result in the invocation of onMeasure() method, where all the magic begins.

AndacAydin
  • 1,416
  • 19
  • 26