커스텀 뷰 의 크기 정하 기 | Android Custom View Programming 
 Android Custom View Programming 
 커스텀 뷰 의 크기 정하 기 
 커스텀 뷰 의 크기 ( 기하 혹은 좌표계 ) 정하 기 
 뷰 를 생각 해 봅시다 . 뷰 는 하나 의 사각형 으로 화면 에 무엇 인가 를 그리 는 객체 입니다 . 그리고 사용자 로부터 터치 이벤트 를 받 는 인터렉션 의 주체 이 기 도 합니다 . 
 뷰 를 생각 할 때 가장 중요 한 것 은 하나 의 사각형 입니다 . 우리 가 뷰 에 무엇 을 그려야 하 거나 어떤 뷰 에 인터렉션 이 발생 했 는지 알 기 위해서 는 뷰 를 구분지 을 영역 이 필요 하 기 때문 입니다 . 
 커스텀 뷰 를 만들 때 , 가장 먼저 해야 할 일 도 뷰 의 사각형 영역 을 정하 는 것 입니다 . 보통 뷰 의 기하 혹은 좌표계 를 정의 하 는 단계 라고 생각 하 면 됩니다 . 절대 좌표계 를 사용 하 는 시스템 에서 는 이 과정 이 무척 쉽 습니다 . 원 하 는 대로 내 가 어디 서부터 어디 까지 사용 하 겠 다고 정하 기 만 하 면 되 니까요 . 
 안드로이드 뷰 에서 는 어떻게 사각형 영역 을 설정 할까요 ? 기억 해야 할 두 단계 
 안드로이드 뷰 는 뷰 의 영역 을 설정 하 고 사용 하 는 두 단계 가 있 습니다 . 하나 는 뷰 의 좌표계 를 설정 하 는 단계 이 고 나머지 하나 는 설정 된 뷰 의 좌표계 를 사용 하 는 단계 입니다 . 
 뷰 의 좌표계 를 설정 하 는 단계 는 measure 단계 입니다 . 측정 단계 라고 할 수 있 는데 무엇 인가 를 측정 해서 뷰 의 좌표계 를 정하 는 것 입니다 . 무엇 을 측정 하 는 것 일까요 ? 뷰 의 좌표계 를 사용 하 는 단계 는 layout 단계 또는 draw 단계 입니다 . 이 단계 에서 가장 중요 하 게 기억 할 점 은 이 단계 에서 는 이미 뷰 의 좌표계 가 정의 되 어 있 다는 것 입니다 . 마치 절대 좌표계 에서 코드 를 작성 하 듯 이 단계 에서 는 그렇게 할 수 가 있 습니다 . 
 안드로이드 뷰 의 측정 단계 를 이해 하 기 위해서 는 안드로이드 뷰 가 어떻게 존재 하 는지 알 아야 합니다 . 안드로이드 뷰 는 그 홀로 존재 할 수 없 습니다 . 반드시 뷰 그룹 ( ViewGroup ) 에 담겨야 합니다 . 그래서 측정 이 라는 단계 가 필요 합니다 . 뷰 가 자기 자신 의 크기 를 결정 하 기 위해서 자기 자신 을 담 고 있 는 뷰 그룹 의 크기 를 알 아야 하 고 뷰 그 릅 에서 사용 할 수 있 는 영역 의 크기 도 알 아야 할 것 입니다 . 그리고 자기 가 사용 할 영역 의 크기 도 알 아야 하 겠 지요 . 즉 , 측정 단계 에서 는 자신 의 주변 환경 과 자기 자신 의 정보 를 측정 하 는 것 입니다 . 측정 한 내용 을 바탕 으로 자신 의 크기 를 결정 하 는 것 입니다 . 이제 코드 를 작성 해 가 며 좀 더 실질 적 으로 살펴 보 도록 하 겠 습니다 . 
 그리고 측정 단계 와 사용 단계 를 살펴보 기 위해서 아래 의 메서 드 들 을 재정 의 합니다 . 
 각 메서 드 를 설명 하 자면 아래 와 같 습니다 . 
 뷰 의 기하 , 좌표계 를 결정 하 는 곳 입니다 . onLayout ( ) 
 뷰 의 좌표계 가 정해진 단계 로 좌표계 를 바탕 으로 뷰 의 내용물 을 배치 하 는 곳 입니다 . 
 뷰 의 좌표계 가 정해진 단계 로 좌표계 를 바탕 으로 뷰 의 내용 을 그리 는 곳 입니다 . 이렇게 작성 한 뷰 도 커스텀 뷰 의 역할 을 충실히 해냄 으로 액티비티 의 레이아웃 파일 에 선언 해서 사용 해 보 겠 습니다 . 
 프로젝트 를 빌 드 해서 앱 을 실행 시켜 봅시다 . 
 E / AndroidRuntime : Process : kr . pe . burt . android . study . customviewsize , PID : 8317 
 E / AndroidRuntime : java . lang . IllegalStateException : onMeasure ( ) did not set the measured dimension by calling setMeasuredDimension ( ) 
 E / AndroidRuntime : at android . view . View . measure ( View . java : 16509 ) 
 E / AndroidRuntime : at android . widget . RelativeLayout . measureChildHorizontal ( RelativeLayout . java : 719 ) 
 E / AndroidRuntime : at android . widget . RelativeLayout . onMeasure ( RelativeLayout . java : 455 ) 
 코드 를 보 면 확인 할 수 있 듯이 아무런 구현 도 없 는 텅 빈 메서 드 입니다 . 따라서 onLayout ( ) 과 onDraw ( ) 에서 는 super 의 메서 드 를 호출 하 지 않 아도 됩니다 . 정리 하 면 코드 가 아래 와 같 겠 죠 . 
 크기 를 무조건 50 픽셀 x 50 픽셀 로 설정 했 습니다 . xml 레이아웃 파일 에서 는 100 dp x 100 dp 로 폭 과 높이 를 설정 했 지만 setMeasuredDimension ( 50 , 50 ) 메 서드 에서 50 픽셀 x 50 픽셀 로 설정 했 기 때문 에 사각형 의 크기 가 작 게 그려졌 습니다 . 즉 , setMeasuredDimension ( ) 메서 드로 원 하 는 뷰 의 기하 를 정해 주 면 그만 입니다 . 너무 쉽 죠 ? 그러나 몇 가지 생각 해 봐야 할 것 이 있 습니다 . 지금 상황 은 커스텀 뷰 를 담 고 있 는 뷰 그룹 이 전체 화면 크기 이 기 때문 에 자기 가 원 하 는 대로 뷰 의 크기 를 설정 할 수 있 었 습니다 . 만약 커스텀 뷰 를 담 고 있 는 뷰 그룹 의 전체 크기 가 30 픽셀 x 30 픽셀 이 라면 어떨까 요 ? 또는 뷰 그룹 의 크기 가 유동 적 이 라면요 ? 안드로이드 에서 는 이런 상황 을 쉽 게 해결 할 수 있 도록 측정 모드 를 알려 줍니다 . 측정 모드 
 3 가지 종류 의 측정 모드 가 있 습니다 . MeasureSpec . UNSPECIFIED 
 각각 이 풍기 는 뉘앙스 는 아래 와 같 습니다 . 
 아직 정확 하 게 측정 된 값 은 없 어 . 아무래도 다시 측정 과정 을 진행 해야겠어 . 그러 니 네 가 원 하 는 값 을 맘대로 적 든 말 든 상관 없 어 ~ 
 네 가 설정 할 수 있 는 최대 측정값 을 알려 줄게 . 이 안 에서 값 을 설정 해 ~. 아 그리고 다시 측정 과정 이 발생 할 수 있 어 . 최대치 도 변할 수 있 고 ~ 
 정확 한 측정값 이 야 . 이 값 으로 해도 되 고 이 값 보다 작 은 값 으로 설정 해도 되 . 측정 과정 이 다시 발생 하 진 않 을 거 야 . 
 이 세 가지 측정 모드 를 이해 하 기 위해서 아래 코드 를 다시 살펴봅시다 . 
 위 코드 에서 getDefaultSize ( ) 메 서 드 는 어떻 게 구현 되 어 있 을까요 ? 
 위 코드 를 보 면 두 가지 size 가 나오 는데 하나 는 내 가 원 하 는 size 이 고 다른 하나 는 측정 된 size 입니다 . 
 result : 내 가 원 하 는 크기 
 specSize : 측정 된 크기 
 참고 로 getSuggestedMinimumWidth ( ) 코드 는 아래 와 같 습니다 . 
 mBackgroud 는 Drawable 로 getMinimumWidth ( ) 메 서 드 는 아래 와 같 습니다 . 
 따라서 MeasureSpec . UNSPECIFIED 일 경우 에 는 뷰 의 최소 크기 를 측정 제안 치 로 계속 설정 합니다 . 부모 뷰 그룹 이 MeasureSpec . UNSPECIFIED 일 때 는 자식 뷰 가 제안 한 측정치 를 가지 고 다시 측정 과정 을 거칩니다 . 그러 다 MeasureSpec . AT _ MOST 이거 나 MeasureSpec . EXACTLY 일 경우 에 는 측정 된 값 을 기본 값 으로 설정 합니다 . 
 이제 CustomView 의 코드 를 아래 와 같이 작성 합니다 . 측정 모드 별 로 측정 되 는 폭 과 높이 가 어떻게 되 는지 알 기 위해서 관련 코드 를 작성 했 습니다 . 
 그리고 xml 레이아웃 파일 을 아래 와 같이 수정 합니다 . 
 자식 뷰 인 커스텀 뷰 의 크기 보다 부모 뷰 그룹 의 크기 를 일부러 적 게 해 보 았 습니다 . 
 V / CustomView - density : 3 . 0 
 V / CustomView - MeasureSpec : width mode : EXACTLY 
 V / CustomView - MeasureSpec : height mode : AT _ MOST 
 V / CustomView - onMeasure : width : 30 height : 1437 
 V / CustomView - MeasureSpec : height mode : EXACTLY 
 V / CustomView - onMeasure : width : 30 height : 300 
 V / CustomView - onMeasure : width : 30 height : 30 
 V / CustomView - onLayout : rect : ( x , y , w , h ) : 0 0 30 30 
 V / CustomView - onDraw : rect : ( x , y , w , h , mw , mh ) : 0 0 30 30 30 30 
 위 로그 를 보 면 측정 과정 을 여러 번 거칩니다 . 측정 하 는 모드 는 EXACTLY 아니 면 AT _ MOST 입니다 . 이 모드 에서 는 주어진 측정치 값 을 최대치 로 생각 하 고 뷰 의 크기 를 설정 해 주 면 됩니다 . 여러 측정 단계 를 거쳐 자식 뷰 의 크기 가 부모 뷰 그룹 의 크기 에 맞춰 지 는 것 을 알 수 있 습니다 . 그 값 이 자식 뷰 가 사용 할 수 있 는 최대 값 이 기 때문 입니다 . 이번 에 는 부모 뷰 그룹 의 크기 를 좀 넉넉 하 게 잡 아 봅시다 . 
 V / CustomView - onMeasure : width : 300 height : 1437 
 V / CustomView - onMeasure : width : 300 height : 300 
 V / CustomView - onMeasure : width : 300 height : 600 
 V / CustomView - onLayout : rect : ( x , y , w , h ) : 0 0 300 300 
 V / CustomView - onDraw : rect : ( x , y , w , h , mw , mh ) : 0 0 300 300 300 300 
 이번 에 도 EXACTLY 와 AT _ MOST 측정 모드 를 번갈 아 가 며 측정 하 고 자식 뷰 가 사용 할 수 있 는 크기 의 최대 값 을 알려 줍니다 . 그리고 로그 를 보 면 onLayout 과 onDraw 에서 는 확실 하 게 뷰 의 좌표계 가 결정 되 는 것 을 확인 할 수 있 습니다 . 즉 , EXACTLY , AT _ MOST 모드 일 경우 주어진 값 을 바탕 으로 뷰 자신 의 크기 를 정 합니다 . 마진 값 이나 패딩 값 등 기타 여러 조건 들 을 바탕 으로 설정 할 수 있 는 최대 값 내 에서 뷰 의 크기 를 결정 하 면 됩니다 . 
 지금 까지 View 의 onMeasure ( ) 메서 드 를 호출 해 뷰 의 크기 를 결정 했 습니다 . 이 부분 을 직접 크기 를 결정 하 는 코드 로 변경 해 봅시다 . 
 최대 값 을 고려 하 지 않 고 무턱대고 1600 x 1600 으로 설정 했 습니다 . 과연 어떻게 될까요 ? V / CustomView - density : 3 . 0 
 V / CustomView - onMeasure : width : 1600 height : 300 
 V / CustomView - onLayout : rect : ( x , y , w , h ) : 0 0 1600 1600 
 V / CustomView - onDraw : rect : ( x , y , w , h , mw , mh ) : 0 0 1600 1600 1600 1600 
 측정 모드 를 번갈 아 가 며 여러 번 측정 되 지만 onLayout 과 onDraw 를 보 면 결국 에 는 무턱대고 설정 한 1600 x 1600 이 됩니다 . 그렇 다면 뷰 는 화면 에 어떻게 보일까요 ? 
 뷰 의 크기 는 1600 x 1600 이 지만 그려지 는 영역 은 부모 뷰 그룹 의 크기 를 벗어날 수 없 음 을 확인 할 수 있 습니다 . 즉 , setMeasuredDimension ( ) 메서 드 를 사용 해서 구현 자 마음대로 뷰 의 크기 를 설정 할 수 있 습니다 . 하지만 정상 적 으로 화면 에 표시 하 기 위해서 는 최대 값 을 고려 해서 뷰 를 표시 해야 합니다 . 측정 된 값 의 10 % 를 패딩 값 으로 뺀 값 으로 뷰 의 크기 를 설정 하 는 코드 를 작성 해 보 겠 습니다 . 
 로그 는 아래 와 같 습니다 . 측정 단계 로그 는 제외 하 였 습니다 . 
 10 - 29 11 : 28 : 49 . 984 10648 - 10648 / ? V / CustomView - onLayout : rect : ( x , y , w , h ) : 0 0 270 270 
 10 - 29 11 : 28 : 49 . 992 10648 - 10648 / ? V / CustomView - onLayout : rect : ( x , y , w , h ) : 0 0 270 270 
 10 - 29 11 : 28 : 49 . 992 10648 - 10648 / ? V / CustomView - onDraw : rect : ( x , y , w , h , mw , mh ) : 0 0 270 270 243 270 
 아래 는 결과 화면 입니다 . 
 아래 는 CustomView 의 전체 코드 입니다 . 
 * Created by burt on 15 . 10 . 16 . .