안드로이드 UI는 여러 개의 View(뷰)로 구성되며, 일반적인 애플리케이션에는 그 수가 상당히 많습니다. 사용자가 지금 화면에서 어떤 뷰를 보고 있는지 파악하려면 Visibility Listener(가시성 리스너)를 활용해야 합니다.
아래에서 뷰의 가시성 상태를 식별할 수 있는 다양한 방법을 소개합니다.
레이아웃에 뷰 추가하기
리스너가 정상적으로 동작하려면 먼저 해당 뷰가 레이아웃 계층 구조 안에 존재해야 합니다. 뷰가 레이아웃에 포함되는 방식은 두 가지입니다.
- XML 파일에 정의된 대로 레이아웃에 이미 포함되어 있는 경우
- 코드에서 뷰를 동적으로 생성한 뒤 addView 메서드로 추가하는 경우
public void addView (View child, ViewGroup.LayoutParams params)여기서 child는 추가하려는 뷰를 의미하고, LayoutParams는 레이아웃 매개변수(예: 가로·세로 크기)를 나타냅니다.
뷰의 가시성 상태는 Integer 타입이며 다음 세 가지 값 중 하나를 가집니다.
- VISIBLE (0) – 사용자에게 뷰가 보이는 상태
- INVISIBLE (4) – 사용자에게는 보이지 않지만, 레이아웃에서 공간은 그대로 차지하는 상태
- GONE (8) – 보이지 않으며, 레이아웃에서도 공간을 차지하지 않는 상태
뷰가 레이아웃 계층에 포함된 이후에는, 가시성 변경 시점을 감지할 수 있도록 플랫폼에서 기본으로 제공하는 몇 가지 옵션을 사용할 수 있습니다.
onVisibilityChanged
protected void onVisibilityChanged (View changedView, int visibility)이 메서드는 해당 뷰 자신 또는 그 조상(부모) 뷰의 가시성이 변경될 때 호출됩니다. 변경된 가시성 값은 visibility 매개변수로 전달됩니다.
onWindowVisibilityChanged
protected void onWindowVisibilityChanged (int visibility)이 메서드는 뷰가 속한 윈도우(Window)의 가시성이 변경될 때 호출됩니다. 다만 윈도우가 가시 상태라고 해도, 다른 윈도우에 가려져 실제로는 사용자에게 보이지 않을 수 있다는 점에 유의해야 합니다.
실전 예제: 리스너 동작 확인하기
두 리스너의 동작을 직접 확인하기 위해 간단한 프로젝트를 만들어 보겠습니다. TextView와 버튼 하나를 담은 LinearLayout을 준비하고, 버튼을 클릭하면 커스텀 뷰가 레이아웃에 추가되도록 구현합니다.

커스텀 뷰 코드는 다음과 같습니다.
package com.tomerpacific.viewvisibility;
import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import static android.view.Gravity.CENTER;
public class MyCustomView extends LinearLayout {
private final String TAG = MyCustomView.class.getSimpleName();
public MyCustomView(Context context) {
super(context);
this.setBackgroundColor(Color.GREEN);
this.setGravity(CENTER);
TextView myTextView = new TextView(context);
myTextView.setText("My Custom View");
addView(myTextView);
}
@Override
public void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
Log.d(TAG, "View " + changedView + " changed visibility to " + visibility);
}
@Override
public void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
Log.d(TAG, "Window visibility changed to " + visibility);
}
}마지막으로 MainActivity의 코드입니다.
package com.tomerpacific.viewvisibility;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
public class MainActivity extends AppCompatActivity {
private Button addCustomViewBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
addCustomViewBtn = (Button) findViewById(R.id.addCustomViewBtn);
addCustomViewBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
LinearLayout mainLayout = (LinearLayout) findViewById(R.id.mainLayout);
MyCustomView myCustomView = new MyCustomView(getApplicationContext());
myCustomView.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
mainLayout.addView(myCustomView);
}
});
}
}위 코드에서는 버튼에 OnClick 리스너를 연결하여, 클릭 시 커스텀 뷰가 생성되어 레이아웃에 추가되도록 했습니다.
앱을 실행하고 버튼을 누르면 로그를 통해 가시성 변경 이벤트가 어떻게 발생하는지 확인할 수 있습니다.
전체 샘플 프로젝트는 원문의 다운로드 링크에서 받을 수 있습니다.
ViewTreeObserver 활용하기
ViewTreeObserver는 뷰 트리에서 발생하는 다양한 가시성 변화를 통지받을 수 있는 여러 리스너를 제공하는 기본 객체입니다. 특히 주목할 만한 리스너는 다음과 같습니다.
- OnGlobalLayoutListener
- OnWindowAttachListener
- OnWindowFocusChangeListener
ViewTreeObserver를 등록하는 방법은 다음과 같습니다.
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.YOUR_VIEW_ID);
ViewTreeObserver viewTreeObserver = linearLayout.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener (new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
linearLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
//TODO Add Logic
}
});참고로 removeOnGlobalLayoutListener는 API 15 이상에서 사용할 수 있습니다.
linearLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this) 코드는 리스너가 단 한 번만 호출되도록 보장하는 역할을 합니다. 레이아웃 변경 사항을 계속해서 감지하고 싶다면 이 줄을 제거하면 됩니다.
의견이나 제안이 있다면 언제든지 알려주세요.