how to set google map fragment inside scroll view

后端 未结 8 879
北海茫月
北海茫月 2020-11-29 00:00

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

相关标签:
8条回答
  • 2020-11-29 00:11

    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

    0 讨论(0)
  • 2020-11-29 00:12

    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.

    0 讨论(0)
  • 2020-11-29 00:14

    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.

    0 讨论(0)
  • 2020-11-29 00:15

    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>
    
    0 讨论(0)
  • 2020-11-29 00:16

    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);
    
    0 讨论(0)
  • 2020-11-29 00:16

    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.

    0 讨论(0)
提交回复
热议问题