안드로이드/정리(Android)

Android - CustomView 만들기

김염인 2022. 5. 12. 19:56

 

CustomView

 

View를 Customizing 하는 것은 중요하다. 도표, 커스텀 원형 그래프. 뷰의 재사용등 아주 중요하다.
그래서 CutomView를 더 자세히 공부해 보려고 한다.

Flutter에서는 UI자체를 Widget으로 만들어 Stateful 혹은 Stateless하게 Custom이 가능 하다.
하지만 안드로이드의 경우 View를 상속받는 CustomView를 처리해주어야 깔끔하게 View를 만들 수 있다.

 

 


 

1) CustomView란?

 

CustomVeiw는 4개의 큰 패더라임을 기억해야 합니다. 일단 생성자인 Constructor를 생성 한 뒤 OnMeasure을 통한 View의 크기를 설정, OnLayout을 통한 어디에 그릴지 위치를 설정, OnDraw를 마지막으로 색상값, 모양등 어떤 그림을 그릴지 선택합니다.
CustomView는 흰 도화지에 그림을 그리듯이 만들어 나가는 재미가 있다.

 

1 - 1. Constructor

뷰는 최대 4개의 생성자를 가집니다.

  • View(Context context) 코드에서 동적으로 뷰를 생성할 때 사용할 수 있는 간단한 생성자입니다. 파라미터 context를 통해 현재 실행중인 뷰의 리소스 등에 액세스 할 수 있습니다.
  • View(Context context, AttributeSet attrs) : xml에서 생성할 때
  • View(Context context, AttributeSet attrs, int defStyleAttr) : ThemeStyle과 함께 뷰를 생성할 때
  • View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) : ThemeStyle 또는 Style로 xml에서 뷰를 생성할 때

 

1 - 2. onMeasure

이 메소드에서는 해당 커스텀 뷰의 사이즈를 지정해줘야 합니다. xml에서 유저가 설정한 width, height의 정보가 파라미터로 넘어옵니다. 우리는 MeasureSpec.getMode(~)를 통해 MATCH_PARENT, WRAP_CONTENT 또는 100dp와 같이 지정된 값인지 알 수 있습니다.

onMeasure은 여러 번 호출될 수 있습니다. 예를 들어 부모가 자식들의 각 크기를 측정한 뒤, 자식들의 크기의 합이 너무 크거나 작다면 다시 measure() 메소드를 호출하여 구체적인 값을 구합니다.

child view를 가지는 커스텀 뷰라면 child의 사이즈를 측정해서 자신의 사이즈를 재야 할 수도 있는데, 이 메소드에서 설정해주면 됩니다.

 

 

1-3. onLayout

뷰의 위치를 설정해주는 함수입니다. 뷰의 child들의 크기와 위치를 할당해야 할 때 호출됩니다. 즉, child를 가지는 뷰라면 해당 메소드를 오버라이드 해주어야 합니다. 이때 파라미터로 넘어오는 값들은 어플리케이션 전체를 기준으로 넘어오는 위치값임을 알아야 합니다.

 
 

1-4. onDraw

뷰에 그림을 그리는 메소드입니다. Paint 클래스를 통해 도형을 그릴 수도 있고, canvas에 텍스트를 추가할 수도 있습니다.

onDraw()에서는 많은 시간이 소요되거나 여러 번 호출될 수 있기 때문에(초당 60번) 되도록 객체 선언, 할당을 피하고 기존 객체를 재사용하는 것이 좋습니다. 이보다 가비지 컬렉터가 더 빨라서 GC 관련된 drop이 없을 수도 있지만, 이 동작 역시 별도의 스레드에서 진행되므로 배터리 소모를 야기할 수 있습니다. 또한, onDraw에서 초기화되는 객체들은 주로 drawing object인데, 이들은 많은 소멸자를 호출하기 때문에 성능에 영향을 줄 수 있습니다.

 

 

 

 

참고자료

http://labs.brandi.co.kr/2021/10/14/jeonhs.html 

 

 


2) 실 습

 

- CustomView.kt

// View Class를 상속 받고 생성자를 정의 해준다.
// View 클래스에는 4개의 생성자가 정의되어 있으며, 아래의 2개 생성자는 항상 정의해 두는 편이 좋다
class CustomView : View {
    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)

    // Paint 객체를 생성해 뷰의 속성(색상, 크기 등)을 정의 한다.

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        val paint = Paint()
        paint.color = Color.BLUE
        paint.style = Paint.Style.FILL

        // Canvas객체로 도형(원) 그리기 매개변수: 중심의 X좌표, 중심의 Y좌표, 반지름, Paint객체
        canvas?.drawCircle(CXSIZE, CYSIZE, 150f, paint)

        paint.color = Color.YELLOW
        paint.textSize = 40f

        canvas?.drawText("커스텀 뷰", (CXSIZE *0.7).toFloat(), CYSIZE, paint)

    }

    companion object{
        val CXSIZE = 300f
        val CYSIZE = 300f
    }
}

첫번째 CustomView는 간단한 원을 그리는 View이다 우선 View를 상속 받아 constructor 생성자를 선언 해주었다. 위에 나온 4개의 생성자중 2개를 선언 해주었다. 간단하게 @JvmOverloads를 사용하여 생성자를 가질 수 있다.

 

Canvas객체에는 다양한 그리기 메서드가 지원된다.

  • drawText() : 텍스트 그리기
  • drawCircle(): 원 그리기
  • drawArc(): 호 그리기
  • drawRect(): 사각형 그리기
  • drawLine(): 선 그리기
  • drawBitmap(): 비트맵 이미지 그리기

- 더많은 메서드는 레퍼런스 참고: https://developer.android.com/reference/kotlin/android/graphics/Canvas

 

 

- XML을 이용하여 CustomView를 추가하는 방법 이 가장 많이 사용 된다.

 


- InValidateCustomView.kt

class InValidateCustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0,
) : View(context, attrs, defStyleAttr) {
    // 터치 X좌표 값을 저장할 변수
    var coords: PointF? = null

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas) // 텍스트 사이즈 설정
        val paint = Paint()
        paint.textSize = 70f // coordX 변수에 저장된 값을 텍스트로 그린다.
        canvas?.drawText("${coords?.x} / ${coords?.y}", 100f, 600f, paint)

    }

    override fun onTouchEvent(event: MotionEvent): Boolean {

        // 1. 터치 좌표 취득
        coords = PointF(event!!.x, event!!.y)
        var action = ""

        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                action = "ACTION_DOWN"
            }
            MotionEvent.ACTION_MOVE -> {
                action = "ACTION_MOVE"
            }
            MotionEvent.ACTION_UP -> {
                action = "ACTION_UP"
            }
            MotionEvent.ACTION_CANCEL -> {
                action = "ACTION_CANCEL"
            }
        }

        Log.d("this", "Action : ${action}")
        // 화면 다시그리기 !! 중요
        invalidate()

        return true

    }
}

OnTouchEvent를 통해 좌표 값을 UI에 반영 가능하다.

여기서 MotinEvent는 DOWN, UP, MOVE, CANCLE 등 눌렀을 때, 놓았을 때, 움직였을 때, 취소됐을 때 와같이 다양한 경우의 이벤트 동작 처리를 해줄 수 있다.

 


 

- AttrCustomView.kt

class AttrCustomView : View {
    // 커스텀 속성을 참조하기 위한 변수
    private var myShapeColor: Int? = null

    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
        if (attrs != null && context != null) { // attr.xml파일 declare-styleable이 CustomView로 정의된 attr(속성)을 typeArray객체로 받아온다.
            val typedArr = context.obtainStyledAttributes(attrs, R.styleable.CustomView)
            // format을 구분하여 속성값 참조
            myShapeColor = typedArr.getColor(R.styleable.CustomView_myShapeColor, Color.YELLOW)
        }
    }

    // context.obtainStyledAttributes() 메서드를 호출하면 attr.xml에 정의된 속성 정보들이 typedArray 객체로 반환된다.


    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        val paint = Paint()
        paint.color = myShapeColor ?: Color.BLACK

        canvas!!.drawRect(100f,100f,350f,350f,paint)
    }
}

CustomView를 그릴 때 View마다 다른 속성을 가지는 View를 그릴 때 가 있다 그 때 사용하는 것이 attrs 속성 타입 설정이다.

res - menu - attrs에 resourse값을 추가해주어 Customizing된 속성값을 적용시켜 줄 수 있다.

- context.obtainStyledAttributes() 메서드를 호출하면 attr.xml에 정의된 속성 정보들이 typedArray 객체로 반환된다.

- 속성의 format에 따라 typedArray 객체에서 getString(), getInt(), getColor() 등을 호출해주면 속성 값이 반환된다.

 

출처: https://curryyou.tistory.com/408?category=961282 [카레유]

 

 

- attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="CustomView">
        <attr name="myShapeColor" format="color" />
        <attr name="myTextColor" format="color" />
        <attr name="myTextString" format="string" />
        <attr name="myTextSize" format="string" />
        <attr name="myStrokeColor" format="color" />
        <attr name="myStrokeWidth" format="string" />
    </declare-styleable>

</resources>

 

attr 속성을 이용한 CustomView

 


 

Conclusion

커스텀 뷰는 개인의 조건에 맞게 onMeasure(), onLayout(), onDraw()를 작성하면 더 효율적인 View가 완성 될 것 같다.

그래프, 원형 뷰 등 다양한 뷰를 onMeasure(), onLayout(), onDraw()조건에 맞게 커스터마이징 하는 실습을 해보면서 CustomView를 더욱더 이해를 하도록 해야겠다.