안드로이드에서는 기본적으로 버튼, 텍스트뷰 등 기본적인 ui를 제공해줍니다.
모든 ui들은 view클래스를 상속받아 만들어집니다.
이중에서 viewGroup을 상속받은 ui들을 layout이라 하고 나머지를 wiget이라 합니다.
viewGroup 을 상속받은 layout들은 자식뷰를 가질 수 있기때문에 이를 배치하는 역할을 합니다.
하지만 내가 custom해서 직접 ui를 만들어 사용할 수 도 있습니다.
자신만의 button 이나 View를 만들고 싶다면 View 클래스를 상속 받아서 필요한 메소드들을 오버라이드 하면됩니다.
Custom View 는 View 클래스를 상속하여 만들고,
Custom Layout 은 Viewgroup 클래스를 상속하여 만듭니다.
View 메소드 호출 순서
View 오버라이드할 메소드
onMeasure
하는 일
- View의 크기를 결정할 때 불리는 함수이다.
함수 호출 과정
- View.measure() -> View.onMeasure() -> View.setMeasuredDimesion()
설명
- view 자신의 크기를 지정하는 함수이다.
- 인자로는 (int widthMeasureSpec, int heightMeasureSpec) 넓이, 높이 값이 각 모드와 함께 조합된 값이다.
- 이 값들을 사용하기 위해서는 MeausreSpce의 getMode()와 getSize()를 사용해야 한다.
int widthMode = MeasureSpec.getMode(widthMeasureSpec)
int width = MeasureSpec.getSize(widthMeasureSpec)
이때 Mode는 3가지가 존재한다.
- MeasureSpec.UNSPECIFIED : 소스상에서 View를 생성했을 때 해당 모드가 넘어 온다. 이 경우 그냥 인자로 넘어온 그대로 사용하면 된다.
- MeasureSpec.AT_MOST : 이경우는 View 내부의 크기를 계산한 값을 넘겨주면 된다. View의 layout_width, layout_height가 wrap_content로 설정된 경우 이 모드가 넘어오게 된다. MeasureSpec.getSize()를 통해 나온 값은 View가 최대로 가질수 있는 크기이다. 이를 바탕으로 내부 크기를 계산해야한다. 내부 크기가 이 값보다 크다면 문제가 될 수 있다.
- MeasureSpec.EXACTLY : 해당 View의 layout_width, layout_height가 fill_parent나 match_parent일 경우 부모 layout(viewGroup)이 해당 View의 크기를 미리 지정해서 width와 height를 넘겨주므로 MeasureSpec.getSize() 으로 반환된 값을 그냥 사용하면 된다.
onMeasure() 함수 override시 내부에서는 setMeasuredDimesion()함수를 호출해서 자신의 크기를 설정하여야만 합니다.(기본동작은 super.onMeasure()을 호출해라.)
setMeasuredDimesion()함수는 자신의 크기를 설정하는 함수로 view의 width,height 값에 상관없이 실제 크기는 이 함수를 통해 지정됩니다. (view 의 width,height은 view의 크기를 권고하는 것입니다. 실제는 setMeasuredDimesion()에 지정한 값으로 크기가 결정됩니다.)
만약 onMeasure()에서 setMeasuredDimesion()을 호출하지 않으면 java.lang.IllegalStateException 예외가 발생합니다.
풀어서 설명
예1 ) 만약 부모가 크기가 width 100, height 200이고 자식의 크기는 width" wrap_content, height : wrap_content 이라면
onMeasure 의 widthMeasureSpec 은 [100,AT_MOST] heightMeasureSpec은 [200,AT_MOST] 가 넘어올 것이다. 부모가 자식에게 말하는 것이다. 너의 내부 컨텐츠의 크기가 너의 크기가 될때 너의 크기는 몇이냐? 근데 너가 최대로 커질수 있는 크기는 100x200이야. 자식은 자신의 내부 컨텐츠 크기를 계산하고 그 크기가 100x200을 넘지 않는다면 그 크기가 자신의 크기가 되는 것이고 내부 컨텐츠가 크기가 100x200을 넘어간다면 자신의 크기는 100x200이 되야 한다. 물론 widthMeasureSpec ,heightMeasureSpec은 부모가 자식에게 권고하는 것이다. 만약 자신의 설정이 wrap_content라도 난 무조건 부모가 지정한 최대크기만큼 커질꺼야하는 자식은 100x200으로 설정하면 된다.
예2) 만약 부모가 크기가 width 100, height 200이고 자식의 크기는 width" fill_content, height : wrap_content이라면 onMeasure 의 widthMeasureSpec 은 [100,EXACTLY ] heightMeasureSpec은 [200,EXACTLY ] 가 넘어올 것이다. 부모가 자식에게 말하는 것이다. 너는 내가 지정한 100x200이 되어야해. 말 잘듣는 자식이라면 자신의 크기를 100x200으로 지정하면 된다. 하지만 이것또한 권고 일뿐이다. 기능이 다르게 동작하는 자식이라면 알아서 크기를 계산하면 된다.
예3) 만약 부모가 크기가 width 100, height 200이고 자식의 크기는 width: 80, height : 50이라면 onMeasure 의 widthMeasureSpec 은 [80,EXACTLY ] heightMeasureSpec은 [50,EXACTLY ] 가 넘어올 것이다. 자식은 이미 크기가 지정되어 있다는 걸 부모는 안다. 그래서 부모는 자식에게 "넌 내가 지정한 80x50이 되어야해" 라고 말하는 것이다.
만약에 부모 크기가 40x30이라면 자식이 아무리 크기가 80x50으로 설정했다해도 부모는 자식에게 MeasureSpec을 [40,EXACTLY ],[30,EXACTLY ] 로 보낼 것이다. 일반적인 레이아웃은 자식이 부모보다 커질수 없다.
보통 view의 onMeasure() 함수의 동작은 말 잘듣는 자식과 같다.
좀더 말하면 자식의 width, height 속성값이 지정되어 있더라도 이를 결정 짓는 것은 부모이다. 꼭꼭 기억하라.
만약 자식 뷰가 부모 뷰보다 크더라도 자식 뷰는 부모 뷰보다 크게 그려지지 않습니다. 부모 뷰보다 크게 그려지는 부분은 보이지 않게 됩니다.
내부에서 많이 쓰이는 함수
this.measureChild() : 자식 뷰의 크기를 계산시킨다.
this.getChildMeasureSpec() : measureChildren()에서 사용하는 하나의 메소드이다. 이 메소드를 통해 특정 자식 view의 MeasureSpce을 만들수 있다.
onLayout
하는 일
- child view의 위치를 잡아주는 일을 한다. onMeasure 호출 후에 onLayout 이 호출 되므로 자신의 크기를 이미 인지하는 상태에서 위치를 잡아 줄 수 있다.
- 주의할 점은 이 위치가 장비 디스플레이의 절대적 위치이다. 부모를 기준으로한 상대적인 위치가 아니다.
- 만약 한 view의 Layout의 크기가 width 100, height 100이고 measure된 크기가 width 20, height이라면 공간은 100x100을 할당하지만 크기는 20x20으로 그려진다.
함수 호출 과정
- ViewGroup.layout()-> View.layout() -> View.onLayout()
설명
ViewGroup을 상속하여 layout을 만드는 경우 child view의 위치를 잡아주는 역할을 한다.
넘어 오는 파라미터는 어플리케이션 전체를 기준으로 위치가 넘어오므로 주의해야한다.
내부에서 많이 쓰이는 함수
this.getPaddingLeft() : 자신의 왼쪽 패딩값
this.getPaddingTop() : 자신의 위쪽 패딩값
this.getPaddingRight() : 자신의 오른쪽 패딩값
this.getPaddingBottom() : 자신의 아래쪽 패딩값
this.getMeasuredWidth() : 자신의 크기 계산후 나온 넓이
this.getMeasuredHeight() : 자신의 크기 계산후 나온 높이
getChildCount() : 자식 뷰의 개수를 반환한다.
child.measure() : 자식 뷰의 크기를 반환한다. 이 때 MeasureSpec.AT_MOST 로 거의 호출한다.
child.getMeasuredWidth() : 자식 뷰의 넓이를 가져온다. child.measure() 호출 후 호출한다.
child.getMeasuredHeight() : 자식 뷰의 길이를 가져온다. child.measure() 호출 후 호출한다.
child.layout() : 자식 뷰의 위치를 지정한다.
onDraw
하는 일
화면을 그리는 일을 한다.
주의할 점은 이때 좌표의 기준은 디스플레이의 절대적인 위치가 아니다. 자신을 기준으로한 좌표이다.
설명
자신을 그리고 자식이 있는 경우 자식들을 그리면 된다.
내부에서 많이 쓰이는 함수
cavans.() : Canvas 의 모든 그리는 함수는 다 사용한다.
this.getMeasuredWidth() : view 자신의 넓이를 반환한다. 이를 바탕으로 그리면 된다.
this.getMeasuredHeight() : view 자신의 높이를 반환한다. 이를 바탕으로 그리면 된다.
기타
MeasureSpec.makeMeasureSpec : 길이와 모드를 조합하여 measurespec 값을 만든다.
padding 과 margin 의 차이 : padding은 내부 여백, margin은 외부 여백
http://clason.tistory.com/321 : 마진(margin) VS 패딩(padding)
invalidate()와 requestLayout() 의 차이 : invalidate는 화면이 유효하지 않으니 다시 그리도록 하라는 것이고 requestLayout()은 layout을 갱신하라는 것이다.
참고
https://onecellboy.tistory.com/344
http://stackoverflow.com/questions/13856180/usage-of-forcelayout-requestlayout-and-invalidate
'Android > Android UI' 카테고리의 다른 글
[Appbar] - Toolbar (0) | 2021.12.31 |
---|---|
View를 만드는 가장 기본적인 방법, LayoutInflater (0) | 2021.12.20 |
[Android AdapterView] : ListView, RecyclerView (0) | 2021.04.29 |
[View]: Widget, layout (0) | 2021.04.17 |