Layout Inflation as Intended

inflate method의 viewGroup 파라미터

CCanary Park
7 min readMay 24, 2014

원본보기

LayoutInflater는 Android SDK에서 흔하게 사용된다. 하지만 놀랍게도 이것은 잘 못된 방식으로 사용될 수 있고, 당신의 어플리케이션이 여기에 해당될 수도 있다. 당신의 안드로이드 어플리케이션에서 아래와 같이 LayoutInflater를 사용하는 코드가 있었다면 말이다.

inflater.inflate(R.layout.my_layout, null);

Get to Know LayoutInflater

먼저 LayoutInflater가 어떻게 동작하는지 알아보자. 표준적인 어플리케이션에서 사용 가능한 inflate()메소드는 두가지가 있다.

inflate(int resource, ViewGroup root)inflate(int resource, ViewGroup root, boolean attachToRoot)

첫 번째 파라미터는 당신이 inflate하고 싶은 layout 리소스를 가르킨다. 두 번째 파라미터는 당신이 inflate하고 있는 리소스가 부착될 루트 뷰이다. 세번째 파라미터가 존재한다면, 이것은 inflate된 뷰가 제공된 루트 뷰에 부착될 지 아닐지를 결정한다.

마지막의 두 파라미터가 약간의 혼동을 일으킬 수 있다. 이 메소드의 두개의 파라미터를 가진 버전에서는 LayoutInflater는 자동으로 inflate된 뷰를 제공받은 루트에 부착시키려고 한다. 프레임워크는 어플리케이션 크래쉬를 피하기 위해서 당신이 root를 null로 보냈는지 체크한다.

많은 개발자들이 루트를 null로 보내는 동작을 inflation중에 부착(attachment)되지 않게하는 적절한 방식이라고 오해하고 있다; 많은 경우 세개의 파라미터 버전의 inflate()가 존재하는 지 조차 알지 못하고 있다.

Examples from the Framework

안드로이드에서 프레임워크가 개발자와 상호 작용을 하면서 뷰의 일부를 inflate하기를 요구하는 특정 상황을 살펴 보자.

Adapters는 LayoutInfalter를 사용하는 가장 흔한 경우이다. LayoutInflater는 커스텀 ListView 어뎁터에서 오버라이딩하는 getView() 메소드에서 사용된다. getView()는 다음과 같은 메소드 시그네처를 가진다.

getView(int position, View convertView, ViewGroup parent)

Fragments은 아래 메소드 시그네처를 가지는 onCreateView()에서 뷰를 생성할 때 inflation을 사용한다.

onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)

프레임워크는 당신이 layout을 inflate하려 할 때마다 layout이 부착 될 부모 ViewGroup을 넘겨주는 것을 알아차렸는가? 위 두 예제를 포함한 대부분의 경우 LayoutInflater가 inflate된 뷰를 루트에 자동으로 부착하게 할 경우 Exception을 던질 것 역시 알아야 한다.

왜 우리가 부착하지도 않을 ViewGroup를 받을 거라고 가정하는가? inflate될 XML의 루트 element안에 선언된 LayoutParams의 값을 구하기 위해서는 부모 뷰가 필수이므로 부모 뷰는 inflation 과정에서 가장 중요한 부분이다. 아무 것도 전달하지 않는 것은 프레임워크에게 "나는 이 뷰가 부착될 부모가 무엇인지 몰라. 미안"이라고 말하는 것과 마찬가지이다.

이 것과 관련된 문제는 android:layout_xxx 속성들은 언제나 부모 뷰의 context에서 값을 구하는 것이다. 결과적으로 부모를 모르면 XML 트리의 루트 element에 선언된 모든 LayoutParam들이 버려질 것이고 "왜 프레임워크는 내가 정의한 layout customizations을 무시하는 것까? SO를 확인하고 버그를 보고하는 것이 좋겠다"라고 물어보는 것만 남을 것이다.

LayoutParams가 없으면, inflate된 레이아웃의 주인이 될 ViewGroup은 기본 셋만 남게 될 것이다. 만약 운이 좋다면 (많은 경우) 기본 파라미터들은 당신이 XML에서 가진 것과 동일할 것이며.. 무엇인가 잘 못 되었다는 것을 숨기게 된다.

Application Example

어플리케이션에서 이런 현상을 한 번도 본적이 없는가? ListView row로 inflate할 다음의 간단한 layout을 확인하라.

R.layout.item_row

우리는 row의 높이를 고정된 높이, 이 경우 현재 테마의 prefered item height로 설정되기를 원하고, 이 것은 합당한 듯 하다.

하지만 잘못된 방식으로 inflate하면

다음과 같은 결과가 된다.

우리가 설정했던 고정 높이에 무슨 일이 닥쳤는가?? 보통 모든 자식 뷰들에게 고정된 높이 값을 설정하고, 루트 element들의 높이를 wrap_content로 바꾸고는 왜 이것이 동작하지 않는지 알지 못하는 체로 넘어가며 끝날것이다. (아마 이 과정에서 구글을 저주하였을 것이다.)

같은 레이아웃을 아래 방식으로 inflate하면

public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflate(R.layout.item_row, parent, false);
}
return convertView;
}

우리가 먼저 기대했던 결과를 얻을 것이다.

Hooray!

Every Rule Has An Exception

inflation동안 부모가 null인 경우가 정당화 될 수 있는 경우도 물론 있다. 이런 경우 중 하나는 커스텀 레이아웃을 AlertDialog에 부탁할 때이다. 동일한 XML 레이아웃을 dialog view에서 사용하는 다음 예제를 보자.

이 이슈는 AlertDialog.Builder가 커스텀 뷰를 지원하지만 레아이웃 리소스를 가지는 setView()를 구현하지 않았기 때문이다; 그래서 XML를 수동으로 inflate해야 한다. 하지만 결과는 루트 뷰를 노출하지 않는 (사실은 아직 존재하지 않는) dialog에 들어가기 때문에 우리는 레이아웃의 최후의 부모에게 접근할 수 없고, 그래서 우리는 이 것을 inflation을 위해 사용할 수 없다. AlertDialog는 레이아웃의 모든LayoutParams를 지우고 match_parent로 바꿀 것이기 때문에 null을 보내는 것이 문제가 되지 않는다.

다음 번에는 당신 손가락이 inflate()안에 null을 입력하려고 할 면 멈추고 스스로에게 물어봐야 한다 "나는 정말 이 뷰가 결국 어디에 있을지 모르는가?"

핵심은 당신은 두개의 파라미터를 가진 버전의 inflate()를 세번째 파라미터가 true인 것이 생략된 편리한 숏컷으로 생각해야 한다는 것이다. null을 보내는 것을 false가 생략된 편리한 숏컷으로 생각하면 안된다.

--

--