0

My Android app is handling configuration changes manually (for reasons I cannot change) and need to re-inflate a GridLayout with different rows and columns depending on the orientation. My solution works great on the Nexus 5 emulator, but often fails on my Galaxy S6 phone.

Working screenshots from emulator:

Portrait screenshot Landscape screenshot

This sometimes works on my phone, but about half of the time this is what happens after rotating from landscape to portrait:

Portrait screenshot with bad layout

It seems like the old layout padding and margin values are being used instead of the new values that are created when the layout is re-inflated. Furthermore, this appears to be a timing issue (see the Hack at the end of my question).

Code

Here is the fragment's layout that uses a GridLayout where columnCount and rowCount change based on the orientation:

fragment_my.xml

<android.support.v7.widget.GridLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:grid="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   grid:columnCount="@integer/col_count"
   grid:rowCount="@integer/row_count"
   grid:orientation="horizontal">

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="80sp"
    grid:layout_gravity="center"
    grid:layout_columnWeight="1"
    grid:layout_rowWeight="1"
    android:text="A"/>

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="80sp"
    grid:layout_gravity="center"
    grid:layout_columnWeight="1"
    grid:layout_rowWeight="1"
    android:text="B"/>

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="80sp"
    grid:layout_gravity="center"
    grid:layout_columnWeight="1"
    grid:layout_rowWeight="1"
    android:text="C"/>

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="80sp"
    grid:layout_gravity="center"
    grid:layout_columnWeight="1"
    grid:layout_rowWeight="1"
    android:text="D"/>

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="80sp"
    grid:layout_gravity="center"
    grid:layout_columnWeight="1"
    grid:layout_rowWeight="1"
    android:text="E"/>

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="80sp"
    grid:layout_gravity="center"
    grid:layout_columnWeight="1"
    grid:layout_rowWeight="1"
    android:text="F"/>

</android.support.v7.widget.GridLayout>

res/values/ints.xml

<resources>
   <integer name="row_count">3</integer>
   <integer name="col_count">2</integer>
</resources>

res/values-land/ints.xml

<resources>
   <integer name="row_count">2</integer>
   <integer name="col_count">3</integer>
</resources>

Here's the relevant parts of my fragment that creates a FrameLayout for the parent view and reloads fragment_my.xml when the orientation changes.

private FrameLayout mFrameLayout;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState)
{
    mFrameLayout = new FrameLayout(getContext());

    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_my, mFrameLayout, true);
}

@Override
public void onConfigurationChanged(Configuration newConfig)
{
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ||
        newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
    {
        // Get rid of previous GridLayout
        mFrameLayout.removeAllViews();

        // Reload GridLayout with new row/col values
        LayoutInflater inflator = LayoutInflater.from(getContext());
        inflator.inflate(R.layout.fragment_my, mFrameLayout, true);
    }

    super.onConfigurationChanged(newConfig);
}

Hack

Here's a hack I can use to "fix" the problem on my phone: delay 100 milliseconds before inflating:

Handler handler = new Handler();
handler.postDelayed(new Runnable() {
    public void run() {
        mFrameLayout.removeAllViews();
        LayoutInflater inflator = LayoutInflater.from(getContext());
        inflator.inflate(R.layout.fragment_my, mFrameLayout, true);
    }
}, 100);

This is obviously not ideal as it relies on some timing issue that may vary from phone to phone. Seems like I need to be notified when some internal configuration has completed before I re-inflate, but I can't seem to find any callbacks that do this.

Any help would be greatly appreciated.

Update

I believe the problem is a bug within the GridLayout. You can see a related question I asked elsewhere dealing with a resizing of the GridLayout that resulted in a similar problem. I have replaced my GridLayout with a ConstraintLayout and had no problems.

tronman
  • 9,862
  • 10
  • 46
  • 61

2 Answers2

0

Why don't you use different layouts, for landscape and for portrait. Create layout-land for landscape layout and layout-port for portrait layout. I hope it will help solve your problem :)

Borko
  • 9
  • 2
  • 1
    No, that doesn't solve the problem. The 2 layouts would be identical except for row and column count, but I use integer resources for portrait or landscape that are automatically inserted into the layout when I re-inflate fragment_my.xml. – tronman Jul 11 '17 at 19:57
0

There are a few ways to wait until the view is laid out. My favourite one is to use view.post(Runnable).

Try to use

mFrameLayout.post(new Runnable() {
    public void run() {
        mFrameLayout.removeAllViews();
        LayoutInflater inflator = LayoutInflater.from(getContext());
        inflator.inflate(R.layout.fragment_my, mFrameLayout, true);
   }
});

If that doesn't help you can try to use the ViewTreeObserver. Set the OnGlobalLayoutListener on it and it will fire when the view is laid out.

TpoM6oH
  • 8,385
  • 3
  • 40
  • 72
  • That doesn't work. I don't need to wait until the view is laid out, I need the inflated view to stick somehow. Thank you anyway. – tronman Jul 12 '17 at 22:22