Android & Kotlin

[ Android ] customView의 onDraw 함수 디버깅 기록

쉽코기 2022. 3. 4. 14:37

1.  문제 상황 

CumtomView 특정 점이 터치가 되면 리스너로 View(MVP 모델의 ) 전달하고 해당 위치 데이터를 이용해 Presenter 를 거쳐 Model을 업데이트 한 뒤 다시 model 의 바뀐 내용대로 포문을 돌면서 커스텀 뷰를 그렸다.

 

즉 다시말해 customView 밖에서 리스트를 순회하면서 customView의 onDraw 및 invalidate() 를 여러번 호출 상황이었다. 

 

그런데 커스텀뷰가 잘 그려질 때도 있고 그려지지 않을 때도 있었다.

특히 모델의 데이터 즉 리스트가 커질 수록 이 문제는 두드러지게 나타났다.

data의 흐름을 따라 디버깅을 해도 별 문제가 없었고 하루를 통째로 날렸다. ㅜㅜㅜ

 

// 문제가 발생하던 코드

 


//  이하 Activity 
// ...
override fun showRectangle(rectangleList: MutableList<Rectangle>) {
        rectangleList.forEach {
            myCanvas.drawRectangle(it)
        }
    }
    

//  이하 MyCanvas -> cusotmview
// ...
fun drawRectangle(rec: Rectangle) = with(rec) {
 		rect =createRectF(rec)
        if (rec == selectedRectangle) {
            paint.apply {
                this.style = Paint.Style.STROKE
                this.color = Color.BLACK
            }
        } else {
            paint.apply {
                this.style = Paint.Style.FILL
                this.color = Color.argb(getAlpha(), rgba.r, rgba.g, rgba.b)
            }
        }
       
        paint.color = Color.argb(getAlpha(), rgba.r, rgba.g, rgba.b)
        invalidate()
    }
    
public override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.drawRect(rect, paint)

    }

 

2. 원인 파악 

문제는 View 생성주기에 대한 이해부족으로 invalidate()를 남발한 것이 었다. 아래 view 생명주기를 고려하여 위 코드를 살펴보면

 

1. 리스트에서 invalidate() 를 호출한다.

2. 이에따라 dipatchToDraw() 부터 호출해나간다.

3. onDraw 까지 다 마치지 않은 상황에서 다음 리스트 원소를 받아 또 invalidate() 를 실행

 

즉 정상동작할때는 onDraw() 가 끝나는 시점이 리스트의 다음 원소에 접근해 invlidate() 를 호출하는 시점보다 먼저일때였고 이상이 있을때는 반대였던 것이다.

 

3. 해결 방안

 

invalidate() 를 한번만 호출 하면되는 상황을 만들면 되었다.

기존에 리스틀 customView 내부로 가져와서 onDraw 안에서 순회하면서 그려나가고 모든 작업이 끝났을때 invalidate() 를 호출함으로써 해결할 수 있었다.

 

// customView
class MyCanvas(context: Context,) : View(context)   {

    fun drawRectangle(recList: MutableList<Rectangle>) {
        rectangles = recList
        invalidate()
    }

    public override fun onDraw(canvas: Canvas?) {
        selectedRectangles.forEach {
            val paint = setBorderPaint(it)
            rect = createRecF(it)
            canvas?.drawRect(rect, paint)
        }
        super.onDraw(canvas)
    }
class MainActivity : AppCompatActivity(), CanvasContract.View {
    private lateinit var binding: ActivityMainBinding
    lateinit var canvasPresent: Present
    lateinit var myCanvas: MyCanvas

...

    override fun showRectangle(rectangleList: MutableList<Rectangle>) {
        myCanvas.drawRectangle(rectangleList)
    }
}