如何监听多个全局布局事件

6

我正在尝试使用GlobalLayout来监听。

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

但是它被称为无限调用。


我知道我应该像这样删除监听器:view.getViewTreeObserver().removeOnGlobalLayoutListener(this);

但我想保持监听器活动,以便下一个GlobalLayout事件。

目前,我只是尝试使用这个问题来监听视图位置的更改。我尝试了onPreDraw,但是效果相同。


是否有可能监听多个GlobalLayout事件?



感谢您的提前帮助。


OnLayoutChangeListener 怎么样? - Pavel Poley
@PavelPoley 同样的问题 https://stackoverflow.com/questions/65831475/how-to-listen-for-several-layoutchange-calls - ATP
@ATP 为什么不在位置改变时才增加c - Sherif elKhatib
4个回答

1
您所发布的代码示例不应该编译,因为存在从监听器访问变量 c 的问题。如果尝试编译,Android Studio将会报以下错误:

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

我们可以采用错误检查建议并创建一个单元素数组,如下所示:
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]++;
            }
        });
    }
}

如果您在以下布局上运行此代码,您将看到侦听器被调用两次,我认为这对应于两个布局传递:
<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>

如果在全局布局侦听器中修改了布局,则会触发另一个布局和对侦听器的另一次调用。如果侦听器再次进行更改,则会再次调用侦听器,如此循环。如果您能发布实际的代码或演示问题的简单项目,那将非常有帮助。在侦听器内是否修改了布局?
更新:正如您在评论中所说的那样,您的问题是您在全局布局监听器中更改了视图,触发了另一个布局和对监听器的无限调用。一旦您删除了使布局更改的代码,该特定问题就得到解决。

非常感谢,我没有考虑到那些类型的问题。"如果全局布局监听器内部修改了布局,则会触发另一个布局和对监听器的另一个调用。如果监听器再次进行更改,则将再次调用监听器,如此循环下去。"尽管我移动了视图,但这不是导致问题的原因,正如您所说,我最初提供的代码并不是导致问题的代码,我的真实代码使用view.setText(c+"")(查看c值),这可能是问题的原因,因为它改变了UI并启动了另一个事件。 - ATP
我已经将代码更改为存在问题的代码。感谢您提醒我,请更新您的答案以获得赏金。 - ATP

0

如果你只是想在视图位置改变时触发代码,那么你可以在增加变量c之前检查x、y坐标是否已经改变。

像这样:

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

是的,我考虑过这个问题...但这并不高效。有其他方法吗?感谢您的回答。 - ATP
视图的位置是如何改变的?这是一个动画吗? - Khaled
约束布局,当受限视图增加大小时。 - ATP
preDraw 有同样的问题。 - ATP
让我们在聊天中继续这个讨论 - Khaled
显示剩余2条评论

0

只需添加一个标志来检测是否为普通事件或由监听器调用的setText(),如果需要,可以跳过它。

代码未经测试。但我想你会明白的。

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

这样做不起作用,因为setText不会同步进行布局,它只是设置一个标志,将在下一帧上发生布局。 - mhsmith

0

同一个问题 https://stackoverflow.com/questions/65831475/how-to-listen-for-several-layoutchange-calls - ATP

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接