web-dev-qa-db-ja.com

ScrollView内のGoogle Maps API v2 SupportMapFragment-ユーザーは地図を垂直にスクロールできません

ユーザーが他のコンテンツをスクロールして地図を表示できるように、Googleマップをスクロールビュー内に配置しようとしています。問題は、このスクロールビューがすべての垂直タッチイベントを使い果たしているため、マップのUIエクスペリエンスが非常に奇妙になることです。

GoogleマップのV1では、onTouchまたはsetOnTouchListenerをオーバーライドして、MotionEvent.ACTION_DOWNでrequestDisallowInterceptTouchEventを呼び出すことができます。私はV2で同様のトリックを実装しようとしましたが、役に立ちませんでした。

これまで私は試しました:

  • SupportMapFragmentをオーバーライドし、onCreateView内で、ビューのオンタッチリスナーを設定します。
  • supportMapFragmentインスタンスの.getView()を呼び出してから、setOnTouchListener
  • 相対レイアウトまたはフレームレイアウトをラップし、透明なビューまたはイメージビューでフラグメントをマスクします

これらのどれもスクロールの問題を解決しませんでした。ここに何かが足りませんか?スクロールビュー内に実際の地図の例がある場合は、コード例を教えてください。

51
In-Ho Yi

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>   
_

次に、メインのScrollViewにrequestDisallowInterceptTouchEvent(true)を設定します。ユーザーが透明画像に触れて移動すると、_MotionEvent.ACTION_DOWN_および_MotionEvent.ACTION_MOVE_の透明画像のタッチが無効になり、マップフラグメントがタッチイベントを取得できるようになります。

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

これは私のために働いた。お役に立てば幸いです。

135
Laksh

同様の問題に遭遇し、上記のIn-Ho YiとДанаилДимитровの回答に基づいて、より一般的な作業ソリューションを思い付きました。

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

ご提案ありがとうございます、

多くの試行錯誤の後、私の髪を脱いでモニターと私の貧しいAndroidテスト電話で宣誓しました。ScrollViewをカスタマイズすると、onInterceptTouchEventをオーバーライドして、イベントが何であれマップビュー上にある場合、マップのスクロールは期待どおりに行われます。

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

このコードはScalaにありますが、アイデアは得られます。

注:生のマップビューを使用することになりました(Android-sdks\extras\google\google_play_services\samples\maps\src\com\example\mapdemoRawMapViewDemoActivity.Javaを参照)。フラグメントでもほぼ同じことができると思いますが、そもそもフラグメントが好きではありませんでした。

Googleには謝罪があると思います。

9
In-Ho Yi

私は同じ問題を抱えていたので、誰かが必要な場合に備えてJavaコードとしてソリューションを使用した方法を示します。使用するときはmapViewフィールドを設定するだけです。

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

私の場合、受け入れられた答えはうまくいきませんでした。ゲストの答えはどちらもしませんでした(しかしほとんど)。これが他の人に当てはまる場合は、この編集済みバージョンのゲストの回答を試してください。

ヒットボックスを計算するときに誰かが使用する必要がある場合、アクションバーの高さをコメントアウトしました。

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

XMLでカスタムのGoogleマップフラグメントを使用します。

ここに私のために働いた完全なコードがあります。質問がありましたら教えてください。

XMLファイルに、以下をマップフラグメントとして追加

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

これがマップのカスタムクラスです

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

mapviewを初期化するには、アクティビティクラスに次を追加します。それだけです。多田:)

((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

透明な画像が再度必要ない場合のコードの改善:

// 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

上記のオプションのほとんどは私には機能しませんでしたが、次の問題は私にとって問題の素晴らしい解決策であることが判明しました。

チーズ男爵のソリューション

また、マップをフラグメントで使用しているため、実装が多少複雑になりますが、作業が簡単になるため、実装する必要があります。

0
Kyle