일반적으로 안드로이드 앱의 레이아웃을 정의하기 위해 XML 레이아웃을 사용한다.
XML 레이아웃은 단순히 XML로 정의된 파일이다. 이 파일엔 뷰의 배치 방식과 속성이 정의되어 있다.

따라서 XML 코드를 작성했다고 바로 화면을 띄우고 앱을 실행할 수는 없다.

 

화면의 구성을 XML로, 기능을 소스 코드로 각각 분리해서 정의했다고 가정해보자.
복수의 파일 중 적절한 XML을 소스 코드에서 어떻게 선택해서 인식할 수 있을까?


소스 코드에 어떤 XML이 지정되어야 하는지 매치하는 과정은 반드시 필요하다.

이 때 등장하는 개념이 레이아웃 인플레이션(Layout Inflation)이다.

 

XML 레이아웃과 Java 코드를 연결하기

안드로이드 스튜디오에서 새로운 프로젝트를 만들면 MainActivity.java가 자동으로 생성된다.
MainActivity의 OnCreate() 메서드 안에는 setContentView(R.layout.activity_main);이 들어가 있다.

즉, setContentView()는 파라미터로 넘어온 레이아웃과 XML 레이아웃 파일을 연결하기 위해 사용한다.


파라미터로 레이아웃을 지정할 때는 확장자 없이 R.layout.파일명의 형식을 사용한다.

이렇게 XML 레이아웃의 내용이 메모리에 객체화되는 과정인플레이션(Inflation)이라고 한다.
setContentView()를 호출하면 내부적으로 인플레이션 과정이 진행되기 때문에 자바 코드에서 XML에 정의한 뷰들을 사용할 수 있다.

 

setContentView()의 역할

  • 화면에 나타날 뷰를 지정
  • 레이아웃 내용을 메모리에 객체화

인플레이션은 앱이 실행되는 시점에 진행된다. 따라서 setContentView()를 호출하기 전에 XML에 정의된 뷰를 참조하게 되면 NPE가 발생하며 앱이 강제로 종료된다.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Button mEmailSignInButton = (Button) findViewById(R.id.sign_in_button);

    mEmailSignInButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            attemptLogin();
        }
    });

    setContentView(R.layout.activity_login);
}

setContentView()는 전달받은 레이아웃 안에 들어있는 뷰들을 메모리에 올린다.
메모리에 올라간 뷰를 소스 코드에서는 id를 통해 참조할 수 있다. 객체를 찾을 때는 findViewById() 메서드를 이용할 수 있다.
XML에 뷰를 추가할 때 넣어둔 id 속성의 값을 파라미터로 사용한다.

Button button = (Button) findViewById(R.id.sign_in_button);

 

별도의 XML 레이아웃 파일을 불러오기

XML로 화면의 일부분을 차지하는 부분 화면을 따로 정의할 수 있다. setContentView()는 화면 전체를 설정하는 역할만을 수행하기 때문에 부분 화면을 불러올 때는 사용할 수 없다.
화면 전체에 뿌려줄 XML 레이아웃이 아닌 별도의 XML 파일을 Java 코드에서 사용하려면 어떻게 해야할까?

 

안드로이드는 인플레이션 처리를 할 XML을 직접 지정할 수 있도록 LayoutInflater라는 클래스를 제공한다.


LayoutInflater는 시스템 서비스로 제공하는 클래스이므로 getSystemService()라는 메서드를 이용해 LayoutInflater 객체를 참조한 후 사용해야 한다.

LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);

아니면 LayoutInflater 클래스의 from() 메서드를 호출하여 참조할 수도 있다.

LayoutInflater inflater = LayoutInflater.from(getApplicationContext());

inflate()의 첫번째 파라미터로 레이아웃 리소스 아이디를, 두번째 파라미터로 부분 화면을 로드할 부모 컨테이너를 지정한다.

container = findViewById(R.id.{부분 화면을 로딩할 뷰의 이름});

LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.{불러올 XML 파일명}, container, true);

아래는 TextView가 든 부분 화면을 로드하는 간단한 예제이다. 버튼을 클릭하면 container로 id가 지정된 LinearLayout에 부분 화면이 로드될 것이다.

  1. activity_main.xml : 전체 화면이 정의된 레이아웃 파일이다.

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
     tools:context=".MainActivity">
    
     <Button
         android:id="@+id/btn_main"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:text="부분 화면 추가하기" />
    
     <LinearLayout
         android:id="@+id/container"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="vertical" />
    
  2. sub.xml : container 자리에 로드될 부분 화면을 정의한 레이아웃 파일이다.

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
    
     <TextView
         android:id="@+id/tv_sub"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content" />
    </LinearLayout>
  3. MainActivity.java : 부분 화면을 inflate 한다.

    public class MainActivity extends AppCompatActivity {
     private Button button;
     private LinearLayout container;
    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         button = findViewById(R.id.btn_main);
         container = findViewById(R.id.container);
    
         button.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
                 LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                 inflater.inflate(R.layout.sub, container, true);
    
                 TextView subTextView = container.findViewById(R.id.tv_sub);
                 subTextView.setText("부분 화면입니다!");
             }
         });
     }
    }

이제 버튼을 클릭한 순간 sub.xml의 내용이 뷰그룹 객체로 메모리에 올라간다.
부분 레이아웃은 container 객체에 설정되었으므로 container 객체에 findViewById()를 사용해야 부분 화면의 뷰를 참조할 수 있다.

 

좌 : 버튼 클릭 전

우 : 버튼 클릭 후

앱이 실행된 모습