Custom View 만들기

sumi Yoo·2022년 9월 22일
0

들어가기 앞서..


안드로이드의 UI는 기본적으로 두개의 구성요소로 구성되어 있다.

View 그리고 ViewGroup

이 둘의 상속받는 subclass들을 각각 Widget 그리고 Layout 이라고 한다.

View -> 위젯, ViewGroup -> 레이아웃 이라고 생각하면 편하다.

위젯의 예시는 Button, TextView, EditText, ListView, CheckBox, RadioButton, Gallery, Spinner 등이 있다.

레이아웃의 예시는 LinearLayout, FrameLayout, RelativeLayout 이 있다.

Custom View란?


기본적으로 제공되는 위젯들로 대부분 원하는 기능들을 구현 가능하다.
허나 안드로이드에서 제공하지 않는 기능을 구현하고 싶다면? 이때 사용하는 것이 Custom View이다.

Custom View를 만드는 방법은 다음과 같은 형태로 진행된다

  1. View 클래스를 확장하는 클래스를 생성한다
  2. View 클래스으 몇가지 메소드를 오버라이딩 한다. 오버라이딩 될 메소드는 보통 'on' 이라는 이름으로 시작한다
  3. onDraw(), onMeasure(), onKeyDown() 등등
    이러한 방법으로 뼈대가 되는 클래스를 만든후 이를 또 확장하면서 여러가지 CustomView를 만든다.

onDraw()와 onMeasure()


커스텀뷰를 만들 때 오버라이딩 될 View 클래스의 함수중 제일 중요한 두 함수는 onDraw()와 onMeasure()이다.

onDraw(Canvas canvas)

뷰를 그려주는 함수
onDraw() 메소드는 Canvas 라는것을 제공한다.

주의할점

onDraw()는 자주 호출되기 때문에, 이곳에서 객체를 생성하게 되면 오버헤드가 발생한다.
더 이상 쓰지 않는 객체들은 일정시간이 지나면 가비지 컬렉터가 주워서 할당을 해제하게 되는데, onDraw()는 자주 호출이 되고 그럴때마다 계속 객체를 생성하면 속도나 성능 문제가 발생하기 때문에 이곳에서 객체를 생성하는 로직은 넣지 않아야 한다.

실제로 여기에 Paint() 를 넣어주었더니 경고문이 떴다.

그래서 맨 위에 paint 를 한번만 생성해주고 paint.reset() 으로 재활용을 하는 방식으로 구현을 했다.

onMeasure(int widthMeasureSpec, int heightMeasureSpec)

뷰의 크기를 결정해주는 함수. 디폴트값으로 100x100 사이즈를 제공함.
뷰의 크기를 계산하고 width와 height가 결정이 되면 이 함수 안에 setMeasuredDimension(int width, int height)를 반드시 호출해 주어야 한다. 이 함수를 call 하는데 실패하면 exception이 난다.

생성자(Constructors)


View의 생성자에는 두가지가 있다. 하나는 코드상에서 정의하는 View의 생성자이고, 다른하나는 XML을 파싱하여 뷰를 생성하는 생성자이다.

커스텀 뷰 만들기


PieChart(커스텀 뷰 클래스)

class PieChart(context: Context, attrs: AttributeSet) : View(context, attrs)

Context : 공식문서에 따르면 컨텐스트란 어플리케이션의 전반적인 환경과 소통하는 인터페이스 입니다.
다시말하면 뷰와 어플리케이션과의 소통창구라고 할 수 있겠다.
AtrributeSet : XML로 뷰를 만들때, XML에 정의된 태그 데이터들이 AttributeSet이라는 형태로 데이터가 전달됨.

xml에서 뷰를 선언하는 작업


  • Custom attributes의 구조를 \<declare-styleable> 엘리먼트에서 정의합니다
  • xml로 ui를 작성할 때 attribute의 value값을 정의합니다
  • 런타임에서 xml에서 정의한 attribute value값을 받아옵니다
  • 받아온 attribute value값을 앞서 정의한 클래스에 적용합니다.

res/values/attrs.xml

<resources>
   <declare-styleable name="PieChart">
       <attr name="showText" format="boolean" />
       <attr name="labelPosition" format="enum">
           <enum name="left" value="0"/>
           <enum name="right" value="1"/>
       </attr>
   </declare-styleable>
</resources>

howText와 labelPosition 이라는 속성을 정의하였습니다.
여기서 styleable name인 PieChart는 반드시 클래스 뷰의 이름과 일치할 필요는 없으나 똑같이 맞춰주는게 기본입니다.

activity_main.xml

   <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:custom="http://schemas.android.com/apk/res-auto">
     <com.example.customviews.charting.PieChart
         custom:showText="true"
         custom:labelPosition="left" />
    </LinearLayout>

맞춤속성을 클래스에 적용


xml로 ui를 정의하면 tag값에 적힌 데이터들은 AttributeSet으로 전달된다.
이제 클래스 뷰 에서 Custom Attributes 값을 읽어 올 수 있다.

맞춤속성 값을 읽어오기위해 obtainStyledAttributes() 메소드를 이용한다.

public class PieChart extends View {
    private boolean mShowText;
    private int textPos;
    public PieChart(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.PieChart,
                0, 0);

        try {
            mShowText = a.getBoolean(R.styleable.PieChart_showText, false);
            textPos = a.getInteger(R.styleable.PieChart_labelPosition, 0);
        } finally {
            a.recycle();
        }
    }
}

TypedArray를 다 사용하였디면 항상 recycle()함수를 호출하라고 공식문서에 명시되어 있다.

invalidate(), requestLayout()


이 두 함수 모두 화면을 갱신한다는 결과적으로 보여지는 퍼포먼스는 같지만, 차이점이 있다.
커스텀뷰가 화면에 그려질 때 과정을 간략하게 적어보자면,

  1. 뷰의 크기의 정한다. 아마 onSizeChanged()를 말하는 것 같다.
  2. 뷰의 위치를 정한다.
  3. onDraw()로 뷰를 그린다.

invalidate()는 크기와 위치는 다시 측정하지 않고, onDraw()만 호출하고
requestLayout()는 1번부터 다시 과정을 따라간다.

참고자료

0개의 댓글

관련 채용 정보