예전에는 안드로이드에서 액티비티 파일을 만들고 xml 파일에 View, ViewGroup들을 정의하고, Activity의 생명주기 함수나 혹은 프래그먼트의 생명주기 함수가 호출되는 것이 가장 중요한 개념이 아닐까 생각했었다.

하지만, 이런 Activity, Fragment의 View, ViewGroup들이 결국 화면에 그려지기 위해서는 분명 화면에 그리는 함수들이 존재할 것이다. 나는 이 개념과 함수들을 부스트 캠프를 준비하면서 알게 되었다.

안드로이드에서 ViewGroup이나 View를 화면에 그릴 때, View Lifecycle의 개념이 중요하다. 이 개념을 아직 완벽하게 이해하진 못했지만, ViewGroup의 성능 측정을 위해서 실험을 하였다. View와 관련된 개념은 이전에 공부한 포스팅에 내용을 추가할 예정이다. 그리고 성능 측정을 위해서 LinearLayout, RelativeLayout, ConstraintLayout의 세가지 ViewGroup을 Custom하여 테스트해보았다.

3개의 ViewGroup의 View 함수들이 어떻게 호출되는지 확인하기 위해서 간단한 CustomView를 만들었다. 이 예제는 박상권님의 블로그를 참고했으며, 참고 링크는 아래에 기재해두었다.

CustomView 만들기

CustomView가 사용되는 경우는 같은 형식의 버튼이 3개라면 하나의 레이아웃 소스 코드를 복사해서 3번 붙여넣기 하면 만들 수 있다. 만약 버튼이 100개라면 100번 정도 복사 / 붙여 넣기 작업을 반복하면 된다.

그런데, padding 값이나 margin값을 바꾸고 싶다면? 100개의 버튼에 대해서 똑같은 작업을 반복하는 지옥에 빠지게 된다.(상상만 해도 끔찍하다.) 이러한 경우 CustomView를 만들어서 이를 재사용하는 것이 좋다. 예제로 로그인 버튼 형태의 레이아웃을 유지하고 아이콘과 텍스트만 변경하도록 구성해보겠다.

위와 같이 custom하게 만든 속성을 이용해서 배경색, 아이콘 이미지, 텍스트, 텍스트 색상 등을 지정할 수 있다. 복잡한 레이아웃 구성도 하나의 CustomView로 만들어 준뒤 해당 CustomView를 재사용하면 편리하고 유용하게 화면을 구성할 수 있다.

1. layout xml파일 생성하기

CustomView의 기본으로 사용될 layout xml 파일을 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/bgLinear"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="55dp"
android:background="@color/kakao_color"
android:layout_gravity="center">

<ImageView
android:id="@+id/symbolLinear"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:src="@drawable/kakao"/>


<TextView
android:id="@+id/textLinear"
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center"
android:text="카카오톡 로그인"
android:textColor="@color/text_color"
android:textStyle="bold"
android:layout_height="match_parent"/>


</LinearLayout>

2. attrs.xml 설정

custom하게 만들어 줄 atrribute를 설정해준다. 해당 파일은 value -> attrs.xml 파일을 만들고 이 안에 내용을 추가해준다. 추가해주는 속성은 CustomView를 사용할 xml에서 속성을 지정하기 위함이다. 즉, 내가 넣고자 하는 속성을 넣어서 마음대로 만들 수 있다는 뜻이다.

이 attr 속성을 보았을 때 4가지를 나중에 속성값으로 받아서 적용할 수 있게 할 것이다. 이 attr은 나중에 app:text=" … " 로 쓰일 수 있으며 “가나다라” 같은 직접적인 String과 @string/exam 같은 reference를 넣어줄 수도 있다.

3. CustomView 만들기

  1. CustomView의 생성자를 모두 만들고 그 안에서 initView, getAttrs 함수를 호출한다. [주의할 사항은 꼭 커스텀 뷰의 생성자를 모두 생성해야 하는 것이다.]
1
2
3
4
5
6
7
8
9
10
constructor(context: Context, attrs: AttributeSet) : this(context) {
initView()
getAttrs(attrs)
}

constructor(context: Context, attrs: AttributeSet, defStyle: Int) : this(context, attrs) {
initView()
getAttrs(attrs, defStyle)

}
  1. initView()에서는 미리 만들어둔 xml 파일을 할당하고 각각의 view를 설정해준다. inflate를 통해서 xml 파일을 view 객체로 메모리에 올린다. 그리고 addView(view)를 해준다.
1
2
3
4
5
6
private fun initView(){
var inflaterService : String = Context.LAYOUT_INFLATER_SERVICE
var inflater : LayoutInflater = context.getSystemService(inflaterService) as LayoutInflater
var view = inflater.inflate(R.layout.login_button_linear, this, false)
addView(view)
}
  1. getAttrs() 함수를 통해서 attrs.xml 파일에 선언해둔 attribute를 가지고 오는 작업을 수행한다. 그리고 setTypeArray() 함수로 넘겨준다.
1
2
3
4
5
6
7
8
9
private fun getAttrs(attrs : AttributeSet, defStyle: Int){
var typedArray : TypedArray = context.obtainStyledAttributes(attrs,R.styleable.LoginButton, defStyle,0)
setTypeArray(typedArray)
}

private fun getAttrs(attrs: AttributeSet){
var typedArray : TypedArray = context.obtainStyledAttributes(attrs,R.styleable.LoginButton)
setTypeArray(typedArray)
}
  1. setTypeArray() 함수를 통해서 attrs.xml 파일에 정의한 속성을 적용해서 layout이 가지고 있는 view에게 값을 설정해주는 작업을 수행한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private fun setTypeArray(typedArray: TypedArray){
var backgroundResourceId = typedArray.getResourceId(R.styleable.LoginButton_bg, R.color.naver_color)
bgLinear.setBackgroundResource(backgroundResourceId)

var symbolResourceId = typedArray.getResourceId(R.styleable.LoginButton_symbol, R.drawable.naver)
symbolLinear.setImageResource(symbolResourceId)

var textColor = typedArray.getColor(R.styleable.LoginButton_textColor, 0)
textLinear.setTextColor(textColor)

var textString = typedArray.getString(R.styleable.LoginButton_text)
textLinear.text = textString

typedArray.recycle()
}

느낀점

뷰를 화면에 그릴 때는 View의 Lifecycle이 중요하다. 이는 우리가 구성한 뷰를 사용자의 화면에 보여주기 때문이다. 그리고 렌더링되는 속도는 뷰를 그리는 것과 연관이 되어있다. 그렇다면 View의 Lifecycle을 한 번 더 살펴보자.

실제로 RelativeLayout이나 LinearLayout보다는 ConstraintLayout이 뷰의 계층을 줄여주고 훨씬 효율적으로 뷰를 구성할 수 있다고 한다. 이로 인해서 얻을 수 있는 이점은 무엇이 있을까 생각해봐야 한다.

  • 뷰의 계층을 수평적으로 만들 수 있다.
  • 이로 인해 findViewById를 호출하는 횟수도 감소시킬 수 있다.
  • View Lifecycle 함수들의 호출 횟수를 줄일 수 있다.
  1. Linear
LiearLayout을 상속받은 CustomView
  1. Relative
RelativeLayout을 상속받은 CustomView
  1. Constraint
ConstraintLayout을 상속받은 CustomView

결론
RelativeLayout이 Linear나 ConstraintLayout보다 onMeasure() 함수가 2배 정도 많이 호출되는 것을 확인할 수 있었다. 아마도 RelativeLayout이 자신의 크기를 측정하고 자식의 크기를 측정하는 과정이 필요하기 때문에 2배 호출되는 것으로 생각하고 있다. RelativeLayout은 뷰를 구성할 때 상대적인 위치를 고려해서 배치하거나 상하좌우에 배치시킬 때 나는 주로 이용했었다.

하지만, 복잡한 레이아웃을 구성할 때 RelativeLayout을 사용한다면 View의 계층이 깊어지게 되고 이로 인해서 View의 크기를 측정하는 과정이 길어지면서 결국에는 렌더링 속도에 영향을 미치는 좋지 않을 결과를 초래할 것이다.

그래서 앞으로는 ConstraintLayout을 사용해보면서 익히고 주로 사용해볼 생각을 하고 있다.

사실, 제대로 테스트를 진행하기 위해서는 커스텀 뷰그룹 안에 커스텀 뷰를 넣어야 한다. 하지만 나는 커스텀 뷰 그룹 안에 TextView, ImageView를 그대로 넣었다.[이 부분은 나중에 다시 테스트 할 예정]

참고