Home » Android » java – Android – View.requestLayout doesn't work in OnLayoutChangeListener

java – Android – View.requestLayout doesn't work in OnLayoutChangeListener

Posted by: admin June 15, 2020 Leave a comment

Questions:

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! 🙂

How to&Answers:

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);
  }
}

Answer:

@user3492470
Thank you so much for your answer. I thought nobody has an idea.

Your “solution” is interesting but unfortunately a little bit wrong. Optically it works great but in the background you have got an endless loop …
You will see it if you set a counter at the text of one of the Buttons. The counter increases endlessly.
The setting of Buttons’ new layout size in the Handler is followed by a new call of the OnLayoutChangeListener and this Listener starts the Handler again and so on …
You will get the same problem if you are using the OnGlobalLayoutListener instead of the OnLayoutChangeListener with a Handler.
The reason is the following. The view.requestLayout() or view.setLayoutParams(…) does not work in the onLayoutChange method, but it works in the onGlobalLayout method or in a Handler which creates a new thread.
Now the part I really don’t understand:
A successful call on the child.requestLayout() makes a new call on the parent’s onLayoutChange method. This is peculiar because the parent’s layout should not change again.
However that be, a little if/else logic solves the endless calls.

  private int counter = 0;
  @Override
  public void onGlobalLayout() {
    // TODO Auto-generated method stub

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

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

    /* Buttons width and height are supposed to be */

    int mButtons_width  = width;
    int mButtons_height = height / 2;

    if(!(mButtonAbove.getWidth()  == mButtons_width  &&
         mButtonAbove.getHeight() == mButtons_height &&
         mButtonBelow.getWidth()  == mButtons_width  &&
         mButtonBelow.getHeight() == mButtons_height)
      ){

      /* Changes the LayoutParams of ContentViews children dynamicly */

      mGridLayout_LayoutParams_Above.width = mButtons_width;
      mGridLayout_LayoutParams_Above.height = mButtons_height;

      mGridLayout_LayoutParams_Below.width = mButtons_width;
      mGridLayout_LayoutParams_Below.height = mButtons_height;

      /* NO ISSUE ANYMORE:
       * updates the rendering of the buttons */

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

    }

    /* A little debug info for knowing the ContentView's and Children's size */

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

  }

In this case I use the OnGlobalLayoutListener instead of the OnLayoutChangeListener/uiHandler Combination. 😉 The if/else logic is the same.

My original question, why requestLayout does not work in onLayoutChange, is still open but a satisfying solution was presented here so I am happy.

Thank you again for your concern!

Answer:

handler is dangerous.suggest use
view.getViewTreeObserver().addOnPreDrawListener();

this is not conflict;