Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change layout while orientation change at runtime in a fragment without recreating the view

I try to develop a first app that download images from the net and show them in a gridview. The gridview is a fragment of a main Activity. The download process is made with an AsyncTask in the onCreate Function. In order not to download again the images while changing orientation, i set the android:configChanges="orientation|screenSize" in the Android Manifest. Then the onCreate function is only call once and everything's good ... except that i've got to make a few changes in the layout for the gridview fragment in landscape mode. So i created 2 layout sheets : fragment_library.xml and fragment_library_land.xml in the layout/ folder. To make these changes work, i tryed to change the layout of the library fragment manually by using the onConfigurationChanged function. At runtime, the program evaluate the function and pass in the good case (portrait or landscape) but the layout used is still the one for portrait mode : fragment_library.xml ...

public class LibraryFragment extends Fragment {
    public GridView gridview;
    private Boolean isImageAdapterPopulated = false;

    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        GetLibraryTask getLibraryTask = new GetLibraryTask(this);
        getLibraryTask.execute(Config.URL + "action=getLibrary");
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
        if (container == null)
            return null;

        // gridview
        View V = inflater.inflate(R.layout.fragment_library, container, false);
        gridview = (GridView)V.findViewById(R.id.gridview);

        if(this.isImageAdapterPopulated)
            this.setGridAdapter();
        return V;
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig){
        super.onConfigurationChanged(newConfig);
        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService( Context.LAYOUT_INFLATER_SERVICE );
            inflater.inflate(R.layout.fragment_library_land, null);
        } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
            LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService( Context.LAYOUT_INFLATER_SERVICE );
            inflater.inflate(R.layout.fragment_library, null);
        }
    }

    public void setGridAdapter(){
        this.isImageAdapterPopulated = true;
        gridview.setAdapter(new ImageAdapter(getActivity()));
    }

    // ...
}

fragment_library.xml

<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android" 
    android:id="@+id/gridview"
    android:cacheColorHint="@android:color/transparent"
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent"
    android:columnWidth="200dp"
    android:numColumns="auto_fit"
    android:verticalSpacing="10dp"
    android:horizontalSpacing="20dp"
    android:stretchMode="columnWidth"
    android:gravity="bottom"
/>

fragment_library_land.xml

<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android" 
    android:id="@+id/gridview"
    android:cacheColorHint="@android:color/transparent"
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent"
    android:columnWidth="400dp"
    android:numColumns="2"
    android:verticalSpacing="50dp"
    android:horizontalSpacing="50dp"
    android:stretchMode="columnWidth"
    android:gravity="bottom"
/>

Thanks for help :)

like image 641
Big_Boulard Avatar asked Jan 15 '13 13:01

Big_Boulard


People also ask

How do I change the orientation of a fragment?

Use the following code line in the fragment where you want a specific (in this case portrait) orientation. getActivity(). setRequestedOrientation( ActivityInfo. SCREEN_ORIENTATION_PORTRAIT);

How to handle changes in screen orientation?

If you want to manually handle orientation changes in your app you must declare the "orientation" , "screenSize" , and "screenLayout" values in the android:configChanges attributes. You can declare multiple configuration values in the attribute by separating them with a pipe | character.

What happens when screen orientation changes in Android?

When you rotate your device and the screen changes orientation, Android usually destroys your application's existing Activities and Fragments and recreates them. Android does this so that your application can reload resources based on the new configuration.

What code is needed to make your Android app use a landscape specific layout on orientation change?

First, add android:configChanges="orientation|screenSize|keyboard|keyboardHidden" so the app handles the config changes instead of android. Make two different layout for landscape and portrait.


1 Answers

It is not possible. A Fragment cannot update it's layout dynamically. You do have some other options however.

1. Not a fan of this, but you may have a Fragment's layout with both the portrait and the horizontal views at the same time and show and hide.

fragment_library.xml:

<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android" 
    android:id="@+id/gridview_portrait"
    android:cacheColorHint="@android:color/transparent"
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent"
    android:columnWidth="200dp"
    android:numColumns="auto_fit"
    android:verticalSpacing="10dp"
    android:horizontalSpacing="20dp"
    android:stretchMode="columnWidth"
    android:gravity="bottom"
/>
<GridView xmlns:android="http://schemas.android.com/apk/res/android" 
    android:id="@+id/gridview_landscape"
    android:cacheColorHint="@android:color/transparent"
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent"
    android:columnWidth="400dp"
    android:numColumns="2"
    android:verticalSpacing="50dp"
    android:horizontalSpacing="50dp"
    android:stretchMode="columnWidth"
    android:gravity="bottom"
    android:visible="gone"
/>

Then some private member variables:

private GridView mGridViewPortrait;
private GridView mGridViewLandscape;

Then in onConfigurationChanged(Configuration newConfig):

@Override
        public void onConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);

            if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
                 mGridViewPortrait.setVisibility(View.VISIBLE);
                 mGridViewLandscape.setVisibility(View.GONE);
            }

            else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
                 mGridViewPortrait.setVisibility(View.GONE);
                 mGridViewLandscape.setVisibility(View.VISIBLE);
            }
        }

Some points: Note that i left out the code to refer the both GridViews. I also changed your GridView to private and also changed the name to mGridView*. Private to keep it "data-encapsulated" and "m" because it is a member of the class, just convention. I also changed the if-else clause because I wanted the portrait check to come first.

This way is the fastest and easiest, however it might become heavy for the system if you have big layouts so do not use this if you have many things. Preferably do not use this approach at all.

2. The proper way is to let the Andorid take care of the orientation and also move your XML to a right directory. This however will recreate your Fragment (if you do not set setRetainInstance(true); which you will not in this case; this will make the Fragment not recreate it's layout (actually looking up the retain method it does not mention onCreateView so you may try to set this to true aswell and try)).

Move your fragment_library_land.xml to the directory layout-land instead of layout and name it fragment_library.xml. Note the bold, it will have the same name but stay in different directories. This way Android will know and take the right layout based on the orientation.

If I have understood why you will not want to recreate the Fragment because onCreate(Bundle savedInstanceState) will be called again (with setRetainInstance(true); it will not and to as regard to what I wrote earlier you may give it a try) thus creating a new instance of GetLibraryTask and download the images again. This could be prevented if you used a database to store the images and if you had a boolean value that kept track if you had downloaded the image or not. In GetLibraryTask you would then pick out the images which are not downloaded, be it the first time the task is run or if the orientation changed. You would also require to put in a stop check in the library task in the download loop that before each item checks if you are supposed to download the image or if the fragment is no longer available and thus quit the task.

Now when you change orientation the Activity will recreate the LibraryFragment and depending on the orientation either layout or layout-land will be used.

Some side notes in your code:

  • As I wrote earlier, never use public access, always use private or protected when necessary. Private can be used all time though and have getters and setters (accesors and mutators) to do the communication.
  • Use "m" as a prefix on member variables, in this case public GridView gridview would be private GridView mGridView and private Boolean isImageAdapterPopulated would be private boolean mIsImageAdapterPopulated
  • Never use classes for primitive types if you do not need it. You may need it in lists which does not support primitive types or class retention etc.
  • In your onConfigurationChanged(Configuration newConfig) you inflate an XML and it returns a View but you are not doing anything with it

I wish you good luck!

like image 183
Simon Zettervall Avatar answered Sep 29 '22 13:09

Simon Zettervall