5

I am in search of methodology for rotating a button. without using animations..!

I don't want use animations because of this.

if any body has any idea please help me.

xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="fill_parent"
    android:layout_height="fill_parent" android:id="@+id/ll" android:gravity="center_vertical">
    <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content" android:text="@string/hello" />
    <com.mind.RotateButton android:layout_width="100dp"  android:gravity="center_vertical"
        android:layout_height="100dp" android:id="@+id/ll1">
    <Button android:layout_width="wrap_content"
    android:layout_height="wrap_content" android:text="Button" android:id="@+id/but" />
    </com.mind.RotateButton>
</LinearLayout>

code

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);      
        Button btn   = (Button) findViewById(R.id.but);     
        btn.setOnClickListener(new OnClickListener() {          
            @Override
            public void onClick(View v) {
                Toast.makeText(getApplicationContext(), "Hello", Toast.LENGTH_SHORT).show();                
            }
        });
    }

Thanks in advance.......!

Community
  • 1
  • 1
Noby
  • 6,562
  • 9
  • 40
  • 63
  • Touch hit check is performed in layout, not in button. To be able to catch touch event in rotated button you can subclass Layout and override it's dispatchTouchEvent(). It's not trivial but AFAIK there is no simple way to implement this. – Dmitry Ryadnenko Nov 08 '11 at 08:59
  • can you please explain me in more detail..? – Noby Nov 08 '11 at 09:02
  • Also you can take a look at Canvas.setMatrix(). Using this I guess it's possible to create a button that draws itself rotated. This approach thought requires implementing a layout that can draw rotating views and a view that can rotate itself. – Dmitry Ryadnenko Nov 08 '11 at 09:05
  • ok, thank you for your valuable suggestion. I will try this approach. – Noby Nov 08 '11 at 09:07
  • Ok I'll try. Touch events come first to layout's dispatchTouchEvent(). In this method layout checks if touch event hits any of it's children and if so, passes this event to the child. This hit test is made using child rect, that was set earlier in onLayout() method. Animation doesn't change child rect, so if you want to make a hit test against a button rotated with animation, you have to implement you own hit test in dispatchTouchEvent(). You can grab code from android sources of ViewGroup understand that it does and change it. You can find android sources i.e. here http://goo.gl/IxAg9 – Dmitry Ryadnenko Nov 08 '11 at 09:21

2 Answers2

1

extend Button class:

public class RotateButton extends Button{

    public RotateButton(Context context) {
        super(context);
    }

    public RotateButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        canvas.rotate(45, getWidth() / 2, getHeight() / 2);
        super.onDraw(canvas);
        canvas.restore();
    }

}

and in your layout:

<com.samples.myapp.ui.RotateButton
        android:layout_height="wrap_content" android:id="@+id/MyBtn"
        android:padding="5dip" android:textColor="@color/darkGreen"
        android:textSize="16dip" android:text="TextView"
        android:layout_width="wrap_content"></com.samples.myapp.ui.RotateButton>

----------------------------------------------------------------------

edit:

Another approach: design a rotatable LinearLayout and put your controls in it. LinearLayout can be rotated completely:

package org.mabna.order.ui;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.view.MotionEvent;

public class RotateLinearLayout extends LinearLayout{

    private Matrix mForward = new Matrix();
    private Matrix mReverse = new Matrix();
    private float[] mTemp = new float[2];

    public RotateLinearLayout(Context context) {
        super(context);
    }

    public RotateLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {

        canvas.rotate(180, getWidth() / 2, getHeight() / 2);

        mForward = canvas.getMatrix();
        mForward.invert(mReverse);
        canvas.save();
        canvas.setMatrix(mForward); // This is the matrix we need to use for
                                    // proper positioning of touch events
        super.dispatchDraw(canvas);
        canvas.restore();
        invalidate();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        event.setLocation(getWidth() - event.getX(), getHeight() - event.getY());
        return super.dispatchTouchEvent(event);
    }
}
Bob
  • 22,810
  • 38
  • 143
  • 225
  • this is just tilting the text on button but not button itself..! Can you please tell me how to rotate button along with text. – Noby Nov 08 '11 at 09:46
  • This should also rotate button background. It doesn't? – Dmitry Ryadnenko Nov 08 '11 at 09:53
  • No its not rotating the background. only text is rotating. – Noby Nov 08 '11 at 10:13
  • I am very much thank full to you for your answer, but still i am facing same problem. – Noby Nov 08 '11 at 11:41
  • The `mReverse` matrix is computed but never used, so you better remove it. Also, the call to `invalidate()` causes the program to run into an infinite loop and quickly consume your battery. – Patrick Nov 15 '11 at 13:34
  • I have found a workaround public ViewParent invalidateChildInParent(int[] location, Rect dirty) { invalidate(); return super.invalidateChildInParent(location, dirty); } – Dmitry Ryadnenko Nov 16 '11 at 08:47
0

Using ideas from breceivemail answer I made rotate layout that actually works. It's designed to hold single view, it will dispatch touch events correctly but no padding or margins are supported in this realization. Also it's only supports angles like 90, 180, 270 etc. Layout size will just match the size of it's child after rotation.

public class RotateLayout extends ViewGroup {

    public static class LayoutParams extends ViewGroup.LayoutParams {

        public int angle;

        public LayoutParams(Context context, AttributeSet attrs) {
            super(context, attrs);
            final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RotateLayout_Layout);
            angle = a.getInt(R.styleable.RotateLayout_Layout_layout_angle, 0);
        }

        public LayoutParams(ViewGroup.LayoutParams layoutParams) {
            super(layoutParams);
        }

    }

    public RotateLayout(Context context) {
        super(context);
    }

    public RotateLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setWillNotDraw(false);
    }

    public View getView() {
        return view;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        view = getChildAt(0);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
        if(angle != layoutParams.angle) {
            angle = layoutParams.angle;
            angleChanged = true;
        }

        if(Math.abs(angle % 180) == 90) {
            measureChild(view, heightMeasureSpec, widthMeasureSpec);
            setMeasuredDimension(
                resolveSize(view.getMeasuredHeight(), widthMeasureSpec), 
                resolveSize(view.getMeasuredWidth(), heightMeasureSpec));
        }
        else {
            measureChild(view, widthMeasureSpec, heightMeasureSpec);
            setMeasuredDimension(
                resolveSize(view.getMeasuredWidth(), widthMeasureSpec), 
                resolveSize(view.getMeasuredHeight(), heightMeasureSpec));
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if(changed || angleChanged) {
            layoutRectF.set(0, 0, r - l, b - t);
            layoutTransitionMatrix.setRotate(angle, layoutRectF.centerX(), layoutRectF.centerY());
            layoutTransitionMatrix.mapRect(layoutRectFRotated, layoutRectF);
            layoutRectFRotated.round(viewRectRotated);
            angleChanged = false;
        }

        view.layout(viewRectRotated.left, viewRectRotated.top, viewRectRotated.right, viewRectRotated.bottom);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        canvas.save();
        canvas.rotate(-angle, getWidth() / 2f, getHeight() / 2f);
        super.dispatchDraw(canvas);
        canvas.restore();
    }

    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        invalidate();
        return super.invalidateChildInParent(location, dirty);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        touchPoint[0] = event.getX();
        touchPoint[1] = event.getY();

        layoutTransitionMatrix.mapPoints(childTouchPoint, touchPoint);
        event.setLocation(childTouchPoint[0], childTouchPoint[1]);
        return super.dispatchTouchEvent(event);
    }

    @Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new RotateLayout.LayoutParams(getContext(), attrs);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams layoutParams) {
        return layoutParams instanceof RotateLayout.LayoutParams;
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams layoutParams) {
        return new RotateLayout.LayoutParams(layoutParams);
    }

    private View view;
    private int angle;

    private final Matrix layoutTransitionMatrix = new Matrix();

    private final Rect viewRectRotated = new Rect();

    private final RectF layoutRectF = new RectF();
    private final RectF layoutRectFRotated = new RectF();

    private final float[] touchPoint = new float[2];
    private final float[] childTouchPoint = new float[2];

    private boolean angleChanged = true;

}

attrs.xml (add this file to res/values folder)

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="RotateLayout_Layout">
        <attr name="layout_angle" format="integer"  />
    </declare-styleable>

</resources>

Usage example:

<com.you.package.name.RotateLayout
    xmlns:app="http://schemas.android.com/apk/res/com.you.package.name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" >

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_angle="-90"
        android:text="Rotated Button"/>
</com.you.package.name.RotateLayout>
Dmitry Ryadnenko
  • 22,222
  • 4
  • 42
  • 56
  • : No resource identifier found for attribute 'layout_angle' in package 'com.you.package.name' this error i am getting. – Noby Nov 16 '11 at 09:43
  • 1. res/values folder 2. Are you kidding me? Doesn't "com.you.package.name" give you a clue what should be there? – Dmitry Ryadnenko Nov 16 '11 at 09:50
  • Sorry, i did a not changed the package name. – Noby Nov 16 '11 at 09:59
  • Its not working if i change the layout_angel to 45. Only text is rotating not the button. – Noby Nov 16 '11 at 10:01
  • It's only supports angles like 90, 180, 270 etc. It was enough for me. I'm sure it's possible to make this layout to support any angle though it requires nontrivial changes in onMeasure, dispatchTouchEvent, onLayout. – Dmitry Ryadnenko Nov 16 '11 at 10:10