Thursday, September 4, 2014

OnGlobalLayoutListener called repeatly, and remove it

In last post, we re-layout the views when the global layout state within the view tree changes, with OnGlobalLayoutListener. But once re-layout the views, it trigger another layout state change, and repeat and repeat again.


To prevent it, we can call removeGlobalOnLayoutListener() (deprecated in API level 16) or removeOnGlobalLayoutListener() (for API Level >= 16) after layout.

MainActivity.java
package com.example.androidtouchview;

import com.example.androidtouchview.MyView.OnToggledListener;

import android.support.v7.app.ActionBarActivity;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.GridLayout;
import android.widget.Toast;
import android.os.Bundle;

public class MainActivity extends ActionBarActivity 
 implements OnToggledListener{
 
 int numberOfGlobalLayoutCalled = 0;
 
 MyView[] myViews;
 
 GridLayout myGridLayout;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  myGridLayout = (GridLayout)findViewById(R.id.mygrid);
  
  int numOfCol = myGridLayout.getColumnCount();
  int numOfRow = myGridLayout.getRowCount();
  myViews = new MyView[numOfCol*numOfRow];
  for(int yPos=0; yPos<numOfRow; yPos++){
   for(int xPos=0; xPos<numOfCol; xPos++){
    MyView tView = new MyView(this, xPos, yPos);
    tView.setOnToggledListener(this);
    myViews[yPos*numOfCol + xPos] = tView;
    myGridLayout.addView(tView);
   }
  }
  
  myGridLayout.getViewTreeObserver().addOnGlobalLayoutListener(
   new OnGlobalLayoutListener(){

   @Override
   public void onGlobalLayout() {
    
    final int MARGIN = 5;
    
    int pWidth = myGridLayout.getWidth();
    int pHeight = myGridLayout.getHeight();
    int numOfCol = myGridLayout.getColumnCount();
    int numOfRow = myGridLayout.getRowCount();
    int w = pWidth/numOfCol;
    int h = pHeight/numOfRow;
    
    for(int yPos=0; yPos<numOfRow; yPos++){
     for(int xPos=0; xPos<numOfCol; xPos++){
      GridLayout.LayoutParams params = 
       (GridLayout.LayoutParams)myViews[yPos*numOfCol + xPos].getLayoutParams();
      params.width = w - 2*MARGIN;
      params.height = h - 2*MARGIN;
      params.setMargins(MARGIN, MARGIN, MARGIN, MARGIN);
      myViews[yPos*numOfCol + xPos].setLayoutParams(params);
     }
    }
    
    Toast.makeText(MainActivity.this, 
     "numberOfGlobalLayoutCalled = " + numberOfGlobalLayoutCalled, 
     Toast.LENGTH_SHORT).show();
    numberOfGlobalLayoutCalled++;
    
    //deprecated in API level 16
    myGridLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
    //for API Level >= 16
    //myGridLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
   }});
 }

 @Override
 public void OnToggled(MyView v, boolean touchOn) {

  //get the id string
  String idString = v.getIdX() + ":" + v.getIdY();

  Toast.makeText(MainActivity.this, 
   "Toogled:\n" +
   idString + "\n" +
   touchOn, 
   Toast.LENGTH_SHORT).show();
 }

}

5 comments:

Unknown said...

Hi, can you show the MYview class?

Erik said...

Hello Andrea Petreti,

MyView.java in last post, Insert view to GridLayout dynamically, with even width and height.

275eaniversarisantana said...

Hello thanks for your help with that code, I'm having some troubles... I implemented it and it works perfect, the thing that I don't understand....
I'm wondering if I can avoid this getViewTreeObserver().addOnGlobalLayoutListener or it must be there on MainActivity to create the GridLayout? the problem is that I don't want t fill of my screen so if I put this android:layout_height="400dp" and I put GridLayout 7x7 it says :vertical constraints: y3-y0>=1749, y3-y2<=400, y2-y1<=400, y1-y0<=400 are inconsistent; permanently removing: y3-y2<=400. Also I'm having problems to get the row column I posted a question on StackOverflow, please help me... http://stackoverflow.com/questions/36061027/girdlayout-getting-row-and-column

Thanks...

cheesus said...

the solution presented her (removing the listener at the end of onGlobalLayout() did not work for us in cases where the soft keyboard was retracting just when we were layouting, resulting in the layout not growing for the newly added space.
so the solution for us is to execute the layout adjustment when they need to, i.e. when width and/or height change; so on the second call it will exit without triggering layout changes, thus not producing an endless loop.
and if now keyboard hides, call 3 will relayout and call 4 will again just quit.

Marc said...

Thanks for the code!!!

Is there a possibility to toggle all grid elements with an extra Button?