androidgoogle-mapsscrollconditional-statementscustom-scrolling

Android Google Map | Two finger scrolling


I am working with Google Map,

My case is that I have a map fragment inside the ScrollView and I need to scroll the map only with two finger if user touches only one finger map should not work and normal Scroll View should work.

This is what i tried so far -

transparent_image.setOnTouchListener(new View.OnTouchListener() {

                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    int action = event.getAction();
                    switch (action & MotionEvent.ACTION_MASK) {
                        case MotionEvent.ACTION_POINTER_DOWN:
                            showMessage("Double finger ACTION_POINTER_DOWN");

                            googleMap.getUiSettings().setScrollGesturesEnabled(false);
                            scroll_view.requestDisallowInterceptTouchEvent(true);
                            return true;
                        case MotionEvent.ACTION_POINTER_UP:
                            showMessage("Double finger ACTION_POINTER_UP");

                            googleMap.getUiSettings().setScrollGesturesEnabled(true);

                            scroll_view.requestDisallowInterceptTouchEvent(false);

                            return true;

                        default:
                            return true;
                    }
                }
            });

Solution

  • That behavior of native Android application you can achieve when:

    1) disable ScrollView scrolling and enable GoogleMap scrolling when user placed two finger on MapFragment;

    2) enable ScrollView scrolling and disable GoogleMap scrolling in other case.

    For disabling/enabling ScrollView scrolling by condition, you need to extend ScrollView and override the onTouchEvent() method to return false when some condition is matched, for example, as in this answer of Josep Earl:

    public class LockableScrollView extends ScrollView {
    
        private boolean mScrollable = true;
    
        public LockableScrollView(Context context) {
            super(context);
        }
    
        public LockableScrollView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public LockableScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        public void setScrollingEnabled(boolean enabled) {
            mScrollable = enabled;
        }
    
        public boolean isScrollable() {
            return mScrollable;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    // if we can scroll pass the event to the superclass
                    if (mScrollable) return super.onTouchEvent(ev);
                    // only continue to handle the touch event if scrolling enabled
                    return mScrollable; // mScrollable is always false at this point
                default:
                    return super.onTouchEvent(ev);
            }
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            // Don't do anything with intercepted touch events if
            // we are not scrollable
            if (!mScrollable) return false;
            else return super.onInterceptTouchEvent(ev);
        }
    }
    

    Enable/disable scroll gestures at GoogleMap you can easily calling setAllGesturesEnabled() and setScrollGesturesEnabled() on GoogleMap.getUiSettings() object and for determining touches of two fingers on MapFragment you can use approach, based on this answer of community wiki:

    public class TouchableWrapper extends FrameLayout {
    
        private LockableScrollView mLockableScroll;
        private GoogleMap mGoogleMap;
    
        public TouchableWrapper(Context context) {
            super(context);
        }
    
        public void setGoogleMapAndScroll(GoogleMap googleMap, LockableScrollView lockableScroll) {
            mGoogleMap = googleMap;
            mLockableScroll = lockableScroll;
        }
    
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
    
            switch (event.getAction() & MotionEvent.ACTION_MASK) {
    
                case MotionEvent.ACTION_DOWN:
                    mGoogleMap.getUiSettings().setScrollGesturesEnabled(false);
                    // UPDATE: add below line to disable zoom gesture
                    mGoogleMap.getUiSettings().setZoomGesturesEnabled(false);
                    mLockableScroll.setScrollingEnabled(true);
                break;
    
                case MotionEvent.ACTION_POINTER_DOWN:
                    mLockableScroll.setScrollingEnabled(false);
                    mGoogleMap.getUiSettings().setScrollGesturesEnabled(true);
                    // UPDATE: add below line to enable zoom gesture
                    mGoogleMap.getUiSettings().setZoomGesturesEnabled(true);
    
                break;
    
                case MotionEvent.ACTION_POINTER_UP:
                    // UPDATE: add below line to disable zoom gesture
                    mGoogleMap.getUiSettings().setZoomGesturesEnabled(false);
                    mGoogleMap.getUiSettings().setScrollGesturesEnabled(false);
                    mLockableScroll.setScrollingEnabled(true);
                break;
    
                case MotionEvent.ACTION_UP:
                    // UPDATE: add below line to disable zoom gesture
                    mGoogleMap.getUiSettings().setZoomGesturesEnabled(false);
                    mGoogleMap.getUiSettings().setScrollGesturesEnabled(false);
                    mLockableScroll.setScrollingEnabled(true);
                break;
            }
            return super.dispatchTouchEvent(event);
        }
    }
    

    MapFragment in this case can be like that:

    public class MultiTouchMapFragment extends MapFragment {
        public View mOriginalContentView;
        public TouchableWrapper mTouchView;
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
            mOriginalContentView = super.onCreateView(inflater, parent, savedInstanceState);
            mTouchView = new TouchableWrapper(getActivity());
            mTouchView.addView(mOriginalContentView);
            return mTouchView;
        }
    
        @Override
        public View getView() {
            return mOriginalContentView;
        }
    }
    

    and MainActivity like that (there is no need to add marker on map - it's just for test):

    public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {
        private LockableScrollView mLockableScrollView;
        private MultiTouchMapFragment mapFragment;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mLockableScrollView = (LockableScrollView) findViewById(R.id.lockable_scroll);
            mapFragment = (MultiTouchMapFragment) getFragmentManager()
                    .findFragmentById(R.id.map_fragment);
            mapFragment.getMapAsync(this);
        }
    
        @Override
        public void onMapReady(GoogleMap googleMap) {
            LatLng marker = new LatLng(48, 38);
            googleMap.addMarker(new MarkerOptions().position(marker).title("Scroll"));
            googleMap.getUiSettings().setAllGesturesEnabled(false);
    
            mapFragment.mTouchView.setGoogleMapAndScroll(googleMap, mLockableScrollView);
        }
    }
    

    and, finally, activity_main.xml for MainActivity, for example, can be like this:

    <[your_package_name].LockableScrollView
        android:id="@+id/lockable_scroll"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
    
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:src="@mipmap/ic_launcher"/>
    
            <fragment
                android:id="@+id/map_fragment"
                android:name="[your_package_name].MultiTouchMapFragment"
                android:layout_width="match_parent"
                android:layout_height="300dp"/>
    
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:src="@mipmap/ic_launcher"/>
    
        </LinearLayout>
    
    </[your_package_name].LockableScrollView>
    

    and that's it.