0.CustomView 개념
안드로이드Android에서는 매우 많은 View의 Sub class들을 제공한다.
예를 들어 Button, TextView, EditText, Spinner, RadioButton 과 같은 View의 Sub class 들을 통해
사용자에게 "정보를 제공" 하거나 사용자와 "상호작용" 한다.
이 때, 개발자는 1.이미 존재하는 View에서 조금 더 기능을 추가하고 싶거나 또는 2.아예 새로운 외형 또는 기능을 가진 View를 제공하고 싶을 수 있다.
1.View의 Sub Class를 구현하여 CustomView를 만드는 경우
시간을 줄이고 개발에 수고를 덜 들이기 위해 View의 Sub Class들을 구현한 CustomView Class를 만들 수 있다.
예를 들어 우리는 EditText와 같은 Sub Class를 구현한 클래스를 만들고,
class CustomViewEditText : EditText() {
}
해당 SubClass에서 제공하는 외형과 기능을 그대로 받아와 수정하여 사용할 수 있다.
2.View Class를 구현하여 CustomView를 만드는 경우
만약 기존에 존재하는 View의 Sub class 중 원하는 View의 외형과 기능이 없다면
View Class 자체를 구현하여 자신만의 CustomView를 생성할 수 있다.
class CustomView : View() {
}
이때 중요한 점은 View의 size와 shape 등 모든 부분을 method 재정의를 통해 그려줘야 한다는 것이다.
위와 같은 방식으로 CustomVIew를 만들고 나면 layout 파일에서도 해당 View를 UI위젯으로 추가할 수 있다.
<com.example.customviewexample.customView
...
/>
1.CustomView 생성
1.CustomView class를 만들고, View 또는 View의 Sub Class 구현하기
class DialView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {...}
> @JvmOverloads는 Kotlin에서 내부적으로 default Values에 따른 overloads 정책이 잘 설정되어 있지만, java에서는 그렇지 않다 따라서 해당 annotation을 function 및 constructor에 추가하여 java에서 multiple한 overloads를 생성할 수 있도록 만든다.
- context:Context
- attrs: AttributeSet?
- defStyleAttr: Int
2.CustomView 클래스 내부에 drawing에 필요한 프로퍼티 만들기.
View가 UI에 그려지기 이전에 view를 그리는데 필요한 값들을
customView class는 초기화된 프로퍼티에 미리 지정하여
drawing단계를 최대한 빨리 실행할 수 있게 만든다.
class DialView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val TAG = "DialView"
// 원의 반지름
private var radius = 0.0f
// position 변수는 label과 indicator circle position을 그리기 위해 사용 된다.
private val pointPosition: PointF = PointF(0.0f,0.0f)
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
textAlign = Paint.Align.CENTER
textSize = 55.0f
typeface = Typeface.create("", Typeface.BOLD)
}
...
}
- radius : Float = 원을 그리기 위해 반지름을 설정하는 프로퍼티
- pointPosition : PointF = x,y좌표의 float값을 갖는 프로퍼티
- paint :Paint = Paint class는 기호, 텍스트 또는 비트맵을 그리기 위한 style 및 color 정보를 갖는 class
Paint.ANTI_ALIAS_FLAG = "안티 앨리어싱"으로 계단 현상을 방지하기 위해 쓰인다.
textAlign = text의 정렬 지정
textSize = text의 사이즈 지정
typeface = text의 글꼴 지정
3.View 메서드 재정의 하여 원하는 데로 view를 drawing하기
View class를 구현하여 CustomView를 생성했다면 view를 그리는 , 즉 "draw" 과정이 필수적이다.
이를 위해 drawing을 다루는 함수들을 재정의해야 한다.
- onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) :
해당 함수는 view가 처음 UI상에 보여질 때, 그리고 view의 size가 변화할 때 마다 호출된다.
따라서 view의 size를 받아와야 할 때 (이용해야할 때) 활용성이 높다. - onDraw(canvas: Canvas?) :
Custom View를 그리기 위해 필수적인 함수.
canvas객체를 이용하여 실질적인 view를 그릴 수 있다.
onDraw() 메서드는 Screen이 refresh 될 때 마다 호출된다.
그렇기에 환경에 따라 몇 초 사이에도 onDraw() 메서드가 수많이 호출될 수 있다.
따라서 onDraw() 메서드 내에 무거운 코드를 구현하는 것을 지양해야 한다.
특히, 변수에 값을 할당하는 Allocation 을 최대한 지양하라. - " performClick() : Boolean" & " invalidate() ":
view를 그리는 것 이외에도, 사용자가 view를 클릭하는 것과 같은 event에 반응하기 위한 함수들이 필요하다.
사용자의 view Click에 대처하기 위해 performClick() : Boolean 메서드를 재정의 한다.
일반적으로 view에서는 onClickListener를 지원한다 .하지만 Custom View에서는 performClick()을 재정의하여 구현하기 때문에 이후에 onClickListener가 사용자에 의해 정의되었을 때도 고려해야 한다.
따라서 performClick()를 호출한 뒤 곧바로 super.performClick을 호출하여 true일 경우 return하는 코드를 구현하도록 하자.
또한 view에 클릭이 가능하게끔 만들기 위해서 init{} 내부에
isClickable = true를 구현해줘서 view가 클릭이 되게끔 만들어야 한다.
이 때, 해당 event가 view의 외형을 바꾸는 등의 기능을 해야한다면 해당 함수 내에서 invalidate() 를 호출한다.
invalidate()는 현재 view가 그려진 모습을 무효화하고, 강제로 onDraw()를 호출하여 새로운 view를 그리도록 한다.
예를 들어, 사용자가 view 를 클릭했을 때 view의 배경 색이 바뀌어야 한다면
performClick() 내에서 색이 변경될 수 있도록 class 내부 프로퍼티의 값을 변경하고,
invalidate()를 호출하여 현재 view를 무효화하고 새로운 view를 그리게끔 onDraw()를 호출된다.
Canvas와 Paint Class
Paint Class로부터 파생된 Canvas 객체는 view를 drawing하는 데 있어서 지름길을 제공해준다.
- drawText() : Text를 그린다.
추가적으로 setTypeface() 및 setColor() 글꼴 및 색깔을 지정해줄 수 있다. (paint 객체이용)
drawText(text : String, x좌표: float, y좌표: float, 스타일: Paint) - 기본 Shape 그린다.
drawRect() - 사각형
drawCircle() - 원형
drawOval() - 타원형
drawArc() - 아치형
drawShape(x좌표: float, y좌표: float, 각 shape에 맞는 인자, style: Paint) - Bitmap 그리기.
drawBitmap()
※심층 뷰 계층(CoordinatorLayout)이 있는 앱에서는 onMeasure() 메서드를 재정의.
Custom View가 해당 layout에 적합하도록 정의가능
onMeasure() 메서드는 view의 높이 및 너비를 결정하는데 사용할 수 있는 일련의 measureSpec을 제공한다.
2.Custom Attribute 사용하기
layout.xml 을 보다보면
각 view widget에는 특정한 값을 설정해줄 수 있는 app:○○○○○= "..." 와 같은 attribution이 있는것을 볼 수 있다.
CustomView 도 마찬가지로 이와같은 attribution을 설정해 줄 수 있다.
이러한 custom Attribution을 통해 CustomView의 프로퍼티에 외부로부터의 값을 할당할 수 있다.
- res/values 경로에 attrs.xml 만들기.
- <resources> Tag 내에 <declare-styleable> Tag 만들기
name = "CustomView Class의 이름"
※Attr의 이름은 일반적으로 CustomView Class의 이름과 동일하게 짓는다. - <declare-styleable> Tag에는 attr을 만들기 위해 name과 type을 설정한다.
<resources>
<declare-styleable name="DialView">
<attr name="fanColor1" format="color"/>
<attr name="fanColor2" format="color"/>
<attr name="fanColor3" format="color"/>
</declare-styleable>
</resources>
format 에는 color 이외에도 여러 유형의 type이 존재

layout에서 attributes을 설정할 때 "android: " 가 아닌 "app: " 을 사용하는 이유.
attributes 이 schema.android.com/apk/res/"내 앱의 pacakge"
경로에 있기 때문에 android보다는 app을 쓰는게 더 좋다.
4.Layout에서 attr 값 설정해보기
<com.example.customviewexample.DialView
android:id="@+id/dialView"
...
app:fanColor1="#FFEB3B"
app:fanColor2="#CDDC39"
app:fanColor3="#009688"
.../>
5. 해당 attr의 값을 CustomView Class의 로컬변수 값으로 초기화 하기.
//Attributes 값을 캐싱할 로컬 변수.
private var fanSpeedLowColor = 0
private var fanSpeedMediumColor = 0
private var fanSpeedMaxColor = 0
init {
context.withStyledAttributes(attrs, R.styleable.DialView) {
fanSpeedLowColor = getColor(R.styleable.DialView_fanColor1, 0)
fanSpeedMediumColor = getColor(R.styleable.DialView_fanColor2, 0)
fanSpeedMaxColor = getColor(R.styleable.DialView_fanColor3, 0)
}
}
소스코드
https://github.com/ranseo/CustomViewExample.git
GitHub - ranseo/CustomViewExample: android developr codelab 고급과정 : 2.custom view
android developr codelab 고급과정 : 2.custom view . Contribute to ranseo/CustomViewExample development by creating an account on GitHub.
github.com
출처
Android Codelab 고급 과정 2.CustomView
https://developer.android.google.cn/courses/kotlin-android-advanced/overview?hl=ko#prereqs