안드로이드

[Android/Kotlin] CustomView (1) View & View.SubClass

란서 2022. 7. 11. 19:36

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의 프로퍼티에 외부로부터의 값을 할당할 수 있다. 


  1. res/values 경로에 attrs.xml  만들기.

  2. <resources> Tag 내에 <declare-styleable> Tag 만들기

    name = "CustomView Class의 이름"

    ※Attr의 이름은 일반적으로 CustomView Class의 이름과 동일하게 짓는다. 

  3. <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