web-dev-qa-db-fra.com

API Google Maps v2 SupportMapFragment dans ScrollView - les utilisateurs ne peuvent pas faire défiler la carte verticalement

J'essaie de placer une carte Google dans une vue de défilement afin que l'utilisateur puisse faire défiler d'autres contenus pour voir la carte. Le problème est que cette vue par défilement absorbe tous les événements touchants verticaux, de sorte que l'expérience de la carte de l'interface utilisateur devient très étrange.

Je sais que dans la V1 de Google Map, vous pouvez remplacer OnTouch ou définirOnTouchListener pour appeler requestDisallowInterceptTouchEvent à MotionEvent.ACTION_DOWN. J'ai essayé d'implémenter le même truc avec V2 en vain.

Jusqu'ici j'ai essayé:

  • Remplacez SupportMapFragment et dans onCreateView, définissez un écouteur tactile pour la vue.
  • appelez .getView () d'une instance SupportMapFragment, puis setOnTouchListener
  • Enroulez la disposition relative ou la disposition du cadre, masquez le fragment avec une vue transparente ou une vue d'image 

Aucune de ces solutions n'a résolu le problème du défilement. Est-ce que j'ai râté quelque chose? Si quelqu'un a un exemple fonctionnel de carte dans la vue défilante, pourriez-vous s'il vous plaît bien vouloir partager un exemple de code?

49
In-Ho Yi

Appliquez une image transparente sur le fragment mapview.

<RelativeLayout
    Android:id="@+id/map_layout"
    Android:layout_width="match_parent"
    Android:layout_height="300dp">

    <fragment
        Android:id="@+id/mapview"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:layout_marginTop="-100dp"
        Android:layout_marginBottom="-100dp"
        Android:name="com.google.Android.gms.maps.MapFragment"/>

    <ImageView
        Android:id="@+id/transparent_image"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:src="@color/transparent" />

</RelativeLayout>   

Puis définissez requestDisallowInterceptTouchEvent(true) pour le ScrollView principal. Lorsque l'utilisateur touche l'image transparente et se déplace, désactivez cette dernière pour MotionEvent.ACTION_DOWN et MotionEvent.ACTION_MOVE afin que le fragment de carte puisse prendre des événements tactiles.

ScrollView mainScrollView = (ScrollView) findViewById(R.id.main_scrollview);
ImageView transparentImageView = (ImageView) findViewById(R.id.transparent_image);

transparentImageView.setOnTouchListener(new View.OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int action = event.getAction();
        switch (action) {
           case MotionEvent.ACTION_DOWN:
                // Disallow ScrollView to intercept touch events.
                mainScrollView.requestDisallowInterceptTouchEvent(true);
                // Disable touch on transparent view
                return false;

           case MotionEvent.ACTION_UP:
                // Allow ScrollView to intercept touch events.
                mainScrollView.requestDisallowInterceptTouchEvent(false);
                return true;

           case MotionEvent.ACTION_MOVE:
                mainScrollView.requestDisallowInterceptTouchEvent(true);
                return false;

           default: 
                return true;
        }   
    }
});

Cela a fonctionné pour moi. J'espère que ça vous aide ..

125
Laksh

J'ai rencontré un problème similaire et ai proposé une solution de travail plus générale basée sur les réponses d'In-Ho Yi et Данаил Димитров ci-dessus.

public class CustomScrollView extends ScrollView {

    List<View> mInterceptScrollViews = new ArrayList<View>();

    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);
    }

    public void addInterceptScrollView(View view) {
        mInterceptScrollViews.add(view);
    }

    public void removeInterceptScrollView(View view) {
        mInterceptScrollViews.remove(view);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

        // check if we have any views that should use their own scrolling
        if (mInterceptScrollViews.size() > 0) {
            int x = (int) event.getX();
            int y = (int) event.getY();
            Rect bounds = new Rect();

            for (View view : mInterceptScrollViews) {
                view.getHitRect(bounds);
                if (bounds.contains(x, y + scrollY)) {
                    //were touching a view that should intercept scrolling
                    return false;
                }
            }
        }

        return super.onInterceptTouchEvent(event);
    }
}
15
guest

Merci pour vos suggestions,

Après beaucoup d'essais et d'erreurs, enlevant mes cheveux et jurant devant un moniteur et mon mauvais téléphone de test Android, je me suis dit que si je personnalise ScrollView, remplacez onInterceptTouchEvent dans lequel nous retournons false lorsque l'événement est sur une carte, peu importe. quoi, alors le défilement sur une carte se produit comme prévu.

class MyScrollView(c:Context, a:AttributeSet) extends ScrollView(c,a) {
  val parent = c.asInstanceOf[MyActivity]
  override def onInterceptTouchEvent(ev:MotionEvent):Boolean = {
    var bound:Rect = new Rect()
    parent.mMap.getHitRect(bound)
    if(bound.contains(ev.getX.toInt,ev.getY.toInt))
      false
    else
      super.onInterceptTouchEvent(ev)
  }
}

Ce code est en Scala mais vous voyez l'idée. 

Remarque J'ai fini par utiliser une vue de carte brute (comme indiqué dans Android-sdks\extras\google\google_play_services\samples\maps\src\com\example\mapdemoRawMapViewDemoActivity.Java). Je suppose que vous pouvez faire à peu près la même chose avec des fragments, je n’ai jamais aimé les fragments au départ.

Je pense que Google me doit des excuses.

9
In-Ho Yi

J'avais le même problème. Voici comment j'ai utilisé la solution sous forme de code Java au cas où quelqu'un en aurait besoin. Il vous suffit de définir le champ mapView lors de son utilisation.

import com.google.Android.gms.maps.MapView;

import Android.content.Context;
import Android.util.AttributeSet;
import Android.view.MotionEvent;
import Android.view.View;
import Android.widget.ScrollView;

public class ScrollViewWithMap extends ScrollView
{
    public MapView mapView;

    public ScrollViewWithMap(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        if (mapView == null)
            return super.onInterceptTouchEvent(ev);

        if (inRegion(ev.getRawX(), ev.getRawY(), mapView))
            return false;

        return super.onInterceptTouchEvent(ev);
    }

    private boolean inRegion(float x, float y, View v)
    {
        int[] mCoordBuffer = new int[]
        { 0, 0 };

        v.getLocationOnScreen(mCoordBuffer);

        return mCoordBuffer[0] + v.getWidth() > x && // right Edge
                mCoordBuffer[1] + v.getHeight() > y && // bottom Edge
                mCoordBuffer[0] < x && // left Edge
                mCoordBuffer[1] < y; // top Edge
    }
}

La réponse acceptée n'a pas fonctionné dans mon cas. La réponse de l'invité ne fit ni l'un ni l'autre (mais presque). Si tel est le cas pour quelqu'un d'autre, essayez cette version modifiée de la réponse de l'invité.

J'ai commenté la hauteur de la barre d'action si quelqu'un devait l'utiliser pour calculer la hitbox.

public class InterceptableScrollView extends ScrollView {

    List<View> mInterceptScrollViews = new ArrayList<View>();

    public InterceptableScrollView(Context context) {
        super(context);
    }

    public InterceptableScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public InterceptableScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void addInterceptScrollView(View view) {
        mInterceptScrollViews.add(view);
    }

    public void removeInterceptScrollView(View view) {
        mInterceptScrollViews.remove(view);
    }

    private int getRelativeTop(View myView) {
        if (myView.getParent() == this)
            return myView.getTop();
        else
            return myView.getTop() + getRelativeTop((View) myView.getParent());
    }
    private int getRelativeLeft(View myView) {
        if (myView.getParent() == this)
            return myView.getLeft();
        else
            return myView.getLeft() + getRelativeLeft((View) myView.getParent());
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

        // check if we have any views that should use their own scrolling
        if (mInterceptScrollViews.size() > 0) {
            int x = (int) event.getX();
            int y = (int) event.getY();


            /*
            int actionBarHeight = 0;

            TypedValue tv = new TypedValue();
            if (getContext().getTheme().resolveAttribute(Android.R.attr.actionBarSize, tv, true))
            {
                actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,getResources().getDisplayMetrics());
            }
            */

            int viewLocationY = 0;
            int viewLocationX = 0;
            int relativeTop = 0;
            int relativeLeft = 0;

            for (View view : mInterceptScrollViews) {

                relativeTop = getRelativeTop((View) view.getParent());
                relativeLeft = getRelativeLeft((View) view.getParent());
                viewLocationY = relativeTop - getScrollY();
                viewLocationX = relativeLeft - getScrollX();

                if (view.getHeight() + viewLocationY > y && y > viewLocationY && view.getWidth() + viewLocationX > x && x > viewLocationX)
                {
                    return false;
                }
            }
        }

        return super.onInterceptTouchEvent(event);
    }
}
1
Joakim Sandqvist

Utilisez un fragment de carte Google personnalisé dans votre XML.

Voici le code complet qui a fonctionné pour moi. Si vous avez des questions, s'il vous plaît faites le moi savoir.

Dans votre fichier XML, ajoutez les éléments suivants en tant que fragment de carte

<fragment
        Android:id="@+id/map_with_scroll_fix"
        Android:name="com.myapplication.maputil.GoogleMapWithScrollFix"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent" />

Voici la classe personnalisée de la carte

package com.myapplication.maputil;

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 com.google.Android.gms.maps.SupportMapFragment;
    public class GoogleMapWithScrollFix extends SupportMapFragment {
        private OnTouchListener mListener;

        @Override
        public View onCreateView(LayoutInflater layoutInflater, ViewGroup viewGroup, Bundle savedInstance) {
            View layout = super.onCreateView(layoutInflater, viewGroup, savedInstance);

            TouchableWrapper touchableWrapper = new TouchableWrapper(getActivity());

            touchableWrapper.setBackgroundColor(getResources().getColor(Android.R.color.transparent));

            ((ViewGroup) layout).addView(touchableWrapper,
                    new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

            return layout;
        }

        public void setListener(OnTouchListener listener) {
            mListener = listener;
        }

        public interface OnTouchListener {
            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);
            }
        }
    }

Ajoutez ce qui suit dans votre classe d’activité, pour initialiser mapview. C'est tout. Tada :)

((GoogleMapWithScrollFix) getSupportFragmentManager()
                .findFragmentById(R.id.map_with_scroll_fix)).getMapAsync(new OnMapReadyCallback() {
            @Override
            public void onMapReady(GoogleMap googleMap) {
                ScrollView mScrollView = findViewById(R.id.scrollview); //parent scrollview in xml, give your scrollview id value
                ((GoogleMapWithScrollFix) getSupportFragmentManager()
                        .findFragmentById(R.id.map_with_scroll_fix)).setListener(new GoogleMapWithScrollFix.OnTouchListener() {
                    @Override
                    public void onTouch() {
                        //Here is the magic happens.
                        //we disable scrolling of outside scroll view here
                        mScrollView.requestDisallowInterceptTouchEvent(true);
                    }
                });
            }
        });
0
Saamzzz

Amélioration du code si vous n’avez plus besoin d’être transparent Image

// gmap hack for touch and scrollview
        final ScrollView mainScrollView = (ScrollView) rootView.findViewById(R.id.scrollView);
        (rootView.findViewById(R.id.fixTouchMap)).setOnTouchListener(new View.OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = event.getAction();
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                        // Disallow ScrollView to intercept touch events.
                        mainScrollView.requestDisallowInterceptTouchEvent(true);
                        // Disable touch on transparent view
                        return false;

                    case MotionEvent.ACTION_UP:
                        // Allow ScrollView to intercept touch events.
                        mainScrollView.requestDisallowInterceptTouchEvent(false);
                        return true;

                    case MotionEvent.ACTION_MOVE:
                        mainScrollView.requestDisallowInterceptTouchEvent(true);
                        return false;

                    default:
                        return true;
                }
            }
        });
0
Ninja Coding

La plupart des options énumérées ci-dessus ne fonctionnaient pas pour moi, mais les solutions suivantes se sont avérées être une excellente solution au problème:

Fromage Barons Solution

J'ai également dû mettre en œuvre une configuration légèrement différente pour ma mise en œuvre, car j'utilise la carte dans un fragment qui rend les choses un peu plus compliquées mais faciles à utiliser.

0
Kyle