ATP
ATP

Reputation: 3249

How to listen for multiple GlobalLayout events

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.

Upvotes: 6

Views: 1599

Answers (4)

Bertram Gilfoyle
Bertram Gilfoyle

Reputation: 10235

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() {}
}

Upvotes: 0

Cheticamp
Cheticamp

Reputation: 62831

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.

Upvotes: 1

ErfanDP
ErfanDP

Reputation: 181

you can set the Listener on your Viewes try to use view.addOnLayoutChangeListener

https://developer.android.com/reference/android/view/View.OnLayoutChangeListener

it will give you the old and new X&Y of your layout and you can adjust your view base on it

Upvotes: 0

Khaled
Khaled

Reputation: 552

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

Upvotes: 0

Related Questions