Takeshi hagikura cover

안드로이드의 새로운 레이아웃 탐구서

이 강연에서 Takeshi Hagikura 는 구글이 2016년에 새롭게 선보인 레이아웃인 ConstraintLayout과 FlexboxLayout에 대해 이야기하고 있습니다. 이 강연은 안드로이드 view, 기존 레이아웃의 문제점, 기존 레이아웃의 문제를 해결하기 위해 개발된 새로운 레이아웃에 대해 다루고 있습니다.


소개 (0:00)

저는 구글의 Developer Relation Team에서 근무 중인 Takeshi Hagikura이며, 이번 강연에서는 새로운 안드로이드 레이아웃에 대해 이야기하고자 합니다.

현재 안드로이드 프레임워크에서 제공 중인 레이아웃은 다음과 같습니다:

간단한 것

  • LinearLayout
  • FrameLayout
  • TableLayout

복잡한 것

  • GridLayout
  • RelativeLayout

여러분은 이들 레이아웃을 사용하여 화면을 구성할 수 있지만, 최적화된 레이아웃을 구성하는 작업은 매우 힘든 일입니다. 또한, 레이아웃을 구성할 때는 다양한 화면 크기, 디바이스 오리엔테이션, 언어 지원 문제 역시 고려해야만 합니다. 이러한 문제를 해결하기 위해, 구글에서는 2016년에 새로운 레이아웃을 발표했습니다. 바로 ConstraintLayout 과 FlexboxLayout 입니다.

안드로이드 view (1:55)

16 밀리 초(milliseconds)

이는 안드로이드에서 중요하게 생각하는 숫자입니다. 인간은 시각적으로 초당 60프레임일 때 움직임이 부드럽다고 느낀다고 합니다. 초당 60프레임을 달성하기 위해서는 각 프레임에서 16 밀리 초 또는 16 밀리 초 미만을 사용해야 합니다.

안드로이드가 view를 화면에 그리는 방법

3가지 단계를 거쳐 view가 화면에 나타납니다 :

  1. 측정(Measurement)
  2. 레이아웃(Layout)
  3. 그리기(Draw)

측정 단계에서는 view의 크기를 결정합니다. 측정은 루트(root) 노드에서 시작해서 반복적으로 호출되며, 각각의 호출은 부모로부터 전달된 인자들과 함께 발생합니다. 이 인자들은 widthMeasureSpec, heightMeasureSpec이라는 이름으로 전달됩니다.

다음 단계인 레이아웃 단계에서는 각각의 view 크기를 기준으로 view의 위치를 결정합니다. 루트 노드에서 시작해서 leaf 노드까지 반복적으로 호출됩니다.

그리기 단계는 스크린에 자신을 그리는 단계입니다. RelativeLayout이 자신을 화면에 그리라는 요청을 받게 되면, 자식(children)들에게 자신들을 측정(measure)하라는 메시지가 전달되며 모든 view가 측정될 때까지 이러한 과정이 계속됩니다. 중첩된 레이아웃이 많을수록 측정하는데 걸리는 시간도 늘어나게 됩니다.

이런 개발 뉴스를 더 만나보세요

기존 레이아웃을 이용한 레이아웃 구성 방식에는 단점이 있는데, RelativeLayouts의 경우 자식을 두 번 측정하는 문제가 존재하며, LinearLayouts 역시 자식이 layout_weight 속성을 가지고 있는 경우 자식을 두 번 측정하는 문제가 있습니다. 이러한 것들은 최적화된 레이아웃이라고 말할 수 없지요.

ConstraintLayout (10:05)

ConstraintLayout은 RelativeLayout과 유사하며, 안드로이드 스튜디오 레이아웃 편집기에서 ConstraintLayout의 모든 기능을 사용할 수 있습니다.

ConstraintLayout에서 자식들은 constraint를 기준으로 배치됩니다.

Constraint

constraint는 view의 4개 handle 또는 baseline과의 관계입니다. ConstraintLayout에서는 레이아웃 편집기에서 view를 추가했을 때, view의 각 가장자리에 네 개의 handle이 표시되는 것을 볼 수 있습니다. 만약 추가된 view가 TextView라면 text의 하단에 baseline도 함께 표시될 것입니다.

ConstraintLayout에서 constraint를 생성하려면 레이아웃 편집기에서 view를 추가해야 합니다. view의 왼쪽 핸들을 부모 쪽으로 드래그하면, view의 왼쪽과 부모 사이에 constraint가 설정됩니다.

view는 반드시 가로와 세로 방향으로 2개 이상의 constraint가 있어야 합니다. 이렇게 설정하지 않을 경우, view는 자신의 위치를 알 수 없으므로 기본 위치(default position)에 위치하게 됩니다.

평평한 구조 (14:00)

ConstraintLayout의 장점은 평평한(flattened) 계층 구조를 유지하면서도 복잡한 레이아웃을 작성할 수 있다는 점입니다. 앞에서 보여준 예제는 전통적인 레이아웃 시스템을 이용해 작성한 것인데, ConstraintLayout를 이용한다면 중첩 단계(nested level)를 8개에서 2개까지 줄일 수 있습니다.

중첩된 레이아웃을 줄이면 성능을 향상할 수 있습니다. 각 단계에서 걸리는 시간을 측정해보았는데, 측정 단계에서 ConstraintLayout 는 0.5ms, 전통적인 레이아웃은 1.5ms를 기록했습니다.

또한, 레이아웃과 그리기 단계에서도 ConstraintLayout 을 사용했을 때 좀 더 성능이 좋았습니다.

체인 (17:54)

체인은 하나의 축(axis)에서 하나의 그룹처럼 동작하는 것이라고 말할 수 있습니다. 체인을 이용하면 view를 가로 또는 세로 방향으로 배치할 수 있습니다. ConstraintLayout 안에서 LinearLayout을 사용한 것과 같은 효과를 거둘 수 있으며 체인을 생성하더라도 계층 구조는 평평하게 유지할 수 있습니다.

체인은 쉽게 생성할 수 있습니다. 안드로이드 스튜디오에서 그룹으로 묶을 위젯들을 선택한 다음 마우스 오른쪽 버튼을 눌러서 center horizontally를 선택하면 됩니다. 체인 모양의 아이콘을 선택하여 체인 형태를 변경할 수도 있습니다. weighted chain 을 만들기 위해서는, layout weight를 0dp로 설정하여 남은 공간을 채울 수 있게 해야 합니다. packed chain 은 슬라이딩 바를 이용해 bias를 변경할 수 있습니다.

Aspect Ratio (20:00)

width와 height 간의 비율을 설정할 수 있는 기능으로, 16:9, 1:1과 같은 형태로 width와 height 간의 비율을 설정할 수 있습니다. aspect ratio를 사용하려면, width 또는 height를 0dp로 설정해야 합니다. 그런 다음 좌측 상단을 클릭하여 aspect ratio 기능을 사용할 수 있습니다.

API를 이용한 Constraints 생성 (21:18)

ConstraintLayout에서는 ConstraintSet이라는 클래스가 새롭게 소개되었습니다. 일련의 Constraint를 코드로 생성하여 사용할 수 있고, 애니메이션도 쉽게 만들 수 있습니다.

ConstraintSet은 3가지 방법의 하나를 이용해 만들 수 있습니다:

  1. ConstrainSet을 생성한 다음 connect 메서드 호출로 constraint 생성
  2. 레이아웃 파일 복제
  3. 기존 ConstraintLayout 복제
ConstraintSet c;
// Manually
c = new ConstraintSet(); c.connect(....);

// from a R.layout.* object
c.clone(context, R.layout.layout1);

// from a ConstraintLayout
c.clone(clayout);

ConstraintSet을 이용해 애니메이션을 처리하는 예제를 살펴보도록 하겠습니다. 우선, ConstraintLayout에 대한 참조를 얻습니다. 그런 다음, ConstraintSet을 생성하고 ConstraintLayout에 포함된 Constraints를 복제합니다. 추가적인 ConstraintSet을 레이아웃 XML 파일을 통해 생성하고, TransitionManager의 beginDelayedTransition 메서드를 호출합니다. 예제에서는 ConstraintSetBig constraint를 rootLayout에 적용했습니다.


	ConstraintLayout rootLayout = (ConstraintLayout)
        findViewById(R.id.activity_constraintset_example);
	ConstraintSet constraintSet = new ConstraintSet();
	constraintSet.clone(rootLayout);
	ConstraintSet constraintSetBig = new ConstraintSet();
	constraintSetBig.load(this, R.layout.constraintset_example_big);
	
	ransitionManager.beginDelayedTransition(rootLayout);

	constraintSetBig.applyTo(rootLayout);

	// constraintSet.applyTo(rootLayout);
  

ConstraintLayout은 현재 1.0.2 버전입니다. 베타 버전을 거쳐 제품에 사용할 수 있는 상태가 되었습니다. d.android.com에서는 ConstraintLayout에 대한 가이드 문서를 제공하고 있습니다. github 에서 체인, ConstraintSet과 같은 새로운 기능에 대한 예제를 제공하고 있으니 참고하시기 바랍니다.

FlexBoxLayout (25:30)

FlexBox Layout은 Flexible Box Layout Module이라고 불리는 CSS 설명서(specification)로 웹 개발자들에게 좀 더 유연한 레이아웃을 제공하기 위해 만들어졌습니다.

CSS 애플리케이션에서 FlexBox Layout이 제공하는 기능을 Android에서 구현한 것이 바로 FlexboxLayout입니다. 아래 예제 코드를 보시면, CSS에서 Flexbox 속성을 정의하는 것처럼 XML에서 Flexbox 속성을 정의할 수 있습니다. FlexboxLayout은 향상된 LinearLayout이라고 말할 수 있는데, 그 이유는 이들 레이아웃이 그들의 자식을 차례로 배치하기 때문입니다.

Flexbox에서는 자식들이 배치된 방향을 Main axis 라고 하고, Main axis와 수직 방향은 Cross axis 라고 부릅니다.

CSS


	.container {
		display: flex;
		flex-direction: row;
		justify-content: flex-end;
	}

	.item {
		flex-grow: 1;
	}

FlexboxLayout

	<com.google.android.flexbox.FlexboxLayout
		app:flexDirection=”row”
		app:justifyContent=”flex_end”>

	  <View
    	app:layout_flexGrow=”1”>
	</com.google.android.flexbox.FlexboxLayout>
  

flexDirection

flexDirection 은 main axis와 cross axis 방향을 변경합니다. Flex Direction을 row로 설정하면, 자식들이 가로 방향으로 배치되고, column으로 설정하면 자식들은 세로 방향으로 배치됩니다. LinearLayout의 orientation과 비슷하다고 생각하시면 됩니다.

justifyContent

justifyContent 는 main axis를 따라 배치된 정렬 상태를 변경할 수 있으며, 왼쪽으로 정렬하거나 가운데 또는 뒤쪽으로 정렬할 수 있습니다.

flexWrap

flexWrap 을 통해 Flexbox를 멀티 라인 레이아웃으로 구성할 것인지, 싱글 라인으로 표현할 것인지 정할 수 있습니다. 이 점이 LinearLayout과 FlexboxLayout의 가장 큰 차이점입니다. flexWrap을 wrap으로 설정하면, 현재 라인에 충분한 공간이 없는 경우 다음 라인에 view를 배치합니다.

예제

LinearLayout을 이용해 다이얼로그 레이아웃을 구성한다면 레이아웃 XML 파일은 아래와 같은 모습일 것입니다.


	<ScrollView>
	  <LinearLayout
    	android:orientation=”vertical”>
    	<TextView />
    	<TextInputLayout />
    	<TextInputLayout />
    	<TextInputLayout />
    	<TextInputLayout />
    	<TextInputLayout />
    	<TextInputLayout />
    	<TextInputLayout />
    	<TextInputLayout />
    	...
	  </LinearLayout>
	</ScrollView>
  

FlexboxLayout을 사용하면 아래와 같은 형태입니다.


	<ScrollView>
	  <com.google.android.flexbox.FlexboxLayout>
    	<TextView />
    	<TextInputLayout />
    	<TextInputLayout />
    	<TextInputLayout />
    	<TextInputLayout />
    	<TextInputLayout />
    	<TextInputLayout />
    	<TextInputLayout />
    	<TextInputLayout />
    	...
	  </com.google.android.flexbox.FlexboxLayout>
	</ScrollView>

또한, Flexbox를 이용해 반응형 레이아웃 을 구성할 수 있습니다. 예를 들면, 앞에서 살펴본 예제의 경우에서 기기를 landscape 모드로 전환했을 때 다이얼로그 레이아웃이 자동으로 landscape 모드에 맞게 변경되는 것이지요. 예제에서는 portrait 모드와 비교했을 때 width와 height에 해당하는 부분의 위치가 변경되었고 landscape 모드 화면에 맞게 잘 배치된 것을 확인할 수 있습니다.

멀티 윈도 모드를 활성화한 경우에서도 다이얼로그 예제의 레이아웃은 잘 표현되고 있습니다. 만약 같은 기능을 LinearLayout을 이용해 구성하려고 했다면 수많은 레이아웃 파일들이 필요할 것입니다.

flexWrap을 wrap으로 설정하면, view는 다음 라인에 배치됩니다. layout_flexGrow 설정도 있는데 LinearLayout의 layout_weight 와 유사한 기능입니다. 남은 공간을 각각의 view에 똑같이 배정해서 최종적인 레이아웃을 좀 더 멋지게 만들어 줍니다.

RecyclerView 통합 (34:21)

RecyclerView는 ListView의 계승자로서 다수의 view 집합을 제어합니다. RecyclerView를 이용하면 데이터셋과 데이터셋에 대한 레이아웃을 독립적으로 관리할 수 있습니다.

FlexboxLayoutManager는 현재 알파 버전이 나온 상태이며, RecyclerView에서 제공하는 기본 layout manager와 유사합니다.

XML 레이아웃 파일 이외에도 코드에서 Flexbox 속성을 정의하여 FlexboxLayout을 사용할 수 있습니다. 아래의 예제는 새로운 FlexboxLayoutManager를 생성하고 flexWrap 속성을 설정하는 코드입니다.


	FlexboxLayoutManager layoutManager = new FlexboxLayoutManager();
	layoutManager.setFlexWrap(FlexWrap.WRAP);

자식에 대한 속성은 FlexboxLayoutManager.LayoutParams를 이용해 설정할 수 있습니다.


	mImageView.setImageDrawable(drawable);
	ViewGroup.LayoutParams lp = mImageView.getLayoutParams();
	if (lp instanceof FlexboxLayoutManager.LayoutParams) {
		FlexboxLayoutManager.LayoutParams flexboxLp = 
      		(FlexboxLayoutManager.LayoutParams) mImageView.getLayoutParams();
		flexboxLp.setFlexGrow(1.0f);
	}

FlexboxLayout을 이용하면 ScrollView 안에 스크롤 가능한 flexible container를 만들 수 있지만, 아직은 view를 재활용하는 기능이 안정적으로 개발되지 않았기 때문에 많은 양의 데이터셋을 처리해야 할 때에는 FlexboxLayout를 이용하지 않는 것이 좋습니다.

FlexboxLayout는 안정 버전과 알파 버전이 제공됩니다. 알파 버전은 앞에서 설명한 RecyclerView 통합이 포함되어 있습니다. 알파 버전은 아직 제품에 바로 활용할 수 있는 수준으로 개발되지 않았기 때문에 RecyclerView 통합을 적용하고 싶은 개발자들은 안정 버전에 이 기능이 적용될 때까지 기다려주시기 바랍니다.

FlexboxLayout 에 대한 추가 정보는 github블로그에서 확인하시길 바랍니다.

질의응답 (40:33)

언제 ConstraintLayout 대신 FlexboxLayout을 사용해야 하나요? 또한 FlexboxLayout을 사용하지 말아야 하는 경우가 있나요?*

대부분은 ConstraintLayout을 사용할 것을 추천합니다. 반응형 레이아웃이 필요한 경우는 FlexboxLayout이 효과적이겠지요. ConstraintLayout에는 FlexboxLayout의 wrapping과 같은 기능이 없으므로, 여러 가지 기기를 대상으로 레이아웃을 제공해야 하는 경우 FlexboxLayout가 유용할 것입니다.

LinearLayout과 비교했을 때 FlexboxLayout의 성능은 어떤가요?

정확하게 측정해 보지는 않았지만, FlexboxLayout과 LinearLayout의 성능은 비슷하다고 생각합니다.

ConstraintLayout을 사용하지 말아야 하는 경우가 있나요?

대부분의 경우 ConstraintLayout을 사용하는 것이 효율적이라고 생각합니다. 반응형 레이아웃이 필요한 경우라면 FlexboxLayout을 이용하는 것이 효과적이겠지요.

다음: RecyclerView와 Realm으로 만드는 Grid Layout

General link arrow white

컨텐츠에 대하여

2017년 3월에 진행한 DroidKaigi 행사의 강연입니다. 영상 녹화와 제작, 정리 글은 Realm에서 제공하며, 주최 측의 허가 하에 이곳에서 공유합니다.

Takeshi Hagikura

Takeshi Hagikura는 Google의 Developer Relations팀에서 일하는 엔지니어입니다.

4 design patterns for a RESTless mobile integration »

close