출처: http://blog.burt.pe.kr/안드로이드-커스텀뷰-이해하기/
안드로이드 뷰는 화면에 그려지기 전에 아래 그림과 같은 몇 단계의 과정을 거친다.
커스텀뷰를 만들기 위해서는 뷰의 드로잉 과정을 이해해야 한다.
위 단계를 3개의 과정으로 나눌 수 있다.
하나의 과정이 실행되면 항상 Draws 단계에서 마무리 된다.
- Animate 과정
- Layout 과정
- Draw 과정
각 과정의 시작점은 아래와 같다.
- Animate 과정은 View의 animate() 메서드에 의해서 시작된다.
- Layout 과정은 requestLayout() 메서드에 의해서 시작된다.
- Draw 과정은 invalidate() 메서드에 의해서 시작된다.
안드로이드에서 커스텀뷰는 두 종류로 나뉜다.
- 자식뷰가 있는 뷰. 즉 뷰그룹
- 자식뷰가 없는 뷰.
안드로이드에서 뷰를 화면에 배치시키는 책임은 뷰를 담고 있는 뷰그룹에게 있다.
따라서 자식이 없는 뷰일 경우 Layout 과정에서 Measure 단계만 구현하는 경우가 많다.
커스텀뷰를 만들기 위해서는 Measure 단계를 정확하게 이해하는 것이 가장 중요하다.
각 뷰의 정확한 크기를 알아야 뷰를 위치 시키고 그릴 수 있기 때문이다.
뷰의 기하(Geometry)
- 뷰의 기하는 사각형이다.
- [(left, top), (right, bottom)] or [(left, top), (width, height)] 로 사각형을 정의한다.
- 안드로이드에서 뷰는 두 종류의 width와 height를 가지고 있다.
- Measured Width, Measured Height 는 뷰가 부모뷰 크기의 범위 내에서 가지고 싶어하는 너비와 높이이다.
- Drawing Width, Drawing Height 는 뷰의 실제 너비와 높이로 뷰를 그리기 위해서 사용한다.
- 안드로이드에서는 이 Drawing Width와 Drawing Height를 width와 height로 표기한다.
- 따라서 Measured Width, Measured Height 는 Drawing Width, Drawing Height 와 다를 수 있다.
- 왜냐하면 뷰의 패딩, 마진 등을 고려하면 원하는 크기에서 패딩 및 마진 값을 빼야 하기 때문이다.
- 왜냐하면 뷰의 패딩, 마진 등을 고려하면 원하는 크기에서 패딩 및 마진 값을 빼야 하기 때문이다.
- 뷰의 정확한 위치와 크기는 언제 알 수 있는가?
- 안드로이드에서 뷰의 위치와 크기는 Layout 과정이 끝나야 정확히 계산된다.
- 이 Layout 과정은 여러번 호출 될 수 있다.
- 그렇기 때문에 Draw 과정이 시작되기 직전에 View의 정확한 위치와 크기를 알 수 있다.
- 위치 얻기 : getLeft(), getTop()
- 크기 얻기 : getWidth(), getHeight()
- 그렇다면 View의 Draw 과정이 시작되기 직전을 어떻게 알 수 있을까?
- ViewTreeObserver 사용
- 뷰의 크기를 알고자 하는 뷰에서 getViewTreeObserver() 를 통해서 ViewTreeObserver를 가져온다
- OnPreDrawListener 를 등록해서 드로잉 직전에 리스너가 호출 되도록 한다.
- 픽셀 단위의 뷰의 크기를 알 수 있다.
- ViewTreeObserver 사용
- 위의 과정이 일어나는 순서는 Top -> Down 이다.
- 즉 부모부터 과정이 시작되고 자식으로 전파된다.
- 따라서 Layout 과정에서 부모뷰의 크기가 결정되고 나서 자식뷰의 Layout 과정이 시작된다.
- 여기서 유추해 볼 수 있는 것은 부모뷰의 Measure Spec Mode 에 따라서 자식의 Layout 과정을 달리 해야 한다.
- Measure Spec Mode
- EXACTLY (고정)
- AT_MOST (가변 및 가변의 최대값)
- UNSPECIFIED (정해지지 않음. Layout 과정 재발생.)
- 즉 부모부터 과정이 시작되고 자식으로 전파된다.
- 뷰의 너비와 높이는 둘 중 하나이다.
- 고정값이다.정 (EXACTLY)
- match_parent 또는 70dp 와 같은 수치
- match_parent 가 고정값인 이유는 Layout 과정이 Top-Down으로 일어나므로 자식뷰의 Layout 과정에서는
이미 부모 뷰의 크기가 정해진 고정값과 같다.
- match_parent 가 고정값인 이유는 Layout 과정이 Top-Down으로 일어나므로 자식뷰의 Layout 과정에서는
- match_parent 또는 70dp 와 같은 수치
- 가변적이다. (AT_MOST)
- wrap_content
- wrap_content
- 고정값이다.정 (EXACTLY)
- 부모뷰의 Measure Spec Mode에 따른 자식뷰의 크기 정하는 규칙
- 아래와 같이 변수를 가정해 보자
- 부모뷰의 크기 : parentSize
- 부모뷰의 패딩 : parentPadding
- 부모뷰의 컨텐츠 크기 : parentContentSize = parentSize – parentPadding
- 자식뷰의 LayoutParams 이
- 고정된 수치 (70dp) : childSize 일 경우
- MATCH_PARENT 일 경우
- WRAP_CONTENT 일 경우
부모뷰가 EXACTLY 일 경우
자식뷰레이아웃 뷰모드 크기 설명 정확한 수치 크기 EXACTLY childSize 자식뷰는 특정 크기를 원한다. MATCH_PARENT EXACTLY parentContentSize 자식뷰는 부모와 같은 크기를 원한다. WRAP_CONTENT AT_MOST parentContentSize 자식뷰는 자신이 크기를 결정하기를 원한다. 즉 parentContentSize 가 최대값이다. 부모뷰가 AT_MOST 일 경우 자식뷰레이아웃 뷰모드 크기 설명 정확한 수치 크기 EXACTLY childSize 자식뷰는 특정 크기를 원한다. MATCH_PARENT AT_MOST parentContentSize 자식뷰는 부모와 같은 크기를 원한다. 하지만 아직 크기가 고정되지 않았다. 단 부모보다 크지 않게 제한한다. WRAP_CONTENT AT_MOST parentContentSize 자식뷰는 자신이 크기를 결정하기를 원한다. 단 부모뷰의 크기보다 클 수는 없다. 즉 parentContentSize 가 최대값이다. 부모뷰가 UNSPECIFIED 일 경우 자식뷰레이아웃 뷰모드 크기 설명 정확한 수치 크기 EXACTLY childSize 자식뷰는 특정 크기를 원한다. MATCH_PARENT UNSPECIFIED 아직 정해지지 않았다. 자식뷰는 부모와 같은 크기를 원한다. 자식뷰는 나중에 자신의 크기를 정할 수 있다. WRAP_CONTENT UNSPECIFIED 아직 정해지지 않았다. 자식뷰는 자신이 크기를 결정하기를 원한다. 자식뷰는 나중에 자신의 크기를 정할 수 있다. - 예제
- 문자열 여러개를 뷰의 너비와 높이에 맞춰 360도 회전해서 그리는 커스텀뷰
- 정해진 Aspect Ratio 에 따라 뷰의 크기를 정하는 커스텀뷰
8. 참고
- http://www.vogella.com/tutorials/AndroidCustomViews/article.html
- https://thenewcircle.com/s/post/1663/tutorial_enhancing_android_ui_with_custom_views_dave_smith_video
- https://www.buzzingandroid.com/2012/11/easy-measuring-of-custom-views-with-specific-aspect-ratio/
'IT_Programming > Android_Java' 카테고리의 다른 글
[펌] 액티비티 전체를 덮는 네비게이션 드로어 만들기 (0) | 2014.11.18 |
---|---|
[안드로이드] 폰에서 자기 전화번호 불러오는 방법 / Marshmallow READ_PHONE_STATE 권한 관련 이슈 (0) | 2014.11.16 |
[펌] Android L API를 살펴보자 (0) | 2014.11.05 |
[펌] Android AsyncTaskLoader에 대하여 (0) | 2014.11.05 |
[펌_안드로이드] LoaderManager 동작 이해 (0) | 2014.11.05 |