[Android] View 함수

Android에서의 layout, button과 같은 UI 관련 클래스들은 모두 View를 상속받아 생성된다.
ViewGroup은 button, imageView 같은 것과 다르게 자식 뷰를 가질 수 있고 이를 배치하는 역할을 한다.

✅ 자신만의 button이나 view를 만들고 싶다면 View 클래스를 상속 받아 필요한 메서드를 오버라이드 하면 된다.

✅ 자신만의 layout을 만들고 싶다면 ViewGroup 클래스를 상속 받아 필요한 메소드들을 오버라이드 하면 된다.
- Custom View는 View 클래스를 상속하여 만든다.
- Custom Layout은 ViewGroup 클래스를 상속하여 만든다.

View가 그려지는 과정

뷰가 focus를 가지면 레이아웃을 그리도록 요청해야 한다.
이때, 레이아웃의 계층 구조 중 루트 뷰를 제공해야 한다.
그래서 그리기는 루트 노드에서 시작되어 트리를 따라 전위 순회 방식으로 그려진다.
[전위 순회 방식 : 루트 -> 왼쪽 -> 오른쪽]
부모 뷰는 자식 뷰가 그려지기 전에 (자식 뷰 뒤에서 그려지며) 형제 뷰는 전위 방식에 따라 순서대로 그려진다.
레이아웃을 그리는 과정은 측정(measure)와 레이아웃(layout) 단계를 통해 그려진다.

measure(int widthMeasureSpec, int heightMeasureSpec)

부모 노드에서 자식 노드를 경유하며 실행되며, 뷰의 크기를 알아내기 위해 호출된다.

이것은 뷰의 크기를 측정 하는 것은 아니며 실제 크기 측정은 onMeasure(int, int)를 통해 이뤄진다.
measure(int,int)의 내부에서 onMeasure(int, int)를 호출함으로써 뷰의 크기를 알아낸다.
측정 과정에서는 부모 뷰와 자식 뷰간의 크기 정보를 전달하기 위해 2가지 클래스를 사용한다.

1. ViewGroup.LayoutParams

자식 뷰가 부모 뷰에게 자신이 어떻게 측정되고 위치를 정할지 요청하는데 사용된다.
ViewGroup의 sub class에 따라 다른 ViewGroup.LayoutParams의 sub class가 존재할 수 있다.
예를 들어, ViewGroup의 sub class인 RelativeLayout의 경우 자신만의 ViewGroup.LayoutParams의 sub class는 자식 뷰를 수평적으로 또는 수직적으로 가운데 정렬 할 수 있는 능력이 있다.
- 숫자
- match_parent
- wrap_content

2. ViewGroup.MeasureSpec

부모 뷰가 자식 뷰에게 요구 사항을 전달하는데 사용된다.

  • UNSPECIFIED : 부모 뷰는 자식 뷰가 원하는 치수대로 결정한다.
  • EXACTLY : 부모 뷰가 자식 뷰에게 정확한 크기를 강요한다.
  • AT MOST : 부모 뷰가 자식 뷰에게 최대 크기를 강요한다.

layout(int l, int t, int r, int b)

부모 노드에서 자식 노드를 경유하며 실행되며 뷰와 자식 뷰들의 크기와 위치를 할당할 때 사용된다.
measure(int,int)에 의해 각 뷰에 저장된 크기를 사용하여 위치를 지정한다. 내부적으로 onLayout()을 호출하고 onLayout()에서 실제 뷰의 위치를 할당하는 구조로 되어있다.

measure()와 layout() 함수는 내부적으로 각각 onMeasure(), onLayout() 함수를 호출한다.
이것은 final로 선언된 measure()와 layout() 대신 onMeasure(), onLayout()을 구현(override)할 것을 장려하기 위해서이다.

뷰의 measure()함수가 반환될 때, 뷰의 getMeasureWidth()와 getMeasureHeight() 값이 설정된다.
만약 자식 뷰 측정 값의 합이 너무 크거나 작을 경우 다시 measure() 함수를 호출하여 크기를 재측정한다.

View Lifecycle

1. Constructor

모든 뷰는 생성자에서부터 출발한다. 생성자에서 초기화를 진행하고, default 값을 설정한다. 뷰는 초기설정을 쉽게 세팅하기 위해서, AttributeSet 이라는 인터페이스를 지원한다. 먼저, attrs.xml 파일을 만들고 이를 부름으로써 뷰의 설정값을 쉽게 설정할 수 있다.

2. onAttachedToWindow

부모 뷰가 addView(childView) 함수를 호출하고 나서 자식 뷰를 윈도우에 붙게 된다.
(attached) 이때부터 뷰의 id를 통해 접근할 수 있다.

3. onMeasure

뷰의 크기를 측정하는 단계이다. 매우 중요한 단계.
대부분의 경우, 레이아웃에 맞게 특정 크기를 가져야한다.
여기에는 2 단계의 과정이 있다.


(1) 뷰가 원하는 사이즈를 계산한다.

(2) MeasureSpec에 따라 크기와 mode를 가져온다.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
}

(3) MeasureSpec의 mode를 체크하여 뷰의 크기를 적용한다.

int width;
if (widthMode == MeasureSpec.EXACTLY) {
  width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
  width = Math.min(desiredWidth, widthSize);
} else {
  width = desiredWidth;
}

(4) onLayout

이 단계에서 뷰의 크기와 위치를 할당한다.

(5) onDraw

뷰를 실제로 그리는 단계. Canvas와 Paint 객체를 사용하면 필요한 것을 그리게 된다. Canvas 객체는 onDraw 함수의 파라미터로 제공된다.
Canvas 객체를 이용하여 뷰의 모양을 그리고 Paint 객체를 이용하여 뷰의 색을 그린다.

주의 점은, onDraw() 함수를 호출 시 많은 시간이 소요된다는 점이다.
Scroll 또는 Swipe 등을 할 경우, 뷰는 다시 onDraw(), onLayout() 함수를 다시 호출하게 된다.
따라서 함수 내에서 객체 할당을 피하고 한 번 할당된 객체를 사용할 것을 권장한다.

onDraw()내부에서 객체 할당을 할 경우, 아래와 같은 경고 문구를 띄워준다.

Avoid object allocations during draw/layout operations (preallocate and reuse instead)...

(6) View Update

View Lifecycle을 보면 뷰를 다시 그리도록 유도하는 invalidate()와 requestLayout() 함수를 볼 수 있다.
이것은 런타임에 뷰를 다시 그릴 수 있게 한다. 각각의 사용 용도는 아래와 같다.

  • invalidate() : 단순히 뷰를 다시 그릴 때 사용한다. 예를 들어 뷰의 text 또는 color가 변경되거나 touch interactivity가 발생할 때 onDraw() 함수를 재호출하면서 뷰를 업데이트 한다.
  • requestLayout() : onMeasure()부터 다시 뷰를 그린다. 뷰의 사이즈가 변경 될 때 그것을 다시 측정해야 하기에 Lifecycle을 onMeasure() 부터 순회하면서 뷰를 그린다.

(7) Animation

뷰의 animation은 frame 단위의 프로세스이다.
예를 들어, 뷰가 점점 커질 때 뷰를 한 단계씩 차례대로 커지도록 할 것.
그리고 각 단계마다 invalidate()를 호출하여 뷰를 그린다.
대표적으로 애니메이션에 사용하는 클래스는 ValueAnimator이다.


실제 Custom View를 그리고자 한다면
아래의 블로그를 참고해보자.
Progress bar 커스텀 하기

좋은 웹페이지 즐겨찾기