ViewTreeObserver
View의 높이를 구하기 위해, onCreate() 콜백 메서드에서 getHeight() 혹은 getMeasuredHeight()를 호출하면 0이 반환되곤 합니다.
그 이유는 높이를 얻고자 하는 View가 아직 화면에 그려지기 이전에 호출했기 때문입니다.
ViewTreeObserver를 알기 전에는 자바에서는 Thread, 코틀린에서는 Coroutine을 사용하여 n MS(Millisecond) 만큼 시간이 지났을 때 높이를 얻으라고 했을 것입니다.
하지만 이런 방식은 View가 그려진 정확한 시점을 알지 못하기 때문에, n MS라는 시간 안에 View를 그리지 못하는 상황이나 기기 스펙에서는 똑같이 0을 반환하는 시간차 문제가 발생할 것입니다.
그렇기 때문에 알아야하는 것이 바로 ViewTreeObserver입니다.
ViewTreeObserver는 뷰 트리의 전역 변경 사항을 알릴 수 있는 리스너를 등록하는 데 사용됩니다.
이러한 리스너에는 아래 표와 같이 여러 구조가 있습니다.
리스너(Listener) |
설명(Desc)
|
onDrawListener |
뷰 트리가 그려지려고 할 때 호출될 콜백에 대한 인터페이스 정의입니다.
|
OnGlobalFocusChangeListener |
뷰 트리 내의 포커스 상태가 변경될 때 호출될 콜백에 대한 인터페이스 정의입니다.
|
OnGlobalLayoutListener |
뷰 트리 내 뷰의 가시성 또는 전역 레이아웃 상태가 변경될 때 호출될 콜백에 대한 인터페이스 정의입니다.
|
OnPreDrawListener |
뷰 트리가 그려지려고 할 때 호출될 콜백에 대한 인터페이스 정의입니다.
|
OnScrollChangedListener |
뷰 트리의 무언가가 스크롤되었을 때 호출될 콜백에 대한 인터페이스 정의입니다.
|
OnTouchModeChangeListener |
터치 모드가 변경될 때 호출될 콜백에 대한 인터페이스 정의입니다.
|
OnWindowAttachListener |
뷰 계층 구조가 창에 연결 및 분리될 때 호출될 콜백에 대한 인터페이스 정의입니다.
|
OnWindowFocusChangeListener |
뷰 계층의 창 포커스 상태가 변경될 때 호출될 콜백에 대한인터페이스 정의입니다.
|
사용하고자 하는 용도에 맞게 ViewTreeObserver에 리스너를 등록해주면 됩니다.
예제에서는 포커스 변화 감지를 알려주는 OnGlobalFocusChangeListener()와,
뷰가 다 그려졌을 때를 알려주는 OnGlobalLayoutListener() 를 사용하였습니다.
ImageView imageView = (ImageView)findViewById(R.id.image_view);
ViewTreeObserver imageViewTreeObserver = imageView.getViewTreeObserver();
예시를 들기 위해, ImageView의 크기를 구해보도록 하겠습니다.
우선 ImageView 객체 내에 있는 viewTreeObserver를 가져옵니다.
// ViewTree의 포커스 변경이 있을 경우
imageViewTreeObserver
.addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() {
@Override
public void onGlobalFocusChanged(View oldFocus, View newFocus) {
//필요한 작업
//리스너 해제
imageView.getViewTreeObserver().removeOnGlobalFocusChangeListener(this);
}
});
// ViewTree의 뷰가 그려질 때마다
imageViewTreeObserver
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//뷰의 생성된 후 크기와 위치 구하기
imageView.getWidth();
imageView.getHeight();
imageView.getX();
imageView.getY();
//리스너 해제
imageViewTreeObserver.removeOnGlobalLayoutListener(this);
}
});
ViewTree의 포커스 변경과, 모두 그려진 시점을 파악하기 위해, 이전에 얻은 imageViewTreeObserver 인스턴스에 Listener를 등록해줍니다.
위에서 설명드렸다시피 너비(Width)와 높이(Height)는 View가 모두 그려진 이후에 알 수 있습니다.
따라서 View가 모두 그려진 시점에 호출되는 onGlobalLayout()에서 Width와 Height을 구하면 정상적으로 값이 출력될 것입니다.
가장 중요한 것은 마지막 줄입니다.
리스너를 해제해주지 않으면, onGlobalLayout() 콜백 함수가 무한으로 호출이 됩니다.
따라서 특정한 상황이 아닌 이상, 원하는 높이를 구했다면 꼭 리스너를 해제해야 합니다.
+ 추가로 제가 겪은 문제를 기록하기 위해 글을 남깁니다.
우선, onGlobalLayout()을 호출했는데도, 너비나 높이가 0이 반환되는 경우가 있습니다.
이 경우에는 리스너를 해제하는 부분에 조건을 걸어주면 됩니다.
// ViewTree의 뷰가 그려질 때마다
imageViewTreeObserver
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//뷰의 생성된 후 크기와 위치 구하기
int width = imageView.getWidth();
int height = imageView.getHeight();
//리스너 해제
if(width > 0 || height > 0) {
imageViewTreeObserver.removeOnGlobalLayoutListener(this);
}
}
});
필자와 동일한 문제를 겪지 않으셨으면 하는 바람에 코드를 함께 올립니다.
오늘도 글 읽어주셔서 감사합니다 :D
내용에 오류가 있거나, 질문이 있으신 분들은 댓글을 남겨주시면 감사하겠습니다! 😊
'Android > 스터디' 카테고리의 다른 글
[Android] ConstraintLayout을 사용해야 하는 이유 (1) | 2023.02.22 |
---|---|
[Android] Thread의 interrupt()와 stop() (0) | 2022.05.02 |
쿠키(Cookie)와 세션(Session)을 이용한 로그인 (2) | 2022.01.21 |
[Android] 안드로이드 Strings.xml 국가별 언어 설정 방법 및 국가 코드 정리 (5) | 2021.12.28 |
[Android] RecyclerView LayoutPosition vs AdapterPosition 차이를 알아보자! (0) | 2021.12.22 |