안드로이드의 UI는 기본적으로 두개의 구성요소로 구성되어 있다.
View 그리고 ViewGroup
이 둘의 상속받는 subclass들을 각각 Widget 그리고 Layout 이라고 한다.
View -> 위젯, ViewGroup -> 레이아웃 이라고 생각하면 편하다.
위젯의 예시는 Button, TextView, EditText, ListView, CheckBox, RadioButton, Gallery, Spinner 등이 있다.
레이아웃의 예시는 LinearLayout, FrameLayout, RelativeLayout 이 있다.
기본적으로 제공되는 위젯들로 대부분 원하는 기능들을 구현 가능하다.
허나 안드로이드에서 제공하지 않는 기능을 구현하고 싶다면? 이때 사용하는 것이 Custom View이다.
Custom View를 만드는 방법은 다음과 같은 형태로 진행된다
커스텀뷰를 만들 때 오버라이딩 될 View 클래스의 함수중 제일 중요한 두 함수는 onDraw()와 onMeasure()이다.
뷰를 그려주는 함수
onDraw() 메소드는 Canvas 라는것을 제공한다.
onDraw()는 자주 호출되기 때문에, 이곳에서 객체를 생성하게 되면 오버헤드가 발생한다.
더 이상 쓰지 않는 객체들은 일정시간이 지나면 가비지 컬렉터가 주워서 할당을 해제하게 되는데, onDraw()는 자주 호출이 되고 그럴때마다 계속 객체를 생성하면 속도나 성능 문제가 발생하기 때문에 이곳에서 객체를 생성하는 로직은 넣지 않아야 한다.
실제로 여기에 Paint() 를 넣어주었더니 경고문이 떴다.
그래서 맨 위에 paint 를 한번만 생성해주고 paint.reset() 으로 재활용을 하는 방식으로 구현을 했다.
뷰의 크기를 결정해주는 함수. 디폴트값으로 100x100 사이즈를 제공함.
뷰의 크기를 계산하고 width와 height가 결정이 되면 이 함수 안에 setMeasuredDimension(int width, int height)를 반드시 호출해 주어야 한다. 이 함수를 call 하는데 실패하면 exception이 난다.
View의 생성자에는 두가지가 있다. 하나는 코드상에서 정의하는 View의 생성자이고, 다른하나는 XML을 파싱하여 뷰를 생성하는 생성자이다.
class PieChart(context: Context, attrs: AttributeSet) : View(context, attrs)
Context : 공식문서에 따르면 컨텐스트란 어플리케이션의 전반적인 환경과 소통하는 인터페이스 입니다.
다시말하면 뷰와 어플리케이션과의 소통창구라고 할 수 있겠다.
AtrributeSet : XML로 뷰를 만들때, XML에 정의된 태그 데이터들이 AttributeSet이라는 형태로 데이터가 전달됨.
<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는 반드시 클래스 뷰의 이름과 일치할 필요는 없으나 똑같이 맞춰주는게 기본입니다.
<?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()는 크기와 위치는 다시 측정하지 않고, onDraw()만 호출하고
requestLayout()는 1번부터 다시 과정을 따라간다.