49

I want to set a Google map fragment inside vertical ScrollView, when I do the map does not zoom vertically, how can I override the touch event listener on MapView over the listener of ScrollView?

This is my xml code:

 <ScrollView android:layout_height="wrap_content" android:layout_width="fill_parent" android:layout_above="@id/footer" android:layout_below="@id/title_horizontalScrollView"> ///other things <fragment android:id="@+id/map" android:layout_width="fill_parent" android:layout_height="300dp" class="com.google.android.gms.maps.SupportMapFragment" /> //other things 
2
  • Check this answer :) stackoverflow.com/a/17317176/3215911 it should be helpful! Commented May 29, 2015 at 8:50
  • i added transparent image but still couldn't zoom the google map view vertically Commented May 29, 2015 at 8:54

11 Answers 11

131

Create a custom SupportMapFragment so that we can override its touch event:

WorkaroundMapFragment.java

import android.content.Context; import android.os.Bundle; import android.widget.FrameLayout; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import com.google.android.gms.maps.SupportMapFragment; public class WorkaroundMapFragment extends SupportMapFragment { private OnTouchListener mListener; @Override public View onCreateView(LayoutInflater layoutInflater, ViewGroup viewGroup, Bundle savedInstance) { View layout = super.onCreateView(layoutInflater, viewGroup, savedInstance); TouchableWrapper frameLayout = new TouchableWrapper(getActivity()); frameLayout.setBackgroundColor(getResources().getColor(android.R.color.transparent)); ((ViewGroup) layout).addView(frameLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); return layout; } public void setListener(OnTouchListener listener) { mListener = listener; } public interface OnTouchListener { public abstract void onTouch(); } public class TouchableWrapper extends FrameLayout { public TouchableWrapper(Context context) { super(context); } @Override public boolean dispatchTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mListener.onTouch(); break; case MotionEvent.ACTION_UP: mListener.onTouch(); break; } return super.dispatchTouchEvent(event); } } } 

In this above class, we intercept the touch event by using TouchableWrapper.class that extends the FrameLayout. There is also a custom listener OnTouchListener to dispatch the touch event to the main activity named MyMapActivity that handles the map. When the touch event occurred, dispatchTouchEvent will be called and the listener mListener will handle it.

Then replace fragment class in xml with this class="packagename.WorkaroundMapFragment" instead of com.google.android.gms.maps.SupportMapFragment

Then in your activity initialize map as follows:

private GoogleMap mMap; 

inside onCreate do this:

 // check if we have got the googleMap already if (mMap == null) { SupportMapFragment mapFragment = (WorkaroundMapFragment) getChildFragmentManager().findFragmentById(R.id.map); mapFragment.getMapAsync(new OnMapReadyCallback() { Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL); mMap.getUiSettings().setZoomControlsEnabled(true); mScrollView = findViewById(R.id.scrollMap); //parent scrollview in xml, give your scrollview id value ((WorkaroundMapFragment) getChildFragmentManager().findFragmentById(R.id.map)) .setListener(new WorkaroundMapFragment.OnTouchListener() { @Override public void onTouch() { mScrollView.requestDisallowInterceptTouchEvent(true); } }); } }); } 

Update: Answer updated to getMapAsync() based on answer by @javacoder123

Sign up to request clarification or add additional context in comments.

7 Comments

Hi, it works! The only correction: getColor inside the WorkaroundMapFragment.java is now deprecatred, so I used ContextCompat.getColor(getActivity().getApplicationContext(), android.R.color.transparent).
@AlokNair i have used your answer and it's working fine inside scrollview but now i have case like need to detect ACTION_UP of TouchableWrapper and it wont getting called. do i need to return true from touch event of touchableWrapper?? . can you help ??
Very Good Answer! It works properly. But I didn't understand what is the getMap() method and comes from where! I simply wrote mMap = googleMap inside the event-oriented method: onMapReady(GoogleMap googleMap).
It works great! But i have problem, when i just click on the map view, scroll view immediately scrolled to up. What's wrong with this?
Can someone explain where the getMap() method has came from?
|
35

Just make CustomScrollView.class,

public class CustomScrollView extends ScrollView { public CustomScrollView(Context context) { super(context); } public CustomScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public CustomScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: //Log.i("CustomScrollView", "onInterceptTouchEvent: DOWN super false" ); super.onTouchEvent(ev); break; case MotionEvent.ACTION_MOVE: return false; // redirect MotionEvents to ourself case MotionEvent.ACTION_CANCEL: // Log.i("CustomScrollView", "onInterceptTouchEvent: CANCEL super false" ); super.onTouchEvent(ev); break; case MotionEvent.ACTION_UP: //Log.i("CustomScrollView", "onInterceptTouchEvent: UP super false" ); return false; default: //Log.i("CustomScrollView", "onInterceptTouchEvent: " + action ); break; } return false; } @Override public boolean onTouchEvent(MotionEvent ev) { super.onTouchEvent(ev); //Log.i("CustomScrollView", "onTouchEvent. action: " + ev.getAction() ); return true; } } 

Then in xml

<com.app.ui.views.CustomScrollView android:id="@+id/scrollView" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" android:orientation="vertical"> </com.app.ui.views.CustomScrollView> 

if xml beffore didn't work change to this...

<com.myproyect.myapp.CustomScrollView android:id="@+id/idScrollView" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" android:descendantFocusability="beforeDescendants" > </com.myproyect.myapp.CustomScrollView> 

how to use programmaticaly

CustomScrollView myScrollView = (CustomScrollView) findViewById(R.id.idScrollView); 

4 Comments

This solved my problem for a MapView inside a ScrollView (which was in a fragment of a swippable tabs fragment). I previously tried to override onTouchEvent in the MapView, with no success. You saved my day!
Fantastic solution. I have no idea why it works. Can someone explain?
Overriding Motion events , Smart one
This asnwer seems to be easier than that suggested by @Alok Nair.
13

I used the google maps listeners to resolve the scrolling issue of google map inside a scroll view.

 googleMap?.setOnCameraMoveStartedListener { mapView.parent.requestDisallowInterceptTouchEvent(true) } googleMap?.setOnCameraIdleListener { mapView.parent.requestDisallowInterceptTouchEvent(false) } 

By using this when you use start moving maps it disallows parent scrolling and when you stop moving maps then it allows scrolling.

Comments

9

This is the Kotlin version of Alok Nair's answer, I have to say that I am relatively new writing Kotlin code, any comment is appreciated.

Custom SupportMapFragmet:

class FocusMapFragment: SupportMapFragment() { var listener: OnTouchListener? = null lateinit var frameLayout: TouchableWrapper override fun onAttach(context: Context) { super.onAttach(context) frameLayout = TouchableWrapper(context) frameLayout.setBackgroundColor(ContextCompat.getColor(context, android.R.color.transparent)) } override fun onCreateView(inflater: LayoutInflater, viewGroup: ViewGroup?, bundle: Bundle?): View? { val layout = super.onCreateView(inflater, viewGroup, bundle) val params = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) (layout as? ViewGroup)?.addView(frameLayout, params) return layout } interface OnTouchListener { fun onTouch() } inner class TouchableWrapper(context: Context): FrameLayout(context) { override fun dispatchTouchEvent(event: MotionEvent?): Boolean { when (event?.action) { MotionEvent.ACTION_DOWN, MotionEvent.ACTION_UP -> listener?.onTouch() } return super.dispatchTouchEvent(event) } } } 

Then change the fragment class in the xml:

android:name="packagename.FocusMapFragment" 

And finally when you initialize the map:

override fun onMapReady(googleMap: GoogleMap) { this.googleMap = googleMap (childFragmentManager.findFragmentById(R.id.mapView) as? FocusMapFragment)?.let { it.listener = object: FocusMapFragment.OnTouchListener { override fun onTouch() { scrollView?.requestDisallowInterceptTouchEvent(true) } } } // ... } 

I tested this code in API 28 and 29, I couldn't find a Kotlin version anywhere so thought it could be useful for somebody.

Comments

4

Please refer to this answer: https://stackoverflow.com/a/17317176/4837103

It creates a transparent view with the same size of mapFragment, at the same position with mapFragment, then invoke requestDisallowInterceptTouchEvent (true), preventing its ancestors to intercept the touch events.

Add a transparent view over the mapview fragment:

<ScrollView> ... <FrameLayout android:layout_width="match_parent" android:layout_height="300dp"> <FrameLayout android:id="@+id/fl_google_map" android:layout_width="match_parent" android:layout_height="match_parent" /> <!-- workaround to make google map scrollable --> <View android:id="@+id/transparent_touch_panel" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> </ScrollView> 

Set touch listener for that transparent view:

 var transparentTouchPanel = view.FindViewById (Resource.Id.transparent_touch_panel); transparentTouchPanel.SetOnTouchListener (this); 

....

public bool OnTouch (View v, MotionEvent e) { switch (e.Action) { case MotionEventActions.Up: v.Parent.RequestDisallowInterceptTouchEvent (false); break; default: v.Parent.RequestDisallowInterceptTouchEvent (true); break; } return false; } 

I thought RequestDisallowInterceptTouchEvent could work with being invoked just once, but apparently it has to be called for EVERY touch event, that's why it has to be put inside onTouchEvent. And the parent will propagate this request to the parent's parent.

I tried set touch event listener for fl_google_map, or for the mapFragment.getView(). Neither of them worked, so creating a transparent sibling view is a tolerable workaround for me.

I have tried, this solution works perfectly. Check the comments under the original post if you do not believe me. I prefer this way because it does not need to create a custom SupportMapFragmet.

Comments

3

My suggestion is to use lighter version of google map when dealing with this situation. this resolved my issue.

add this attribute in fragment.

 map:liteMode="true" 

and my issue resolved. i also added customScrollView in addition to this. but after placing liteMode property my issue resolved.

1 Comment

I am using a androidx.core.widget.NestedScrollView, and adding this solve the problem that shows my map in gray with nothing inside.
2

I solved it this way, add to implement this class:

public class YourNameFragmentHere extends Fragment implements OnMapReadyCallback, GoogleMap.OnCameraMoveStartedListener {...} 

Into onMapReady:

 @Override public void onMapReady(GoogleMap googleMap) { // Your code here mMap = googleMap; mMap.setOnCameraMoveStartedListener(this); } 

Then I created this function:

@Override public void onCameraMoveStarted(int reason) { if (reason == GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE) { mScrollView.requestDisallowInterceptTouchEvent(true); } else if (reason == GoogleMap.OnCameraMoveStartedListener .REASON_API_ANIMATION) { mScrollView.requestDisallowInterceptTouchEvent(true); } else if (reason == GoogleMap.OnCameraMoveStartedListener .REASON_DEVELOPER_ANIMATION) { mScrollView.requestDisallowInterceptTouchEvent(true); } } 

Comments

1

Using default SupportMapFragmentin scrollview (fix mapview scroll issue in vertical scrollview):

 val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment mapFragment.getMapAsync { googleMap -> mMap = googleMap // simple add this interceptTouchEvents googleMap.setOnCameraMoveStartedListener { mapFragment.view?.parent?.requestDisallowInterceptTouchEvent(true) } googleMap.setOnCameraIdleListener { mapFragment.view?.parent?.requestDisallowInterceptTouchEvent(false) } } 

Comments

0

Update

@Alok Nair's answer is very good however, the .getMap() method no longer works from android 9.2 onwards. Change the getMap method to .getMapAsync and add code in the onMapReady method

Updated Solution, that works in Fragments

if (gMap == null) { getChildFragmentManager().findFragmentById(R.id.map); SupportMapFragment mapFragment = (WorkaroundMapFragment) getChildFragmentManager().findFragmentById(R.id.map); mapFragment.getMapAsync(new OnMapReadyCallback() { @Override public void onMapReady(GoogleMap googleMap) { gMap = googleMap; gMap.setMapType(GoogleMap.MAP_TYPE_NORMAL); gMap.getUiSettings().setZoomControlsEnabled(true); mScrollView = mView.findViewById(R.id.advertScrollView); //parent scrollview in xml, give your scrollview id value ((WorkaroundMapFragment) getChildFragmentManager().findFragmentById(R.id.map)) .setListener(new WorkaroundMapFragment.OnTouchListener() { @Override public void onTouch() { mScrollView.requestDisallowInterceptTouchEvent(true); } }); } }); } 

Comments

0

Solution in Kotlin

@Alok Nair's answer still works today (2022). I used his solution in my project in a fragment. I translated from Java to Kotlin and made minor changes (such as eliminating the OnTouchListener interface).

SupportMapFragmentWrapper.kt (WorkaroundMapFragment)

 import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import androidx.core.content.ContextCompat import com.google.android.gms.maps.SupportMapFragment class SupportMapFragmentWrapper : SupportMapFragment() { private lateinit var mListener: () -> Unit fun setOnTouchListener(listener: () -> Unit) { mListener = listener } inner class TouchableWrapper(context: Context) : FrameLayout(context) { override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { when(ev!!.action) { MotionEvent.ACTION_DOWN -> mListener.invoke() MotionEvent.ACTION_UP -> mListener.invoke() } return super.dispatchTouchEvent(ev) } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { val layout = super.onCreateView(inflater, container, savedInstanceState) val frameLayout = TouchableWrapper(requireContext()) frameLayout.setBackgroundColor( ContextCompat.getColor( activity?.applicationContext!!, android.R.color.transparent) ) with(layout as ViewGroup) { addView( frameLayout, ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) ) } return layout } } 

Use only this in onCreateView(...) [Fragment]

 override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = DataBindingUtil.inflate(inflater, R.layout.fragment_route_creation, container, false) val mapFragment = childFragmentManager .findFragmentById(R.id.map) as SupportMapFragmentWrapper mapFragment.getMapAsync(this) mapFragment.setOnTouchListener { binding.scrollView.requestDisallowInterceptTouchEvent(true) } return binding.root } 

XML

 <ScrollView ... > <fragment android:id="@+id/map" android:name="com.example.natour.ui.route.SupportMapFragmentWrapper" android:layout_width="match_parent" android:layout_height="500dp" android:layout_marginTop="24dp" app:cameraTilt="30" app:cameraZoom="20" app:uiRotateGestures="true" app:uiZoomControls="true" /> ... </ScrollView> 

Comments

-10

As Android Document suggested,
You can add like this way:

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="3" android:orientation="horizontal" > <fragment android:id="@+id/map" android:name="com.google.android.gms.maps.SupportMapFragment" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> </ScrollView> 

8 Comments

Also i cant scroll my map and zoom vertically because the scroll view scroll over the map
I hope you referred the mentioned document. Thi is not a good practice to put map fragment inside the scrollview. Please let me know why are you doing like this. Any reason behind that ? I have done same thing without add in scrollview. If you would like to watch the code then let me inform.
@RiteshGajera What if you have a long form and want map in the centre of it with all functionalities. Wont you use scrollview then??
No, I haven't yet use. But without scrollview you can same feature achieved through SupportMapFragment it.
Alok, You can perform or bind the touch event on google map. like as: googleMap.setOnMapLongClickListener(new GoogleMap.OnMapLongClickListener() { @Override public void onMapLongClick(LatLng point) { //do your stuff here.. } }); Which I have done in one of my R&D Task.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.