How to listen for multiple GlobalLayout events

Viewed 675

I am trying to listen for GlobalLayout using

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    int c=0;
    @Override
    public void onGlobalLayout() {
        c++; //without removing the listener c will grow for ever even though there is no GlobalLayout events
        view.setText(""+c);
    }
});

but it's called endlessly.


I know I should remove the listener like this: view.getViewTreeObserver().removeOnGlobalLayoutListener(this);

But I want to keep the listener alive for the next GlobalLayout events.

Currently I am just trying to listen for view position changing using this question I tried onPreDraw but it is the same.


Is it possible to listen for several GlobalLayout events?



Thanks in advance.

4 Answers

The code you posted as an example shouldn't compile because of issues with accessing the variable c from the listener. If you try it, you should get the following error in Android Studio:

Variable 'c' is accessed from within inner class, needs to be final or effectively final

We can take the suggestion that error check suggests and create a one-element array as follows:

public class MainActivity extends AppCompatActivity {

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

        final int[] c = {0};
        final View[] view = {findViewById(R.id.textView)};
        view[0].getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                c[0]++;
            }
        });
    }
}

If you run this code against the following layout, you will see that the listener is called twice which I think corresponds to two layout passes:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textView" />

</androidx.constraintlayout.widget.ConstraintLayout>

If the layout is somehow modified within the global layout listener, then it will trigger another layout and another call to the the listener. If the listener again makes a change, the listener will be called yet again and so on forever.

It would be helpful if you would post the actual code you are having trouble with or a simple project that demonstrates the problem. Is the layout somehow modified within the listener?


Update: As you say in one of your comments on this answer, your issue was that you made a change to a view in the global layout listener which triggered another layout and another call to the listener ad infinitum. Once you removed the code that made the layout change, that particular issue was resolved.

If you're just trying to trigger the code once the view position has changed, then you could probably just check if the x, y coordinates has changed before increasing the variable c

Something like this:

int c = 0;
int x = 0;
int y = 0;
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        int x2 = view.getX();
        int y2 = view.getY();
        // do not increase c unless position has changed
        if (x2 != x && y2 != y) {
            c++;
            // update coordinates values
            x = x2;
            y = y2;
        }
        
    }
});

Simply add a flag to detect if it is a normal event or a setText() that is called by the listener and skip it if needed.

Code not tested. But I guess you will get the idea.

int c=0;

view.getViewTreeObserver().addOnGlobalLayoutListener(new CustomLayoutListener() {

     int c=0;

    @Override
    public void onGlobalLayout() {

    if(skipEvent)
            return;

    c++;

    //flag skipEvent while performing setText()
    skipEvent = true;
        view.setText(""+c);
    skipEvent = false;

    }
});


class CustomLayoutListener extends ViewTreeObserver.OnGlobalLayoutListener {
    static boolean skipEvent;

    @Override
    public void onGlobalLayout() {}
}
Related