Android - View.requestLayout doesn't work in OnLayoutChangeListener

Barock picture Barock · Aug 15, 2014 · Viewed 9.2k times · Source

I have to use the GridLayout for my application. The problem of GridLayout is the limitation of weight so I must scale its children size at runtime. I do this by the help of an OnGlobalLayoutListener.

The children of my GridLayout are two Buttons which have got the width of the parent and the half of parents height. One Button above and one Button below. If the upper Button is clicked I want to switch the size of the GridLayout to 500 in width and height and 700 in width and height. After the click at the upper Button the Buttons should scale correctly but they don't do that.

public class HauptAktivitaet extends Activity implements OnLayoutChangeListener, OnClickListener{

  /** ContentView its sizes I will change on a click later */

  private GridLayout mGridLayout;

  /** LayoutParams of the above child */

  private GridLayout.LayoutParams mGridLayout_LayoutParams_Above;

  /** LayoutParams of the below child */

  private GridLayout.LayoutParams mGridLayout_LayoutParams_Below;

  /** Children of the ContentView */

  private Button mButtonAbove;
  private Button mButtonBelow;


  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    inline();

    setContentView(mGridLayout);

  }

  private void inline(){

    /* instantiation of ContentView which is named mGridLayout */

    mGridLayout = new GridLayout(this);

    /* set the count of rows and columns of ContentView */

    mGridLayout.setRowCount(2);
    mGridLayout.setColumnCount(1);

      /* set OnGlobalLayoutListener for observe a change of its layout */

    mGridLayout.addOnLayoutChangeListener(this);

    /* instantiation of the LayoutParams for the children */

    mGridLayout_LayoutParams_Above = new GridLayout.LayoutParams(GridLayout.spec(0), GridLayout.spec(0));
    mGridLayout_LayoutParams_Below = new GridLayout.LayoutParams(GridLayout.spec(1), GridLayout.spec(0));

    /* instantiation of the children and setting of their LayoutParams */

    mButtonAbove = new Button(this);
    mButtonAbove.setLayoutParams(mGridLayout_LayoutParams_Above);
    mButtonAbove.setOnClickListener(this); // A click on this Button changes the Size of its parent ViewGroup.

    /* instantiation of ContentView */

    mButtonBelow = new Button(this);
    mButtonBelow.setLayoutParams(mGridLayout_LayoutParams_Below);

    /* add children to the ContentView */

    mGridLayout.addView(mButtonAbove);
    mGridLayout.addView(mButtonBelow);

  }

  @Override
  public void onLayoutChange(View v, int left, int top, int right, int bottom,
      int oldLeft, int oldTop, int oldRight, int oldBottom) {

    /* Width and height on this time are known */

    int width = mGridLayout.getWidth();
    int height = mGridLayout.getHeight();

    /* Changes the LayoutParams of ContentViews children dynamicly*/

    mGridLayout_LayoutParams_Above.width = width;
    mGridLayout_LayoutParams_Above.height = height / 2;

    mGridLayout_LayoutParams_Below.width = width;
    mGridLayout_LayoutParams_Below.height = height / 2;

    /* ISSUE:
     * should update the rendering of the buttons but it doesn't work correctly */

    mButtonAbove.requestLayout();
    mButtonBelow.requestLayout();

    /* A little debug info for knowing of the ContentViews size */

    mButtonBelow.setText("Own Width = " + mButtonBelow.getWidth() + "\n" + 
                         "Own Height = " + mButtonBelow.getHeight() + "\n" +
                         "Perents Width = " + width + "\n" +
                         "Perents Height = " + height);


  }

  private boolean switcher = true;

  /** 
   * Works correctly
   * */

  @Override
  public void onClick(View v) {

    int width = 0;
    int height = 0;

    if(switcher){
      width = 500;
      height = 500;
      switcher = false;
    }else{
      width = 700;
      height = 700;
      switcher = true;
    }

    mGridLayout.getLayoutParams().width = width;
    mGridLayout.getLayoutParams().height = height;

    mGridLayout.requestLayout();

  }

}

Can anybody tell me what to do?

How can I tell the ContentView to refresh itself and its children? I am searching for an elegant solution.

Thank you very much for answers!

Regards! :-)

Answer

user3492470 picture user3492470 · Aug 18, 2014

I ran into the same problem. I guess Android has not finished calculating the new layout when onLayoutChanges() is called, and a recalculation cannot be triggered from within the method. The solution is to use a Handler to post layout changes to the end of the UI thread.

I am not really sure about requestLayout(), maybe replace it with another call of setLayoutParams(). It probably has the same effect.

So implement your re-layout in a class implementing Runnable, instantiate a Handler in onCreate() and in onLayoutChanges() instantiate the Runnable class and post it to the Handler:

public class HauptAktivitaet extends Activity implements OnLayoutChangeListener, OnClickListener{

  // this class updates the layout
  private class LayoutUpdater implements Runnable {
    private final int width;
    private final int height;

    private LayoutUpdater(int width, int height) {
      this.width = width;
      this.height = height;
    }

    public void run() {
      mGridLayout_LayoutParams_Above.width = width;
      mGridLayout_LayoutParams_Above.height = height;

      mGridLayout_LayoutParams_Below.width = width;
      mGridLayout_LayoutParams_Below.height = height;

      // might be necessary or not: set layout parameters to trigger update
      mButtonAbove.setLayoutParams(mGridLayout_LayoutParams_Above);   
      mButtonBelow.setLayoutParams(mGridLayout_LayoutParams_Below);
    }
  }

  /* snip */

  // add handler running in UI thread
  private Handler uiHandler;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // instantiate handler
    uiHandler = new Handler();

    inline();

    setContentView(mGridLayout);

  }

  /* snip */

  @Override
  public void onLayoutChange(View v, int left, int top, int right, int bottom,
      int oldLeft, int oldTop, int oldRight, int oldBottom) {

    /* Width and height on this time are known */

    int width = mGridLayout.getWidth();
    int height = mGridLayout.getHeight();

    // post layout update to the end of queue
    uiHandler.post(new LayoutUpdater(width, height / 2);
  }
}