맞춤 뷰 구성요소

Android는 기본 레이아웃 클래스 ViewViewGroup에 기반하여 UI를 구축할 수 있는 정교하고 강력한 구성요소화된 모델을 제공합니다. 우선 플랫폼에는 UI를 구성하는 데 사용할 수 있는 미리 빌드된 다양한 View 및 ViewGroup 서브클래스(위젯 및 레이아웃)가 포함되어 있습니다.

사용 가능한 위젯으로는 Button, TextView, EditText, ListView, CheckBox, RadioButton, Gallery, Spinner가 있으며 좀 더 특수한 목적에 맞는 AutoCompleteTextView, ImageSwitcherTextSwitcher가 있습니다.

사용 가능한 레이아웃에는 LinearLayout, FrameLayout, RelativeLayout 등이 있습니다. 더 많은 예는 일반 레이아웃 객체를 참고하세요.

미리 빌드된 위젯 또는 레이아웃이 필요에 맞지 않으면 View 서브클래스를 직접 만들 수 있습니다. 기존 위젯 또는 레이아웃을 약간만 조정해야 하는 경우 위젯 또는 레이아웃을 서브클래스로 만들고 그 메서드를 재정의할 수 있습니다.

View 서브클래스를 직접 만들면 화면 요소의 모양과 기능을 정밀하게 제어할 수 있습니다. 맞춤 뷰를 사용하여 제어하는 방법은 다음과 같은 몇 가지 예를 참고하세요.

  • 완전히 맞춤 렌더링된 뷰 유형을 만들 수 있습니다. 예를 들어 '볼륨 컨트롤' 노브를 2D 그래픽을 사용하여 렌더링하면 아날로그 전자 컨트롤과 유사합니다.
  • ComboBox(팝업 목록 및 자유 입력 텍스트 필드의 조합), 이중 창 선택기 컨트롤(목록이 있는 왼쪽 및 오른쪽 창으로, 원하는 항목을 원하는 목록에 재할당 가능) 등을 만들려면 View 구성요소 그룹을 새로운 단일 구성요소로 결합할 수 있습니다.
  • EditText 구성요소가 화면에 렌더링되는 방법을 재정의할 수 있습니다(메모장 가이드에서는 효과적으로 이 기능을 사용하여 줄이 있는 메모장 페이지를 만듭니다).
  • 키 누름과 같은 다른 이벤트를 캡처하고 맞춤 방식(예: 게임용)으로 처리할 수 있습니다.

다음 섹션에서는 맞춤 뷰를 만들어 애플리케이션에서 사용하는 방법을 설명합니다. 자세한 내용은 View 클래스를 참조하세요.

기본 접근법

다음 단계는 자체 View 구성요소를 만들기 시작할 때 알아야 하는 내용을 대략적으로 보여줍니다.

  1. 자체 클래스로 기존의 View 클래스 또는 서브클래스를 확장합니다.
  2. 슈퍼클래스에서 일부 메서드를 재정의합니다. 재정의할 슈퍼클래스의 메서드는 on으로 시작합니다(예: onDraw(), onMeasure()onKeyDown()). 이는 수명 주기 및 기타 기능 후크에 관해 재정의하는 Activity 또는 ListActivityon... 이벤트와 유사합니다.
  3. 새 확장 클래스를 사용합니다. 완료되면, 기반이 되었던 뷰 대신 새 확장 클래스를 사용할 수 있습니다.

팁: 확장 클래스는 확장 클래스를 사용하는 활동 내에서 내부 클래스로 정의할 수 있습니다. 이러한 정의는 확장 클래스에 대한 액세스를 제어하므로 유용하지만 꼭 필요한 것은 아닙니다(애플리케이션에서 더 광범위하게 사용하기 위해 새로운 공개 뷰를 만들 수 있음).

완전히 맞춤설정된 구성요소

완전히 맞춤설정된 구성요소를 사용하여 원하는 모양의 그래픽 구성요소를 만들 수 있습니다. 오래된 아날로그 게이지처럼 보이는 그래픽 VU 미터 또는 노래방 기계에 맞춰 노래할 수 있도록 튀는 공이 단어를 따라 움직이는 긴 텍스트 뷰를 예로 들 수 있습니다. 어떤 방식이든, 결합 방법에 상관없이 내장된 구성요소가 할 수 없는 기능을 구현하고 싶을 것입니다.

다행히 원하는 모양과 동작의 구성요소를 손쉽게 만들 수 있습니다. 단지 화면 크기와 사용 가능한 처리 능력의 제약을 받을 뿐입니다(애플리케이션이 데스크톱 워크스테이션보다 성능이 훨씬 낮은 기기에서 실행될 수도 있다는 것을 기억하세요).

완전히 맞춤설정된 구성요소를 만들려면 다음과 같이 하세요.

  1. 확장할 수 있는 가장 일반적인 뷰는 당연히 View이므로, 일반적으로 이를 확장하여 새로운 슈퍼 구성요소를 만듭니다.
  2. XML에서 속성과 매개변수를 가져올 수 있는 생성자를 제공할 수 있으며, 이런 고유한 속성과 매개변수(VU 미터의 색상과 범위, 바늘의 너비와 댐핑 등)를 사용할 수도 있습니다.
  3. 고유한 이벤트 리스너, 속성 접근자 및 특수키를 만들고 구성요소 클래스에서 좀 더 정교한 동작을 만들 수도 있습니다.
  4. 거의 확실하게 onMeasure()를 재정의해야 하고, 구성요소에 무언가를 표시하려면 onDraw()를 재정의해야 합니다. 둘 다 기본 동작이 있지만 기본값인 onDraw()는 아무것도 하지 않고 기본값인 onMeasure()가 항상 100x100 크기를 설정하는데, 이것이 원하는 크기가 아닐 수 있습니다.
  5. 필요할 경우 다른 on... 메서드도 재정의할 수 있습니다.

onDraw()onMeasure() 확장

onDraw() 메서드는 Canvas를 제공하는데, 이를 통해 2D 그래픽, 기타 표준 또는 맞춤 구성요소, 스타일이 지정된 텍스트는 물론 상상할 수 있는 어떤 것이든 구현할 수 있습니다.

참고: 3D 그래픽에는 적용되지 않습니다. 3D 그래픽을 사용하려면 View 대신 SurfaceView를 확장하고 별도의 스레드에서 그려야 합니다. 자세한 내용은 GLSurfaceViewActivity 샘플을 참고하세요.

onMeasure()는 좀 더 복잡합니다. onMeasure()는 구성요소와 컨테이너 간 렌더링 계약의 중요한 부분입니다. 포함된 부분의 측정값을 효율적으로 정확하게 보고하려면 onMeasure()를 재정의해야 합니다. 이는 상위에서의 제한 요구사항(onMeasure() 메서드로 전달됨) 및 setMeasuredDimension() 메서드 호출을 위한 요구사항(계산된 후 측정된 너비 및 높이 사용)에 의해 좀 더 복잡해집니다. 재정의된 onMeasure() 메서드에서 이 메서드를 호출하지 못하면 측정 시간에 예외가 발생합니다.

요약하면 onMeasure()의 구현은 다음과 같습니다.

  1. 재정의된 onMeasure() 메서드는 너비와 높이 측정치를 제한하기 위한 요구사항으로 취급해야 하는 너비 및 높이 측정 사양(widthMeasureSpecheightMeasureSpec 매개변수로 둘 다 치수를 나타내는 정수 코드)과 함께 호출됩니다. 이러한 사양에서 요구할 수 있는 종류의 제한에 관한 전체 참조 내용은 View.onMeasure(int, int) 아래의 참조 문서에서 찾을 수 있습니다(이 참조 문서에는 전체 측정 작업도 상당히 잘 설명되어 있습니다).
  2. 구성요소의 onMeasure() 메서드는 구성요소를 렌더링하는 데 필요한 측정 너비와 높이를 계산해야 합니다. 전달되는 사양의 범위 내에 있어야 하지만 확장도 가능합니다(이 경우 상위는 다른 측정 사양을 사용하여 클리핑, 스크롤, 예외 발생, onMeasure()에 다시 시도하도록 요구 등을 포함하여 원하는 작업을 선택할 수 있습니다).
  3. 너비와 높이가 계산되면 계산된 측정값으로 setMeasuredDimension(int width, int height) 메서드를 호출해야 합니다. 이렇게 하지 않으면 예외가 발생합니다.

다음은 프레임워크가 뷰에서 호출하는 다른 몇몇 표준 메서드를 요약한 것입니다.

카테고리 메서드 설명
생성 생성자 코드에서 뷰를 생성할 때 호출되는 생성자의 양식과 레이아웃 파일에서 뷰가 확장될 때 호출되는 양식이 있습니다. 두 번째 양식은 레이아웃 파일에 정의된 모든 속성을 파싱하고 적용해야 합니다.
onFinishInflate() 뷰 및 뷰의 모든 하위가 XML에서 확장되었을 때 호출됩니다.
레이아웃 onMeasure(int, int) 이 뷰 및 모든 하위의 크기 요구사항을 결정하기 위해 호출됩니다.
onLayout(boolean, int, int, int, int) 뷰가 모든 하위에 크기와 위치를 할당해야 할 때 호출됩니다.
onSizeChanged(int, int, int, int) 이 뷰의 크기가 변경되었을 때 호출됩니다.
그리기 onDraw(Canvas) 뷰가 콘텐츠를 렌더링할 때 호출됩니다.
이벤트 처리 onKeyDown(int, KeyEvent) 새 주요 이벤트가 발생할 때 호출됩니다.
onKeyUp(int, KeyEvent) 주요 이벤트가 발생할 때 호출됩니다.
onTrackballEvent(MotionEvent) 트랙볼 모션 이벤트가 발생하면 호출합니다.
onTouchEvent(MotionEvent) 터치스크린 모션 이벤트가 발생할 때 호출됩니다.
포커스 onFocusChanged(boolean, int, Rect) 뷰가 포커스를 얻거나 잃을 때 호출됩니다.
onWindowFocusChanged(boolean) 뷰가 포함된 창이 포커스를 얻거나 잃을 때 호출됩니다.
연결 onAttachedToWindow() 뷰가 창에 연결될 때 호출됩니다.
onDetachedFromWindow() 뷰가 창에서 분리될 때 호출됩니다.
onWindowVisibilityChanged(int) 뷰가 포함된 창의 가시성이 변경되었을 때 호출됩니다.

복합 컨트롤

완전히 맞춤설정된 구성요소를 만들지 않고 기존 컨트롤 그룹으로 구성된 재사용 가능한 구성요소를 조합하려는 경우 복합 구성요소(또는 복합 컨트롤)를 만드는 것이 적합할 수 있습니다. 요약하면, 더 작은 많은 컨트롤(또는 뷰)을 하나로 취급할 수 있는 논리적 항목 그룹으로 만드는 것입니다. 예를 들어, 콤보 상자는 한 줄로 된 EditText 필드 및 연결된 PopupList가 있는 인접한 버튼의 조합으로 생각할 수 있습니다. 버튼을 누르고 목록에서 항목을 선택하면 EditText 필드가 채워지지만 EditText에 직접 입력할 수도 있습니다.

사실 Android에는 SpinnerAutoCompleteTextView라는 사용 가능한 다른 뷰가 두 가지있지만, 콤보 상자의 개념이 이해하기 쉬운 예입니다.

복합 구성요소를 만들려면 다음과 같이 하세요.

  1. 일반적인 출발점은 일종의 레이아웃이기 때문에 레이아웃을 확장하는 클래스를 만듭니다. 콤보 상자의 경우 가로 방향의 LinearLayout을 사용할 수 있습니다. 다른 레이아웃이 내부에서 중첩될 수 있으므로, 복합 구성요소는 임의의 복잡성과 구조를 가질 수 있습니다. 활동과 마찬가지로 선언적(XML 기반) 접근 방식을 사용하여 포함된 구성요소를 만들 수도 있고, 코드에서 프로그래밍 방식으로 구성요소를 중첩할 수도 있습니다.
  2. 새 클래스의 생성자에서 슈퍼클래스가 예상하는 매개변수를 가져와서 먼저 슈퍼클래스 생성자에 전달합니다. 그런 다음 새 구성요소 내에서 사용할 다른 뷰를 설정할 수 있습니다. 여기에서 EditText 필드와 PopupList를 만들게 됩니다. 생성자가 가져와서 사용할 수 있는 자체 속성과 매개변수를 XML에 포함할 수도 있습니다.
  3. 포함된 뷰에서 생성할 수 있는 이벤트에 관한 리스너를 만들 수 있습니다. 예를 들어 List Item Click Listener에 관한 리스너 메서드는 목록 선택이 만들어질 경우 EditText의 내용을 업데이트합니다.
  4. 예를 들어 접근자 및 특수키로 자체 속성을 만들면, 구성요소에서 EditText 값을 초기에 설정하고 필요 시 내용을 쿼리할 수 있습니다.
  5. 레이아웃에는 정상적으로 작동할 기본 동작이 있으므로 레이아웃을 확장할 경우 onDraw()onMeasure() 메서드를 재정의할 필요가 없습니다. 하지만 필요한 경우 재정의할 수도 있습니다.
  6. 특정 키를 눌렀을 때 콤보 상자의 팝업 목록에서 특정 기본값을 선택할 수 있도록 다른 on... 메서드(예: onKeyDown())를 재정의할 수 있습니다.

요약하면, 레이아웃을 맞춤 컨트롤의 기본으로 사용하면 다음과 같은 여러 가지 장점이 있습니다.

  • 활동 화면처럼 선언적 XML 파일을 사용하여 레이아웃을 지정하거나 프로그래밍 방식으로 뷰를 만들고 코드의 레이아웃에 중첩할 수 있습니다.
  • onDraw()onMeasure() 메서드(그리고 대부분의 기타 on... 메서드)는 적절한 동작을 가지게 되므로 재정의할 필요가 없습니다.
  • 마지막으로, 임의로 복잡한 복합 뷰를 빠르게 생성하고 단일 구성요소인 것처럼 재사용할 수 있습니다.

기존 뷰 유형 수정

특정 상황에서 유용한 맞춤 뷰를 훨씬 더 쉽게 만드는 옵션이 있습니다. 원하는 것과 이미 매우 유사한 구성요소가 있는 경우 이 구성요소를 확장하고 변경하려는 동작만 재정의하면 됩니다. 완전히 맞춤설정된 구성요소로 원하는 작업을 모두 할 수 있지만, 뷰 계층 구조에서 좀 더 전문화된 클래스로 시작하면 정확히 원하는 기능을 하는 많은 동작을 무료로 얻을 수 있습니다.

예를 들어 메모장 애플리케이션은 Android 플랫폼 사용의 여러 측면을 보여줍니다. 그중에서 EditText 뷰를 확장하면 줄이 있는 메모장을 만들 수 있습니다. 이는 완벽한 예는 아니며, 이 작업을 위한 API는 다를 수 있지만 원리를 보여줍니다.

Android 스튜디오로 메모장 샘플을 가져오세요(또는 제공된 링크를 사용하여 소스를 살펴보세요). 특히 NoteEditor.java 파일에 있는 LinedEditText의 정의를 살펴보세요.

다음은 이 파일에서 참고할 사항입니다.

  1. 정의

    클래스는 다음 행으로 정의됩니다.
    public static class LinedEditText extends EditText

    • LinedEditTextNoteEditor 활동 내에서 내부 클래스로 정의되지만, 원하는 경우 NoteEditor 클래스 외부에서 NoteEditor.LinedEditText로 액세스할 수 있도록 공개되어 있습니다.
    • 이 클래스는 static입니다. 즉, 상위 클래스에서 데이터에 액세스할 수 있는 소위 '합성 메서드'를 생성하지 않습니다. 따라서 NoteEditor와 강력하게 관련된 클래스가 아닌 별도의 클래스로 작동하게 됩니다. 외부 클래스에서 상태에 액세스할 필요가 없는 경우 이 방법으로 내부 클래스를 더 간단하게 만들 수 있으며, 생성된 클래스를 작게 유지하고 다른 클래스에서 쉽게 사용할 수 있습니다.
    • 이 경우 맞춤설정하기 위해 선택한 뷰인 EditText를 확장합니다. 완료되면 새 클래스가 일반 EditText 뷰를 대체할 수 있습니다.
  2. 클래스 초기화

    항상 그렇듯이 슈퍼클래스가 먼저 호출됩니다. 또한 이는 기본 생성자가 아니라 매개변수화된 생성자입니다. EditText는 XML 레이아웃 파일에서 확장될 때 이러한 매개변수로 만들어지므로, 생성자는 이를 가져와서 슈퍼클래스 생성자에 전달해야 합니다.

  3. 재정의된 메서드

    이 예에서는 onDraw() 메서드 하나만 재정의하지만 맞춤 구성요소를 직접 만들 때 다른 메서드를 재정의해야 할 수 있습니다.

    이 샘플의 경우 onDraw() 메서드를 재정의하면 EditText 뷰 캔버스에서 파란색 선을 칠할 수 있습니다(캔버스는 재정의된 onDraw() 메서드로 전달됩니다). super.onDraw() 메서드는 메서드가 종료되기 전에 호출됩니다. 슈퍼클래스 메서드를 호출해야 하며, 이 경우에는 포함할 선을 칠한 후 끝에서 호출합니다.

  4. 맞춤 구성요소 사용

    이제 맞춤 구성요소를 만들었는데, 어떻게 사용할 수 있을까요? 메모장 예에서는 맞춤 구성요소가 선언적 레이아웃에서 직접 사용되므로, res/layout 폴더에 있는 note_editor.xml을 살펴보세요.

    <view xmlns:android="http://schemas.android.com/apk/res/android"
        class="com.example.android.notepad.NoteEditor$LinedEditText"
        android:id="@+id/note"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/transparent"
        android:padding="5dp"
        android:scrollbars="vertical"
        android:fadingEdge="vertical"
        android:gravity="top"
        android:textSize="22sp"
        android:capitalize="sentences"
    />
    
    • 맞춤 구성요소는 XML의 일반 뷰로 생성되며, 클래스는 전체 패키지를 사용하여 지정됩니다. 정의한 내부 클래스는 자바 프로그래밍 언어에서 내부 클래스를 참조하는 표준 방법인 NoteEditor$LinedEditText 표기법을 사용하여 참조됩니다.

      맞춤 뷰 구성요소가 내부 클래스로 정의되지 않은 경우 대신 XML 요소 이름으로 뷰 구성요소를 선언하고 class 속성을 제외할 수 있습니다. 예:

      <com.example.android.notepad.LinedEditText
        id="@+id/note"
        ... />
      

      LinedEditText 클래스는 이제 별도의 클래스 파일입니다. 클래스가 NoteEditor 클래스에 중첩되어 있으면 이 기법이 작동하지 않습니다.

    • 정의에 있는 다른 속성과 매개변수는 맞춤 구성요소 생성자로 전달된 후 EditText 생성자로 전달되므로, EditText 뷰에 사용하는 것과 동일한 매개변수입니다. 고유한 매개변수를 추가할 수도 있으며, 이 내용은 아래에서 다시 설명하겠습니다.

이제 모두 끝났습니다. 위 사례는 분명히 단순한 예이지만 그 점이 바로 핵심입니다. 즉, 맞춤 구성요소는 필요에 따라 단순하거나 복잡하게 만들 수 있습니다.

좀 더 정교한 구성요소에서는 더 많은 on... 메서드를 재정의하고 몇몇 자체 도우미 메서드도 사용하여 속성과 동작을 실제로 맞춤설정할 수 있습니다. 별다른 제약 없이 원하는 대로 마음껏 구성요소를 맞춤설정할 수 있습니다.