Enrico
Enrico

Reputation: 6276

Xamarin, Android and Google Maps: I can't move the map up and down (only left and right)

In a Page I have a ScrollView and inside some labels and a Google Maps in the middle. In Android, when I try to move the map up or down, all page is moved but the map. To display the map I'm using Xamarin.Forms.GoogleMaps. I created my own custommap to draw a route. The component is working well, I can see the map in iOS and Android and I can move the map.

I read some posts on Stackoverflow and this post but always the guys are an Activity and the project is native.

I created:

TouchableMapFragment

public class TouchableMapFragment : MapFragment
{
  public event EventHandler TouchDown;
  public event EventHandler TouchUp;

  public override View OnCreateView(LayoutInflater inflater, 
                                    ViewGroup container, Bundle savedInstanceState)
  {
    var root = base.OnCreateView(inflater, container, savedInstanceState);
    var wrapper = new TouchableWrapper(Activity);
    wrapper.SetBackgroundColor(Resources.GetColor(Android.Resource.Color.Transparent));
    ((ViewGroup) root).AddView(wrapper,
      new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, 
      ViewGroup.LayoutParams.MatchParent));

    wrapper.TouchUp = () =>
    {
      if (TouchUp != null)
        TouchUp(this, EventArgs.Empty);
    };
    wrapper.TouchDown = () =>
    {
      if (TouchDown != null)
        TouchDown(this, EventArgs.Empty);
    };

    return root;
  }

  class TouchableWrapper : FrameLayout
  {
    public Action TouchDown;
    public Action TouchUp;

    #region ctors
    protected TouchableWrapper(IntPtr javaReference, JniHandleOwnership transfer) 
      : base(javaReference, transfer) {}
    public TouchableWrapper(Context context) 
      : this(context, null) {}
    public TouchableWrapper(Context context, IAttributeSet attrs) 
      : this(context, attrs, 0) {}
    public TouchableWrapper(Context context, IAttributeSet attrs, int defStyle) 
      : base(context, attrs, defStyle) { }
    #endregion

    public override bool DispatchTouchEvent(MotionEvent e)
    {
      switch (e.Action)
      {
        case MotionEventActions.Down:
          if (TouchDown != null)
            TouchDown();
          break;
        case MotionEventActions.Cancel:
        case MotionEventActions.Up:
          if (TouchUp != null)
            TouchUp();
          break;
      }

      return base.DispatchTouchEvent(e);
    }
  }
}

view.xaml

<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/scroll">
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="wrap_content"
        android:layout_height="match_parent">
        <fragment
            android:id="@+id/map"
            android:layout_width="600dp"
            android:layout_height="match_parent"
            android:name="my.awesome.namespace.TouchableMapFragment" />
    </LinearLayout>
</HorizontalScrollView>

Activity.cs

public class MyActivity : Activity
{
  private GoogleMap _map;
  private HorizontalScrollView _hsv;

  protected override void OnCreate(Bundle bundle)
  {
    base.OnCreate(bundle);
    SetContentView(Resource.Layout.rtc);

    _hsv = FindViewById<HorizontalScrollView>(Resource.Id.scroll);
    SetupMapIfNeeded();
  }

  private void SetupMapIfNeeded()
  {
    if (null != _map) return;

    var frag = FragmentManager.FindFragmentById<TouchableMapFragment>(Resource.Id.map);
    if (frag != null)
    {
      frag.TouchUp += (sender, args) => _hsv.RequestDisallowInterceptTouchEvent(false);
      frag.TouchDown += (sender, args) => _hsv.RequestDisallowInterceptTouchEvent(true);

      _map = frag.Map;
      if (_map == null) return; // will probably not happen

      // do stuff to _map here, such as adding overlays etc.
    }
  }
}

There are a couple of errors:

In my case, I have a PCL solution with iOS and Android projects. In iOS everything is fine. The only problem is for Android. How can I fix this problem?

Any advice? Thanks.

Upvotes: 0

Views: 937

Answers (2)

Grace Feng
Grace Feng

Reputation: 16652

My question is related to the code I found. I can't understand how I can use it in Xamarin.Forms

In XF framework, we only have one Activity, means the default MainAcitivty, all views are displayed based on this MainAcitivty, it you want to show a custom view from client Android project in PCL, usually we use Custom Renderer to create a view Renderer, for your case your view.axml is the layout resource in client Android project, then you should implement the logic code for this view inside the renderer.

First create a subclass of View in PCL so it can be used like normal XF control:

public class ScrollViewWithMap:View
{
}

Then in Android client project place your view.axml under the Resources/layout folder and together create it's renderer:

[assembly: ExportRenderer(typeof(ScrollViewWithMap), typeof(ScrollViewWithMapRenderer))]

namespace YOURNAMESPACE.Droid
{
    public class ScrollViewWithMapRenderer : ViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
        {
            base.OnElementChanged(e);
            if (Control == null)
            {
                var context = Xamarin.Forms.Forms.Context;
                LayoutInflater minflater = context.GetSystemService(Context.LayoutInflaterService) as LayoutInflater;
                var view = minflater.Inflate(Resource.Layout.view, this, false);
                SetNativeControl(view);
            }
        }
    }
}

Then you can move the logic code from your MyActivity to this ScrollViewWithMapRenderer for example:

public class ScrollViewWithMapRenderer : ViewRenderer
{
    private GoogleMap _map;
    private HorizontalScrollView _hsv;
    private Context context = Xamarin.Forms.Forms.Context;

    protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
    {
        base.OnElementChanged(e);
        if (Control == null)
        {
            LayoutInflater minflater = context.GetSystemService(Context.LayoutInflaterService) as LayoutInflater;
            var view = minflater.Inflate(Resource.Layout.view, this, false);
            _hsv = view.FindViewById<HorizontalScrollView>(Resource.Id.scroll);
            SetNativeControl(view);

            SetupMapIfNeeded();
        }
    }

    private void SetupMapIfNeeded()
    {
        if (null != _map) return;
        var frag = ((Activity)context).FragmentManager.FindFragmentById<TouchableMapFragment>(Resource.Id.map);
        if (frag != null)
        {
            frag.TouchUp += (sender, args) => _hsv.RequestDisallowInterceptTouchEvent(false);
            frag.TouchDown += (sender, args) => _hsv.RequestDisallowInterceptTouchEvent(true);

            _map = frag.Map;
            if (_map == null) return; // will probably not happen

            // do stuff to _map here, such as adding overlays etc.
        }
    }
}

I couldn't find a property Map in your TouchableMapFragment, I think you code in incomplete and you should be able to solve this issue. By the way, the code TouchableMapFragment should also be placed in android client project.

Finally we can use this ScrollViewWithMap in PCL for example like this:

<local:ScrollViewWithMap />

Upvotes: 1

Ganesh Cauda
Ganesh Cauda

Reputation: 1016

  1. Basically Resource.Layout.xxxx the xxxx" is the name of the layout file, i.e. xxxx.xml
  2. Any component inside layout.xml can have an "id" attribute for example if we have a

    <Button id="@+id/something"></Button>
    

    then the id of the button is "something" and we can get this view from code behind by calling

    Button btn = (Button) v.findViewById(Resource.id.something);
    
  3. Best possible solution based on your code, maybe change the view.axml to rtc.axml

Upvotes: 0

Related Questions