DecorView에 OnApplyWindowInsetsListener를 설정하면 statusBarBackground와 navigationBarBackground가 사라집니다.

13499 단어 안드로이드

요약



View에 OnApplyWindowInsetsListener를 등록했을 때는 그 View의 onApplyWindowInsets를 호출할 필요가 없는 것인지 생각해 봅시다.
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, insets ->
    // ごにょごにょ
    WindowInsetsCompat.toWindowInsetsCompat(view.onApplyWindowInsets(insets.toWindowInsets()))
}

배경



상태바나 네비게이션 바 등의 영역 정보를 받기 위해서는 setOnApplyWindowInsetsListener 를 사용해 WindowInsets 의 값을 받습니다.

WindowInsets는 상위 View에서 하위 View로 전파됩니다. 그러나 중간에 Insets 정보가 소비 될 수 있습니다.
부모가 이미 오프셋을 가지고 있다면 자식 View가 더 오프셋을 취하면 이상해지기 때문에 당연한 거동입니다.
(OnApplyWindowInsetsListener#onApplyWindowInsets가 반환 값을 가지는 것은 값의 소비를 전달하기 때문입니다)

하지만 어떤 이유로 소비되기 전의 정보를 집어 싶다면 decorView에 리스너를 설정하면 좋을지도 모릅니다.

※DecorView에 OnApplyWindowInsetsListener를 set 하는 것이 원래 좋은 것인가 그런 것은 두어 둡니다
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, insets ->
    // insets使ってごにょごにょ
    insets
}

그러나 그렇게 하면 리스너를 설정했을 뿐이라고 하는데, 스테이터스바와 네비게이션 바의 표시가 이상하게 됩니다.


아무것도
OnApplyWindowInsetsListener 세트






어떻게 되는지, layout insepector로 보자.









navigationBarBackground와 statusBarBackground가 사라졌습니다.

setOnApplyWindowInsetsListener에서 리스너를 등록하면 어떻게 되는지 살펴 보겠습니다.

View.java
public void setOnApplyWindowInsetsListener(OnApplyWindowInsetsListener listener) {
    getListenerInfo().mOnApplyWindowInsetsListener = listener;
}

리스너는 ListenerInfo에 등록됩니다.
이 리스너는 dispatchApplyWindowInsets로 호출됩니다.

View.java
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
    try {
        mPrivateFlags3 |= PFLAG3_APPLYING_INSETS;
        if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {
            return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this, insets);
        } else {
            return onApplyWindowInsets(insets);
        }
    } finally {
        mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS;
    }
}

보시다시피, 리스너가 등록되면 OnApplyWindowInsetsListener의 onApplyWindowInsets가 호출되고 등록되지 않은 경우 해당 View의 onApplyWindowInsets가 호출됩니다.

즉, 그 View 가 onApplyWindowInsets 에 있는 본래 호출되어야 할 처리가 청취자를 등록한 것으로 실행되지 않게 됩니다.
그리고 DecorView는 onApplyWindowInsets에 확실히 본래 실행되어야 하는 처리가 있고, 여기서 navigationBarBackground와 statusBarBackground와 같은 View를 만들어 추가하는 처리도 있습니다.

DecorView.java
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
    final WindowManager.LayoutParams attrs = mWindow.getAttributes();
    mFloatingInsets.setEmpty();
    if ((attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0) {
        // For dialog windows we want to make sure they don't go over the status bar or nav bar.
        // We consume the system insets and we will reuse them later during the measure phase.
        // We allow the app to ignore this and handle insets itself by using
        // FLAG_LAYOUT_IN_SCREEN.
        if (attrs.height == WindowManager.LayoutParams.WRAP_CONTENT) {
            mFloatingInsets.top = insets.getSystemWindowInsetTop();
            mFloatingInsets.bottom = insets.getSystemWindowInsetBottom();
            insets = insets.inset(0, insets.getSystemWindowInsetTop(),
                    0, insets.getSystemWindowInsetBottom());
        }
        if (mWindow.getAttributes().width == WindowManager.LayoutParams.WRAP_CONTENT) {
            mFloatingInsets.left = insets.getSystemWindowInsetTop();
            mFloatingInsets.right = insets.getSystemWindowInsetBottom();
            insets = insets.inset(insets.getSystemWindowInsetLeft(), 0,
                    insets.getSystemWindowInsetRight(), 0);
        }
    }
    mFrameOffsets.set(insets.getSystemWindowInsetsAsRect());
    insets = updateColorViews(insets, true /* animate */);
    insets = updateStatusGuard(insets);
    if (getForeground() != null) {
        drawableChanged();
    }
    return insets;
}

DecorView에 OnApplyWindowInsetsListener를 등록하면, 본래 실행될 것이었던, 이러한 처리가 실행되지 않게 되기 때문에 이상해지고 있었던 것이군요. 이것은 DecorView 뿐만이 아니라, android:fitsSystemWindows 의 처리 등, onApplyWindowInsets 로 실행될 어떠한 처리가 있는 경우도 마찬가지입니다.

해결 방법



OnApplyWindowInsetsListener에서 정보를 얻으면서 원래 수행해야 할 작업을 수행하려면 OnApplyWindowInsetsListener에서 onApplyWindowInsets를 호출합니다.
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, insets ->
    // ごにょごにょ
    WindowInsetsCompat.toWindowInsetsCompat(view.onApplyWindowInsets(insets.toWindowInsets()))
}

ViewCompat 를 사용하고 있는 경우 WindowInsetsCompat 를 WindowInsets 로 변환해 건네주어, 반환값의 WindowInsets 를 WindowInsetsCompat 로 변환하지 않으면 안 되므로 기술이 길어집니다.

이렇게 하면 본래의 처리를 방해하지 않도록 정보만 집어낼 수 있게 됩니다.


아무것도
OnApplyWindowInsetsListener 세트






리스너를 등록하면 그 View가 가지고 있는 처리보다, 리스너 콜이 우선된다고 하는 동작은 OnTouchListener 등도 같은 거동이지만, 안의 동작을 이해하고 있지 않으면 꽤 당황하게 되면 생각합니다. 라고 할까, 제가 당황했습니다.

이상입니다.

좋은 웹페이지 즐겨찾기