[Android]View 업데이트로 초기화를 억제했습니다.

소개



안드로이드의 View를 유저 조작으로 이동시킨 후, View의 갱신에 의해 초기 위치로 돌아 버린다고 하는 현상에 조우했기 때문에 해결한 방법을 기재해 갑니다.
강한 방법인 것 같은 생각은 하고 있으므로, 더 좋은 방법이 있으면 코멘트 주시면 좋겠습니다.

환경



언어: Kotlin
운영 단말기: Android 9.0

개요



커스텀 뷰를 터치 이벤트로 이동할 수 있도록 했다.
다른 뷰를 조작하는 것으로 뷰의 재구축이 달리는 것으로 초기 위치로 돌아오는 현상이 발생했습니다.

대처 전




이 문제에 대해 사용자 정의 뷰의 onLayout을 Override하고 View를 다시 빌드하여 초기 위치로 돌아가는 것을 피했습니다.

대처 후





View 업데이트 작동 방식



우선은 Android의 View 업데이트의 구조에서 살펴 보겠습니다.
Activity, Fragment와 마찬가지로 View에도 라이프사이클이 존재합니다.

인용구 : The Life Cycle of a View in Android
View의 크기와 위치를 결정하는 데는 두 가지 프로세스(View#layout, View#measure)가 있습니다.
View#layout, View#measure는 부모 뷰가 자식 뷰에 대해 호출됩니다.
View#layout, View#measure가 호출되면 View#onMeasure, View#onLayout이 호출되고,
게다가 아이 뷰에 대해서, , 와 리커시블에 불려 가는 것 같습니다.

View#onMeasure, View#onLayout 안에서의 처리는 다음과 같이 되어 있습니다.
onMeasure: 하위 뷰의 View#measure를 호출하여 하위 뷰의 크기를 측정하고 자체 뷰의 크기를 측정합니다.
onLayout : 자식 뷰의 View#layout을 호출하여 자식 뷰의 위치를 ​​결정합니다.

공식적으로 이 근처 에 쓰여져 있습니다.

대책



사용자의 터치 이벤트에서 사용자 정의 뷰를 이동하는 프로세스


    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
        when (event!!.action) {
            MotionEvent.ACTION_DOWN -> {
                mLastX = event.rawX.toInt()
                mLastY = event.rawY.toInt()
            }

            MotionEvent.ACTION_MOVE -> {
                if (event.pointerCount == 1) {
                    val x = event.rawX.toInt()
                    val y = event.rawY.toInt()
                    val deltaX = x - mLastX
                    val deltaY = y - mLastY
                    mLastX = x
                    mLastY = y
                    if (abs(deltaX) >= 5 || abs(deltaY) >= 5) {
                        mIsMoving = true     // ユーザ操作による移動かどうかの判定
                        mDx += deltaPoint[0] // X軸方向の初期位置からの移動を管理
                        mDy += deltaPoint[1] // Y軸方向の初期位置からの移動を管理

                        val dx = left + deltaPoint[0]
                        val dy = top + deltaPoint[1]
                        layout(dx, dy, dx + width, dy + height) // Viewを更新
                    }
                }
            }
        }
        return false
    }

ACTION_DOWN에서 얻은 좌표와 ACTION_MOVE에서 얻은 좌표의 차이를 이동량으로 계산합니다.

커스텀 뷰 내에서 초기 위치로의 이동을 억제하는 처리


    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        super.onLayout(changed, l, t, r, b)
        if (mX != x && mY != y && !mIsMoving) { // ユーザの操作中(mIsMoving)は再構築を行わない
            val dx = left + mDx
            val dy = top + mDy
            layout(dx, dy, dx + width, dy + height)
        }
        mIsMoving = false
        mX = x
        mY = y
    }

첫째, 자뷰의 onLayout에서는 x, y에 초기 위치의 좌표가 들어옵니다.
따라서 초기화 전의 좌표 (mX, mY)와 비교하여 x, y의 값이 초기화 전의 값과 다른 경우,
초기 위치로부터의 이동량(mDx, mDy)을 기초로 layout를 재구축합니다.
mX, mY는 View의 갱신이 실행되기 전의 처리(터치 이벤트 등)의 타이밍으로 미리 저장해 둡니다.
로직상은 초기 위치로 이동하고 초기화 전의 위치로 다시 한번 이동시키고 있는 이미지입니다.

마지막으로



소스는 여기입니다.
서두에도 기재했습니다만, 강인한 방법인 것 같은 생각은 하고 있으므로, 보다 좋은 방법이 있으면 코멘트 주시면 좋겠습니다.

참고



이하, 참고로 했습니다. 감사합니다.
공식
The Life Cycle of a View in Android
[Android] ImageView 드래그
onMeasure와 onLayout에 대해 알아보기

좋은 웹페이지 즐겨찾기