6Answers
  • 7
name

A PHP Error was encountered

Severity: Notice

Message: Undefined index: userid

Filename: views/question.php

Line Number: 191

Backtrace:

File: /home/prodcxja/public_html/questions/application/views/question.php
Line: 191
Function: _error_handler

File: /home/prodcxja/public_html/questions/application/controllers/Questions.php
Line: 433
Function: view

File: /home/prodcxja/public_html/questions/index.php
Line: 315
Function: require_once

name Punditsdkoslkdosdkoskdo

Rotate View Hierarchy 90 degrees

I am working on a subclass of FrameLayout that is supposed to rotate all of its children by 90 degrees. I am doing this to overcome the landscape-only camera limitation present in android 2.1 and below, by having the activity be in landscape, but placing my camera overlay into this framelayout overlay to cause it to appear as if it was portrait (this is how Layar does it) To accomplish this, I'm adapting Jeff Sharkey's code to rotate views. My problem is that I can rotate the Framelayout, but I cannot resize it to match the new dimensions. So on my g1, instead of a 320x480 portrait view over a 480x320 camera view in landscape, I get a 320x320 box in the middle showing my portrait view with the sides chopped off.

Here is my code so far:

public class RotateLayout extends FrameLayout {
    private Matrix mForward = new Matrix();
    private Matrix mReverse = new Matrix();
    private float[] mTemp = new float[2];

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

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

    }


    /* (non-Javadoc)
     * @see android.widget.FrameLayout#onMeasure(int, int)
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //This didn't work:
        //super.onMeasure(heightMeasureSpec, widthMeasureSpec);
    }

    /* (non-Javadoc)
     * @see android.widget.FrameLayout#onSizeChanged(int, int, int, int)
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        canvas.rotate(270, getWidth()/2, getHeight()/2);
        //This code will stretch the canvas to accommodate the new screen size. This is not what I want.
        //float scaleX=(float)getHeight()/getWidth();
        //float scaleY=(float)getWidth()/getHeight();
        //canvas.scale(scaleX, scaleY,  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();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        final float[] temp = mTemp;
        temp[0] = event.getX();
        temp[1] = event.getY();

        mReverse.mapPoints(temp);

        event.setLocation(temp[0], temp[1]);
        return super.dispatchTouchEvent(event);
    }
}

I have tried overriding OnMeasure to switch the X and Y dimensions of the View, but have not been able to get that to work. Any help you can provide is greatly appreciated.

      • 1
    • Nope - the eventual solution was to use the above code as-is and just rotate the buttons, textviews, etc. individually by wrapping each one in one of these. It's not very elegant, but it works.
      • 1
    • I believe that inside dispatchTouchEvent() you should be using getRawX() and getRawY() instead of getX() and getY(). That works better for me, at any rate. My view is square, fwiw.
    • As Carl Manaster says, it needs to be getRawX & getRawY. Giving +1 to Carl as it solved my problem. Thanks.

I had the same problem and managed to solve it. Instead of rotating each view or the layout by hand, I used a LayoutAnimationController.

First, place a file in /res/anim/ called rotation.xml

<?xml version="1.0" encoding="utf-8"?>
<rotate
 xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:toDegrees="-90"
    android:pivotX="50%"
    android:pivotY="50%"
    android:duration="0" android:fillAfter="true">
</rotate>

Then, in your Activity's onCreate, do

  @Override
  public void onCreate(Bundle icicle) {
  super.onCreate(icicle);

     setContentView(R.layout.myscreen);

     Animation rotateAnim = AnimationUtils.loadAnimation(this, R.anim.rotation);
     LayoutAnimationController animController = new LayoutAnimationController(rotateAnim, 0);
     FrameLayout layout = (FrameLayout)findViewById(R.id.MyScreen_ContentLayout);
     layout.setLayoutAnimation(animController);
 }

If you want to rotate elements that lie above your camera preview view (SurfaceHolder), simply place a FrameLayout above the SurfaceHolder, place all your elements in that FrameLayout and call the Layout "MyScreen_ContentLayout". Done.

Hope that helped someone out, took me quite a while to get everything together.

  • 39
Reply Report
      • 2
    • This is an interesting solution. The only problem I encountered with it is that rotated layouts don't expand. So for instance, if you have a layout with 2 images - 1 being at the very top of the screen and 1 being at the very bottom. After rotating the layout, the distance between the 2 images will remain the same - rather than expanding to be at the top/bottom of the rotated view they will be somewhere in the middle.
      • 2
    • In your layout xml file, create a framelayout and place it so that it lies above the surfaceholder (the framelayout completely covers the surfaceholder).
      • 1
    • @georg what do you mean by above? What sort of layout would work to hold both the framelayout and the surfacelayout?

Using API level 11 and later you can use the method setRotation(degreesFloat); to change the rotation of a view programmatically, or you can use the XML attribute android:rotation="" to change it in your XML. There are also methods/attributes for changing only the X or Y values of a view's rotation: Android Docs - View (setRotation).

So nowadays as long as you're using API level 11 or above, you should be able to apply the rotation to a wrapper layout node. However, you probably will also have to change the dimensions of the top-level layout to match the dimensions you desire after the rotation. I.e. if you have a portrait view w/ dimensions 800x1280, you'll have to change them to 1280x800 in order for it to line up after rotating to landscape.

  • 7
Reply Report

This is what has worked for me in general.

private void init() {
    setRotation(90f);
}

public YourViewOrViewGroup(final Context context) {
    super(context);
    init();
}

... (all the View/ViewGroup constructors) ...

@Override
protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) {
    super.onLayout(changed, l, t, r, b);

    final int width = getWidth();
    final int height = getHeight();
    final int offset = Math.abs(width - height) / 2;
    setTranslationX(-offset);
    setTranslationY(offset);
}

@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
    super.onMeasure(heightMeasureSpec, widthMeasureSpec);
}

What you want to do is swap the width with height and then put the X & Y offsets so that the view becomes full screen after the rotation.

The above is a 'landscape'-rotated version. To achieve a landscape inverted just apply 270-deg rotation. You can either modify code within the snippet or apply the rotation outside in a more generic way, i.e

final YourViewOrViewGroup layout = inflater.inflate(...);
if (currentOrientation.isInverted()) {
    layout.setRotation(layout.getRotation + 180f);
}

this way you are able to embed the rotated View/ViewGroup within the xml definition and inflate 'port' and 'land' versions while the screen orientation changes, but this seems out of this topic.

Edit: actually it is much better to defer the offset setting until at least one layout pass is over. This is due the fact that in my case after first onMeasure() the view would be drawn (before the offsets were set). Eventually it could be experienced as glitching because the view/layout would not get drawn within the final bounds at first. (updated the snippet)

  • 1
Reply Report

I think you forgot one line in your onMeasure.

@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     super.onMeasure(heightMeasureSpec, widthMeasureSpec);
     setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}

Taken from the code for a vertical seekbar here: How can I get a working vertical SeekBar in Android?

You might need to fiddle with the getMeasuredHeight() to make it the correct size of your screen.

  • 0
Reply Report

Try turning off children clipping of your view root: call setClipChildren(false) on parent of your RotateLayout and in onMeasure method of your RotateLayout put these lines:

super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());

I'm having basically the same problem as you and I still haven't tested my solution - I'll do it tomorrow and tell if it is working correctly.

  • 0
Reply Report