상황 설명

스크롤뷰 안에 지도가 들어갈 경우, 지도를 상하 방향으로 움직일 때 스크롤뷰 쪽의 이벤트 우선순위가 높아서 지도는 안움직이고 화면 스크롤이 됨

예제는 네이버 지도를 사용했지만 네이버, 구글, 카카오 상관없이 적용 가능한 코드임

  1. 맵 클릭 이벤트를 구현할 인터페이스를 추가한다.

    interface OnMapTouchListener {
        fun onTouch()
    }
  2. 스크롤 뷰에대한 터치 이벤트를 재정의할 수 있도록 FrameLayout을 상속받은 TouchableWrapper 클래스를 추가한다.

    class TouchableWrapper @JvmOverloads constructor(
       context: Context,
       attrs: AttributeSet? = null,
       defStyleAttr: Int = 0,
       private val onTouchListener: OnMapTouchListener? = null
    ) : FrameLayout(context, attrs, defStyleAttr) {
    
       override fun dispatchTouchEvent(event: MotionEvent): Boolean {
           when (event.action) {
               MotionEvent.ACTION_DOWN -> onTouchListener?.onTouch()
               MotionEvent.ACTION_UP -> onTouchListener?.onTouch()
           }
           return super.dispatchTouchEvent(event)
       }
    }
  3. 지도를 표시할 MapFragment 클래스를 만든다.
    MapFragment 쪽으로 재정의된 지도 터치 이벤트를 전달해야하는데, Fragment는 생성자 argument를 사용할 수 없기 때문에 ScrollView가 포함된 Activity나 Fragment를 OnMapTouchListener의 구현체로 만들어서 콜백을 전달한다. onTouch를 구현할 때 스크롤뷰 객체에 requestDisallowInterceptTouchEvent(true)를 적용하면 자식 객체(지도)가 부모(스크롤뷰)의 이벤트를 가로채게 된다.

    class MainActivity : BaseActivity, OnMapTouchListener {
        // ...
        override fun onTouch() {
            // 스크롤뷰 객체에 requestDisallowInterceptTouchEvent를 true로 설정
            binding.scrollView.requestDisallowInterceptTouchEvent(true)
        }
    }

    fragment_map.xml

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto">
    
     <data>
    
     </data>
    
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent">
    
         <fragment
             android:id="@+id/fragment_map"
             android:name="com.naver.maps.map.MapFragment"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             app:navermap_zoomControlEnabled="false" />
     </androidx.constraintlayout.widget.ConstraintLayout>
    </layout>

    MapFragment.kt : MapFragment가 onAttach 되는 시점에 부모 activity를 형변환해서 Activity에서 정의한 이벤트를 가져온다. 지도 클릭 이벤트를 가로채기 위해 onCreateView에서 fragment 전체를 덮도록 FrameLayout을 상속받은 TouchableWrapper 객체를 추가한다.

    class NaverMapFragment : Fragment(R.layout.fragment_naver_map), OnMapReadyCallback {
    
        private lateinit var binding: FragmentNaverMapBinding
    
        private var listener: OnMapTouchListener? = null
    
        override fun onAttach(context: Context) {
            super.onAttach(context)
            listener = requireActivity() as? OnMapTouchListener
        }
    
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
            binding = DataBindingUtil.inflate(inflater, R.layout.fragment_naver_map, container, false)
            binding.lifecycleOwner = this
    
            // MapFragment 객체 초기화
            val mapFragment = childFragmentManager.findFragmentById(R.id.fragment_map) as? MapFragment?
            mapFragment?.getMapAsync(this)
    
            // 지도 전체를 덮어씌우도록 TouchableWrapper를 추가
            val frameLayout = TouchableWrapper(requireActivity(), null, 0, listener)
            frameLayout.setBackgroundColor(ContextCompat.getColor(requireContext(), android.R.color.transparent))
            (binding.root as? ViewGroup)?.addView(
                frameLayout,
                ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
            )
    
            return binding.root
        }
    
        // ...
    }

그러면 스크롤뷰 안에서 지도의 상하 방향 이동이 가능해진다.

 

참고 자료