우리는 안드로이드에서 Jetpack Compose
를 사용하는 것이 아니라면 보통 XML 형태로 레이아웃을 구상하게 된다. 이 때, 결국 XML 은 마크업 언어에 불과하기 때문에, 이를 기반으로 무언가 동작을 정의하려면 메모리상으로 실체화를 해주어야한다.
LayoutInflater 는 레이아웃 XML 파일을 통해, View 객체를 실체화해주는 역할을 한다. 즉, 화면을 구성하는 XML 리소스를 View 객체로 만들어 반환해주는 역할을 한다. 아래와 같이 말이다.
안드로이드 앱을 개발하다보면 아래 코드와 거의 썸을 타게 된다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
라이프사이클 콜백 메소드? 알겠는데, 대체 이 안의 setContentView()
는 내부적으로 어떻게 동작할까? 대강 레이아웃을 뿌려주는 역할이다 정도로만 알고 있었을 것이다. 이 setContentView()
는 레이아웃을 인플레이팅 시켜주는 역할을 수행한다. 같이 내부구현을 살펴보자.
Window
추상 클래스를 상속받은 PhoneWindow
클래스에서 setContentView()
의 내부 구현을 확인해볼 수 있다. 파라미터로 레이아웃의 고유 ID 를 전달받으면, 아래와 같은 동작을 수행한다.
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
코드를 한 부분씩 떼서 살펴보자.
음, 우선 mContentParent
라는 녀석의 null
체크가 가장 먼저 일어난다. 간략히 설명하자면 인자로 받은 레이아웃 리소스가 Inflate 될 상위 ViewGroup 이라고 볼 수 있다. 이것이 null
이라면 installDecor()
를 호출하여 mContentParent
를 초기화해준다. (자세한 동작은 생략)
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
그리고 만약 mContentParent
가 존재한다면 FEATURE_CONTENT_TRANSITIONS
설정 유무를 체크하고, 만약 설정되어있지 않다면 기존의 mContentParent
에 붙어있던 모든 자식 뷰들을 제거한다.
FEATURE_CONTENT_TRANSITIONS
해당 상수는, 트랜지션 애니메이션을 통해 뷰를 변화시킬 때 설정하게 된다.
/** * Flag for requesting that window content changes should be animated using a * TransitionManager. * * <p>The TransitionManager is set using * {@link #setTransitionManager(android.transition.TransitionManager)}. If none is set, * a default TransitionManager will be used.</p> * * @see #setContentView */ public static final int FEATURE_CONTENT_TRANSITIONS = 12;
계속 각기 다른 페이지를 setContentView()
요청해도 이전 뷰들과 겹치지 않는 이유는 인플레이팅 전에 매번 이렇게 자식 뷰들에 대한 삭제 작업을 해주기 때문이라고 볼 수 있다.
만약 FEATURE_CONTENT_TRANSITIONS
이 설정되어 있다면, Transition
동작을 수행한다. 그게 아니라면 현재 mContentParent
에 인자로 받은 레이아웃 리소스를 inflate
하는 동작을 수행한다.
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
이렇듯 setContentView()
에게 레이아웃의 리소스 ID 를 넘겨주게 되면, inflate()
를 수행하여 XML 리소스를 View 객체로 만드는 동작을 수행하게 된다.
그래서 항상 액티비티를 생성하게 되면 기본 코드로 자리잡고 있었던 것이다.
LayoutInflater 객체는 System Service 객체로 제공되기 때문에, getSystemService
메소드를 통해 갖고올 수 있다.
getSystemService(Context.LAYOUT_INFLATER_SERVICE)
그리고 아래와 같이 활용할 수 있다. 부모 레이아웃으로 활용할 뷰 객체를 정의해두고, inflate()
메소드에 인플레이팅 시킬 레이아웃과 부모 레이아웃 뷰 객체를 넘겨주게 되면 레이아웃 인플레이션이 진행된다.
FrameLayout container = (FrameLayout) findViewById(R.id.container);
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.sub1, container, true);