How to rotate views on orientation change without recreating layout?

13,842

Solution 1

Try to use OrientationEventListener. You don't need to use onConfigurationChanged and android:configChanges="orientation|keyboardHidden|screenSize".

You need set android:screenOrientation="portrait" for the activity in AndroidManifest.xml. Here is my solution with OrientationEventListener:

public class MyActivity extends Activity{

private ImageButton menuButton;

private Animation toLandAnim, toPortAnim;
private OrientationListener orientationListener;

@Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.layout_image_ruler);

    menuButton=(ImageButton)findViewById(R.id.menu_button);
    toLandAnim= AnimationUtils.loadAnimation(this, R.anim.menubutton_to_landscape);
    toPortAnim= AnimationUtils.loadAnimation(this, R.anim.menubutton_to_portrait);

    orientationListener = new OrientationListener(this);
}

@Override protected void onStart() {
    orientationListener.enable();
    super.onStart();
}

@Override protected void onStop() {
    orientationListener.disable();
    super.onStop();
}

private class OrientationListener extends OrientationEventListener{
    final int ROTATION_O    = 1;
    final int ROTATION_90   = 2;
    final int ROTATION_180  = 3;
    final int ROTATION_270  = 4;

    private int rotation = 0;
    public OrientationListener(Context context) { super(context); }

    @Override public void onOrientationChanged(int orientation) {
        if( (orientation < 35 || orientation > 325) && rotation!= ROTATION_O){ // PORTRAIT
            rotation = ROTATION_O;
            menuButton.startAnimation(toPortAnim);
        }
        else if( orientation > 145 && orientation < 215 && rotation!=ROTATION_180){ // REVERSE PORTRAIT
            rotation = ROTATION_180;
            menuButton.startAnimation(toPortAnim);
        }
        else if(orientation > 55 && orientation < 125 && rotation!=ROTATION_270){ // REVERSE LANDSCAPE
            rotation = ROTATION_270;
            menuButton.startAnimation(toLandAnim);
        }
        else if(orientation > 235 && orientation < 305 && rotation!=ROTATION_90){ //LANDSCAPE
            rotation = ROTATION_90;
            menuButton.startAnimation(toLandAnim);
        }
    }
}
}

This also prevents from too frequent rotations when orientation is about 45, 135... etc. Hope it helps.

Solution 2

The basics are actually a lot easier. Have a look at Handling Runtime Changes.

First things first, by setting

android:configChanges="orientation|keyboardHidden|screenSize"

in your Manifest on your activity tag you can handle the orientation change yourself. (orientation should be enough, but there are sometimes issues where the event does not fire with that alone.)

You then skip onCreate and instead onConfigurationChanged gets called. Overwrite this method and apply your layout changes here. Whether you change your linearLayouts orientation here or have a custom view handling layout for different screens itself is up to you and depends on your implementation.

Animating will be a bit trickier, if it is even possilbe. A quick search says it is not.


Update for comment "I only want to rotate some views themselves rather than rotating the layout"

In theory it is possible to create your own layout and handle the drawing of your child views. I just tried it but could not produce any results in an appropriate time, but what you would need to do:

  • keep your last measured values use tags on the view or similar approaches to keep the last measurements and layouts, so that after the orientation change you can diff
  • await orientation change: trigger rotated drawing - rotate the canvas, layout the views with the previous dimensions, and draw the child views where they would have been before, and
  • start an animation interpolate from the last to the new values, rotating the canvas from the last to the new layout

This is how I would do it.

Share:
13,842

Related videos on Youtube

Hussein El Feky
Author by

Hussein El Feky

I'm a 22 years old Android Developer with 2,000,000+ installs on Google Play. I developed and published my first Android app when I was 15. As an Android Enthusiast, I'm always passionate about learning new technologies related to Android. If you would like to learn more about me, you can check my personal website. My Apps on Google Play: Typing Master Character Pad GPA Calculator Items Counter Be Like Bill If you want to contact me, email me at [email protected] #SOreadytohelp

Updated on September 15, 2022

Comments

  • Hussein El Feky
    Hussein El Feky over 1 year

    This question has been asked before here but its answer is incorrect. I would like to rotate some views on screen orientation change, but I want to keep the layout unchanged. They should be rotated 90, 180, 270 or 360 degrees according to the current orientation (SCREEN_ORIENTATION_LANDSCAPE, SCREEN_ORIENTATION_PORTRAIT, SCREEN_ORIENTATION_REVERSE_LANDSCAPE, SCREEN_ORIENTATION_REVERSE_PORTRAIT).

    This is what I want to achieve:

    LayoutExample

    The answer in the link I mentioned stated that I should create a new different layout in layout-land. Clearly, this is not what I want. I don't want to recreate the activity or change layout orientation. I only want to rotate some views, and keep other views unchanged on orientation change.

    There is a huge difference between rotating specific views and changing or recreating the whole layout (both on orientation change).

    Using the answer in this link, I will be able to get the current screen orientation with this method:

    public static int getScreenOrientation(Context context) {
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        int rotation = windowManager.getDefaultDisplay().getRotation();
        DisplayMetrics dm = new DisplayMetrics();
        windowManager.getDefaultDisplay().getMetrics(dm);
        int width = dm.widthPixels;
        int height = dm.heightPixels;
        int orientation;
        // if the device's natural orientation is portrait:
        if ((rotation == Surface.ROTATION_0
                || rotation == Surface.ROTATION_180) && height > width ||
                (rotation == Surface.ROTATION_90
                        || rotation == Surface.ROTATION_270) && width > height) {
            switch (rotation) {
                case Surface.ROTATION_0:
                    orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
                    break;
                case Surface.ROTATION_90:
                    orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
                    break;
                case Surface.ROTATION_180:
                    orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
                    break;
                case Surface.ROTATION_270:
                    orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
                    break;
                default:
                    Log.e("ScreenOrientation", "Unknown screen orientation. Defaulting to " + "portrait.");
                    orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
                    break;
            }
        }
        // if the device's natural orientation is landscape or if the device
        // is square:
        else {
            switch (rotation) {
                case Surface.ROTATION_0:
                    orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
                    break;
                case Surface.ROTATION_90:
                    orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
                    break;
                case Surface.ROTATION_180:
                    orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
                    break;
                case Surface.ROTATION_270:
                    orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
                    break;
                default:
                    Log.e("ScreenOrientation", "Unknown screen orientation. Defaulting to " + "landscape.");
                    orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
                    break;
            }
        }
    
        return orientation;
    }
    

    On orientation change, I would like to do something simple like this:

    RotateAnimation rotateAnimation = new RotateAnimation(0, getScreenOrientation(getContext()));
    rotateAnimation.setDuration(2000);
    
    for (int i = 0; i < 63; i++) {
        Button button = (Button) rootView.findViewById(i);
        button.startAnimation(rotateAnimation);
    }
    

    Another way to rephrase my question would be "Is there any way to detect orientation change in onConfigurationChanged() method without changing the layout?". The problem is that it will not be able to detect any orientation change if I already disable layout orientation change.

    Anyone knows how it is done? I might have totally gone through wrong steps, and I think I will have to use Accelerometer Sensor or something similar to that to achieve what I want, so please guide me through.

  • Hussein El Feky
    Hussein El Feky over 8 years
    First of all, thanks for your answer, but I have already tried that, and the problem still persists. Although the orientation change will be detected and I can rotate the views I want through it, the layout will still be rotated on orientation change, and I only want to rotate some views themselves rather than rotating the layout. Is there any way to detect orientation change without changing the layout?
  • David Medenjak
    David Medenjak over 8 years
    @HusseinElFeky I think you need to go into more detail. Basically "just rotating the views and not the layout" would require you to write a custom layout and there you could react to the change and then rotate/move your views accordingly...I'll just quickly try that
  • David Medenjak
    David Medenjak over 8 years
    @HusseinElFeky I wasn't successful but updated the answer to include the idea
  • Hussein El Feky
    Hussein El Feky over 8 years
    I understand what you mean, but the problem is that my layout is quite complex, and I'm actually using ViewPager with 10 fragments (pages). I know that's a lot, so if I did as what you said, it would take a bunch of time to reload the whole activity again and would consume a lot of memory (I have already tested it). That's why I'm looking for an answer that doesn't require changing layouts. Thanks for your help though.
  • Hussein El Feky
    Hussein El Feky over 8 years
    Thanks. OrientationEventListener was what I exactly wanted. I just had to modify some stuff in your code to fulfill my needs.
  • Blo
    Blo about 7 years
    However, the system bar and the status bar will be still displayed in portrait mode whatever the orientation of the device. Any solution for that?
  • mirekkomis
    mirekkomis about 7 years
    @Fllo The question was about rotating just some views, not whole layout. I'm afraid you have to reload whole layout to landscape/portrait if you want system bars to rotate. You can use: setRequestedOrientation(requestedRotation) in OrientationListener .onOrientationChanged or much easier set android:screenOrientation="sensor" in AndroidManifest.xml for your Activity
  • Blo
    Blo about 7 years
    @mirekkomis No problem, I found the solution - last part of the answer linked.
  • BST Kaal
    BST Kaal almost 6 years
    After a loooong search, finally a worthy answer.