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

16,974

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!

Share:
16,974
Big_Boulard
Author by

Big_Boulard

Updated on June 04, 2022

Comments

  • Big_Boulard
    Big_Boulard about 2 years

    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 :)

  • Simon Zettervall
    Simon Zettervall over 11 years
    A -1 without an explanation? I am confused. What did I do wrong?
  • Simon Zettervall
    Simon Zettervall over 11 years
    @Chirag Patel I thank you for the ambition to make things better but I can honestly not read efficiently with all that bold text.
  • WarrenFaith
    WarrenFaith over 11 years
    @ChiragPatel indeed, code highlighting is sufficient done with the ticks, no need to go bold on it, too.
  • Big_Boulard
    Big_Boulard over 11 years
    Hi Simon ! Thanks for reminds me the conventions of coding and the best practices. Method 2 seems to be nice in my case, i'm gotta try it this week, i think it will be ok. Many thanks for this complete and helpful answer. have a nice day :)
  • Simon Zettervall
    Simon Zettervall over 11 years
    @popov130 I am glad you found it helpful!
  • Big_Boulard
    Big_Boulard over 11 years
    For people how read this question. As WarrenFaith said, "You can use a database to store the image and their download status", it's useful if you want to restart downloads too if the app crash for example. But you can also use a static propertie in your class, for instance setting it to "true" into the onCreate(Bundle savedInstanceState) function : LibraryFragment.mIsLibraryDownloaded = true
  • Simon Zettervall
    Simon Zettervall over 11 years
    @popov130 First of I think you meant me. Secondly if you use a static boolean value to keep track if all the images have been downloaded then it will not be saved if the app crashes. Therefore it is better to use a persistent database.
  • Big_Boulard
    Big_Boulard over 11 years
    @Simon Zettervall "it's useful if you want to restart downloads too if the app crash for example" but if you just code a sample code for student or a demo and you don't have time to to it so the static boolean is faster to develop.
  • Simon Zettervall
    Simon Zettervall over 11 years
    @popov130 Yes it is faster but if the app crashes the boolean value will be reset. So if you have ten images to download and you download them all, the boolean will be true. If you close the app and start it again the boolean will be false. You have no way to track if the image was downloaded except to check if the file exists. If the app crash at image five it will download all ten images again, even the five that already exists, if you do not check if the file exists here as well.
  • Big_Boulard
    Big_Boulard over 11 years
    @Simon Zettervall Obviously the class that open a URL and write the content into a new File always check if the file already exists. We are ok :)
  • Simon Zettervall
    Simon Zettervall over 11 years
    @popov130 I just wanted to add that if you get a server generated file the name might be different and you cannot check if that file exists. Something to think about.