I want to create a custom info window in my Xamarin forms map,How can I implement this.I am using xamarin.forms.map map plugin to create map.
Please Help me
I want a custom info window like
this
i make custom map and custom pin
public class CustomMap : Map
{
public List<CustomPin> CustomPins { get; set; }
}
Custom Pin
public class CustomPin : Pin
{
public string ImageUrl { get; set; }
public float rating { get; set; }
}
Map page xaml.cs
public partial class MapPage : ContentPage
{
CustomPin pin;
MapVM MapVM;
public MapPage()
{
InitializeComponent();
pin = new CustomPin
{
Type = PinType.Place,
Position = new Position(37.79752, -122.40183),
Label = "Xamarin San Francisco Office",
Address = "394 Pacific Ave, San Francisco CA",
Url = "http://xamarin.com/about/",
rating = 3
};
var pin1 = new CustomPin
{
Type = PinType.Place,
Position = new Position(38.79752, -124.40183),
Label = "Xamarin San Francisco Office",
Address = "395 Pacific Ave, San Francisco CA",
Url = "http://xamarin.com/about/",
rating=2
};
customMap.CustomPins = new List<CustomPin> { pin, pin1 };
customMap.Pins.Add(pin);
customMap.Pins.Add(pin1);
customMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(37.79752, -122.40183), Distance.FromMiles(1.0)));
}
}
now i don't know about custom render class. please help me how i can define
custom render class and how i assign the image value and rating values to display in info window..
CustomRender.cs
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace CustomRenderer.Droid
{
public class CustomMapRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter
{
List<CustomPin> customPins;
public CustomMapRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
NativeMap.InfoWindowClick -= OnInfoWindowClick;
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
customPins = formsMap.CustomPins;
Control.GetMapAsync(this);
}
}
protected override void OnMapReady(GoogleMap map)
{
base.OnMapReady(map);
NativeMap.InfoWindowClick += OnInfoWindowClick;
NativeMap.SetInfoWindowAdapter(this);
}
protected override MarkerOptions CreateMarker(Pin pin)
{
var marker = new MarkerOptions();
marker.SetPosition(new LatLng(pin.Position.Latitude, pin.Position.Longitude));
marker.SetTitle(pin.Label);
marker.SetSnippet(pin.Address);
return marker;
}
public Android.Views.View GetInfoContents (Marker marker)
{
var inflater = Android.App.Application.Context.GetSystemService (Context.LayoutInflaterService) as Android.Views.LayoutInflater;
if (inflater != null) {
Android.Views.View view;
var customPin = GetCustomPin (marker);
if (customPin == null) {
throw new Exception ("Custom pin not found");
}
if (customPin.Id.ToString() == "Xamarin") {
view = inflater.Inflate (Resource.Layout.XamarinMapInfoWindow, null);
} else {
view = inflater.Inflate (Resource.Layout.MapInfoWindow, null);
}
var infoTitle = view.FindViewById<TextView> (Resource.Id.InfoWindowTitle);
var infoSubtitle = view.FindViewById<TextView(Resource.Id.InfoWindowSubtitle);
var imageView = view.FindViewById<ImageView>(Resource.Id.image);
var ratings = view.FindViewById<RatingBar>(Resource.Id.ratingbar);
//here how i set values for Image and Rating Cotrol
if (infoTitle != null) {
infoTitle.Text = marker.Title;
}
if (infoSubtitle != null) {
infoSubtitle.Text = marker.Snippet;
}
return view;
}
return null;
}
}
}
You can define a component that based on Xamarin.Forms component and you
can modify it like adding some bindable property,object etc. Also you can derive from a layout like stack , flow etc. instead of Xamarin.Forms component. Thus you can improve customizing level.
On Android side
[assembly: ExportRenderer(typeof(CustomEntry), typeof(AndroidCustomEntryRenderer))]
namespace MyProject.Droid.Renderer
{
public class AndroidCustomEntryRenderer : EntryRenderer
{
public AndroidCustomEntryRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.SetBackgroundColor(global::Android.Graphics.Color.White);
}
}
}
}
On PCL side
namespace MyProject.Views.ViewComponents
{
public class CustomEntry : Entry
{
}
}
Related
Greetings to the community. This is my first question, please guide me if I did any mistake.
I have four fragments in my app. An activity(Main Activity) that hosts all the four fragments.
Google Maps Fragment
Fragment two
Fragment three
Fragment four
When the application starts. Maps fragment is loaded and it shows marker at my current location. But when I move from Maps fragment to Fragment two and then came back to Map Fragment it doesn't show my current location
Here is the code
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
// Fragments
private MapsFragment mapsFragment = new MapsFragment();
private QrScanFragment qrScanFragment = new QrScanFragment();
private SeatsFullFragment seatsFullFragment = new SeatsFullFragment();
private EmergencyFragment emergencyFragment = new EmergencyFragment();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initLayouts();
initListeners();
// Maps Fragment Loaded
mIvMaps.setBackground(getResources().getDrawable(R.drawable.bg_tint_icon));
loadFragment(mapsFragment);
}
}
loadFragment(Fragment fragment)
public void loadFragment(Fragment fragment) {
if (fragment != null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.frame_container, fragment)
.commit();
}
}
Moving b/w fragments
if (view == mLlMaps) {
if (!mapsFragment.isVisible()) {
loadFragment(mapsFragment);
mTvTitle.setText("Map");
}
mIvMaps.setImageResource(R.drawable.ic_map_pin_2_line_fill);
mIvMaps.setBackground(getResources().getDrawable(R.drawable.bg_tint_icon));
} else if (view == mLlQrScan) {
if (!qrScanFragment.isVisible()) {
loadFragment(qrScanFragment);
mTvTitle.setText("ScanQR");
}
mIvQrScan.setImageResource(R.drawable.ic_baseline_qr_code_scanner_24_fill);
mIvQrScan.setBackground(getResources().getDrawable(R.drawable.bg_tint_icon));
}
}
MapsFragment.java
public class MapsFragment extends Fragment implements OnMapReadyCallback {
private GoogleMap mMap;
private SupportMapFragment mapFragment;
// To get Current Location of Driver
private FusedLocationProviderClient fusedLocationProviderClient;
private LocationRequest locationRequest;
private LocationCallback locationCallback;
public MapsFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_maps, container, false);
buildLocationRequest(); // Request for current Location
buildLocationCallback(); // When location is provided
updateLocation(); // Fused Location Provider
// Child Fragment Manager copied from Uber
mapFragment = (SupportMapFragment) getChildFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
return view;
}
// Request for current Location
private void buildLocationRequest() {
if (locationRequest == null) {
locationRequest = new LocationRequest();
locationRequest.setSmallestDisplacement(50f); // 50m
locationRequest.setInterval(15000); // 15s
locationRequest.setFastestInterval(10000); // 10s
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
}
// When location result is provided
private void buildLocationCallback() {
if (locationCallback == null) {
locationCallback = new LocationCallback() {
#Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
LatLng newPosition = new LatLng(locationResult.getLastLocation().getLatitude(),
locationResult.getLastLocation().getLongitude());
// 18f is the radius of circle
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(newPosition, 18f));
}
};
}
}
// Fused Location Provider
private void updateLocation() {
if (fusedLocationProviderClient == null) {
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(getContext());
if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&&
ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(getActivity(), "Permission Required", Toast.LENGTH_SHORT).show();
return;
}
fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
}
}
#Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
Log.d("EB", "onMapReady: called");
// Check Permission
Dexter.withContext(getContext())
.withPermission(Manifest.permission.ACCESS_FINE_LOCATION)
.withListener(new PermissionListener() {
#Override
public void onPermissionGranted(PermissionGrantedResponse permissionGrantedResponse) {
if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&&
ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
mMap.setMyLocationEnabled(true);
mMap.getUiSettings().setMyLocationButtonEnabled(true);
mMap.setOnMyLocationButtonClickListener(new GoogleMap.OnMyLocationButtonClickListener() {
#Override
public boolean onMyLocationButtonClick() {
if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.ACCESS_FINE_LOCATION}, 1);
} else {
Toast.makeText(getContext(), "Permission Granted", Toast.LENGTH_SHORT).show();
}
fusedLocationProviderClient.getLastLocation().addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(getContext(), "" + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}).addOnSuccessListener(new OnSuccessListener<Location>() {
#Override
public void onSuccess(Location location) {
LatLng userLatLng = new LatLng(location.getLatitude(),
location.getLongitude());
// 18f is the radius of circle
mMap.animateCamera(CameraUpdateFactory
.newLatLngZoom(userLatLng, 18f));
}
});
return true;
}
});
// Set Layout - Location Button
View locationButton = ((View) mapFragment.getView().findViewById(Integer.parseInt("1"))
.getParent())
.findViewById(Integer.parseInt("2"));
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)
locationButton.getLayoutParams();
//Right Bottom
params.addRule(RelativeLayout.ALIGN_PARENT_TOP, 0);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
params.setMargins(0, 0, 0, 50);
// Move to current Location
buildLocationRequest();
buildLocationCallback();
updateLocation();
}
#Override
public void onPermissionDenied(PermissionDeniedResponse permissionDeniedResponse) {
Toast.makeText(getContext(),
"Permission" + permissionDeniedResponse.getPermissionName() + "wasdenied",
Toast.LENGTH_SHORT).show();
}
#Override
public void onPermissionRationaleShouldBeShown(PermissionRequest permissionRequest, PermissionToken permissionToken) {
}
}).check();
// Change/Parse the Maps Style
try {
boolean success = googleMap.setMapStyle(MapStyleOptions
.loadRawResourceStyle(getContext(), R.raw.uber_maps_style));
if (!success)
Log.e("Error", "Style parsing Error");
} catch (Resources.NotFoundException e) {
Log.e("Error", e.getMessage());
}
}
}
In on mapReady function of google map i have written
below: it basically forms cluster of marker location data and pass it
to the cluster manager
How can i do googleMap.animateCamera() on getInfoWindow() as it not working to get zoomed in by animating camera on click of marker
with popup opened.
mClusterManager = new ClusterManager<>(getActivity(), mMap);
mClusterManager.setAlgorithm(getAlgorithm());
mClusterManager.setRenderer(new MyRenderer(getActivity(), mMap));
mClusterManager.setAnimation(true);
//To set custom dialog on marker click
mClusterManager.getMarkerCollection().setInfoWindowAdapter(new PubLocationCustomClusterInfoView(this, getActivity(), pubArrayList, this, getMap()));
mClusterManager.setOnClusterItemClickListener(new ClusterManager.OnClusterItemClickListener<PubLocation>() {
#Override
public boolean onClusterItemClick(PubLocation item) {
return false;
}
});
PubLocationCustomClusterInfoView.java -- This is my custom view for
marker popup where i want to zoom in by animating google camera but im
unable to.
public class PubLocationCustomClusterInfoView implements GoogleMap.InfoWindowAdapter {
private View clusterItemView;
private static final String TAG = "PubLocationCustomCluste";
Context context;
private OnInfoWindowElemTouchListener infoButtonListener,orderFromPubListener;
ArrayList<PubData.Pub> pubArrayList;
MarkerInfoWindow markerInfoWindowlistner;
MapMarkerOnClickListener mapMarkerOnClickListener;
GoogleMap googleMap;
public PubLocationCustomClusterInfoView(MarkerInfoWindow markerInfoWindow, Context context, ArrayList<PubData.Pub> pubArrayList, MapMarkerOnClickListener mapMarkerOnClickListener,GoogleMap googleMap) {
LayoutInflater layoutInflater = LayoutInflater.from(context);
this.context=context;
this.markerInfoWindowlistner=markerInfoWindow;
this.pubArrayList=pubArrayList;
clusterItemView = layoutInflater.inflate(R.layout.marker_info_window, null);
this.mapMarkerOnClickListener = mapMarkerOnClickListener;
this.googleMap=googleMap;
}
#Override
public View getInfoWindow(Marker marker) {
Log.i(TAG, "getInfoWindow: "+marker);
return null;
}
#Override
public View getInfoContents(Marker marker) {
Log.i(TAG, "getInfoContents: "+marker);
PubLocation pubLocation = (PubLocation) marker.getTag();
// googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(marker.getPosition(),13));
if (pubLocation == null) return clusterItemView;
TextView itemNameTextView = clusterItemView.findViewById(R.id.itemNameTextView);
TextView itemAddressTextView = clusterItemView.findViewById(R.id.itemAddressTextView);
ImageButton pub_info=(ImageButton)clusterItemView.findViewById(R.id.pub_info);
Button order_from_pub=(Button) clusterItemView.findViewById(R.id.order_from_pub);
this.infoButtonListener = new OnInfoWindowElemTouchListener(pub_info) //btn_default_pressed_holo_light
{
#Override
protected void onClickConfirmed(View v, Marker marker) {
// Here we can perform some action triggered after clicking the button
Log.i(TAG, "onClickConfirmed: ");
Intent intent=new Intent(context,PubInfoActivity.class);
intent.putExtra(Constants.PUB_ID_KEY, pubLocation.getPubId());
context.startActivity(intent);
}
};
pub_info.setOnTouchListener(infoButtonListener);
this.orderFromPubListener = new OnInfoWindowElemTouchListener(order_from_pub) //btn_default_pressed_holo_light
{
#Override
protected void onClickConfirmed(View v, Marker marker) {
// Here we can perform some action triggered after clicking the button
for (int i = 0; i < pubArrayList.size(); i++) {
if (pubArrayList.get(i).getPubId().equals(pubLocation.getPubId())) {
mapMarkerOnClickListener.onMarkerClick(pubArrayList.get(i),v);
break;
}
}
}
};
order_from_pub.setOnTouchListener(orderFromPubListener);
itemNameTextView.setText(pubLocation.getTitle());
itemAddressTextView.setText(pubLocation.getSnippet());
markerInfoWindowlistner.setMarkerInfoWindow(clusterItemView,marker);
return clusterItemView;
}
}
Finally, got the solution to this issue
First set clustermanager to map as below:
getMap().setOnMarkerClickListener(mClusterManager);
On click of marker you just need to return true by adding one below condition and and pass zoom level to google map camera
mClusterManager.setOnClusterItemClickListener(new ClusterManager.OnClusterItemClickListener<PubLocation>() {
#Override
public boolean onClusterItemClick(PubLocation item) {
Log.i(TAG, "onClusterItemClick: ");
for (Marker marker : mClusterManager.getMarkerCollection().getMarkers()) {
if (marker.getPosition().latitude == item.getPosition().latitude &&
marker.getPosition().longitude == item.getPosition().longitude) {
marker.showInfoWindow();
}
}
CameraUpdate location = CameraUpdateFactory.newLatLngZoom(item.getPosition(), 10);
getMap().animateCamera(location, new GoogleMap.CancelableCallback() {
#Override
public void onFinish() {
getMap().animateCamera(CameraUpdateFactory.newLatLng(getMap().getProjection().fromScreenLocation(mappoint)));
}
#Override
public void onCancel() {
}
});
return true;
}
});
I am building an android GPS app with Codename one. I use com.codename1.googlemaps.MapContainer to create a Google map;
In my app is use tabs to create different "pages".
Code:
cnt = new MapContainer();
t.addTab("Tab3", cnt);
And for my current location I use:
try {
Coord position = new Coord(lat,lng);
cnt.clearMapLayers();
cnt.setCameraPosition(position);
cnt.addMarker(EncodedImage.create("/maps-pin.png"), position, "Hi marker", "Optional long description", new ActionListener() {
public void actionPerformed(ActionEvent evt) {
// stuff todo...
}
});
} catch(IOException err) {
// since the image is iin the jar this is unlikely
err.printStackTrace();
}
I like to add a wms layer to the Google Maps. Is this possible? I can't find in codenameone a command addLayer. If yes, do you have a code snippet how to do this?
If it is not possiple, can I use openlayers in my codename one app? Can you give me a code snippet to do this?
Edit
I started to create an native file to "catch"the addtileoverlay from google maps api. The layer I want to use is a xyz layer, so I think I can use a urltileprovider from the googlemap api
I made the native code for the tileoverlay but the tileoverlay doesn't appear. Is it because i didn't get a link with the mapcontainer.
I am little bit stuck. I tried to build from scratch with the googmaps example but the mapcompnent is not anymore used.
package com.Bellproductions.TalkingGps;
import com.google.android.gms.maps.model.UrlTileProvider;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.TileOverlayOptions;
import com.google.android.gms.maps.model.TileProvider;
import com.google.android.gms.maps.model.TileOverlay;
import com.codename1.impl.android.AndroidNativeUtil;
import java.util.Locale;
import java.net.MalformedURLException;
public class SeamarksImpl {
private GoogleMap mapInstance;
private TileOverlay m;
TileProvider provider;
public boolean isSupported() {
return true;
}
public long addTilelayer (){
final String URL_FORMAT = "http://t1.openseamap.org/seamark/{z}/{x}/{y}.png";
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
provider = new UrlTileProvider(256, 256) {
#Override
public synchronized URL getTileUrl(int x, int y, int zoom) {
try {
y = (1 << zoom) - y - 1;
return new URL(String.format(Locale.US, URL_FORMAT, zoom, x, y ));
} catch (MalformedURLException e) {
throw new RuntimeException();
}
}
TileOverlayOptions tileopt = new TileOverlayOptions().tileProvider(provider);
public void addlayer() {
m = mapInstance.addTileOverlay(tileopt);
}
};
}
});
long p = 1;
return p;}
}
My seamarks.java file has this code to bind with the native interface
import com.codename1.system.NativeInterface;
/**
*
* #author Hongerige Wolf
*/
public interface Seamarks extends NativeInterface {
public void addTilelayer ();
}
In the mainactivity java file i have the statements
public Seamarks seamark;
public void init(Object context) {
seamark = (Seamarks)NativeLookup.create(Seamarks.class);
}
public void start() {
seamark.addTilelayer();
}
Update
I created a new googlemaps.CN1lib. But the xyz layer is not showing on the googlemaps. I used native code tot use the Tileoverlay feature and tried to add tileoverlay in the same way as Markers.
In the InternalNativeMapsImpl file i changed
private void installListeners() {
/*
if (mapInstance == null) {
view = null;
System.out.println("Failed to get map instance, it seems google play services are not installed");
return;
}*/
view.getMapAsync(new OnMapReadyCallback() {
#Override
public void onMapReady(GoogleMap googleMap) {
mapInstance = googleMap;
TileProvider tileProvider;
tileProvider = new UrlTileProvider(256, 256) {
String tileLayer= "http://t1.openseamap.org/seamark/";
#Override
public synchronized URL getTileUrl(int x, int y, int zoom) {
// The moon tile coordinate system is reversed. This is not normal.
int reversedY = (1 << zoom) - y - 1;
//String s = String.format(Locale.US, tileLayer , zoom, x, y);
String s = tileLayer + "/" + zoom + "/" + x + "/" + reversedY + ".png";
URL url = null;
try {
url = new URL(s);
} catch (MalformedURLException e) {
throw new AssertionError(e);
}
return url;
}
};
mMoonTiles = mapInstance.addTileOverlay(new TileOverlayOptions().tileProvider(tileProvider));
mapInstance.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
public boolean onMarkerClick(Marker marker) {
Long val = listeners.get(marker);
if (val != null) {
MapContainer.fireMarkerEvent(InternalNativeMapsImpl.this.mapId, val.longValue());
return true;
}
return false;
}
});
mapInstance.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
public void onCameraChange(CameraPosition position) {
MapContainer.fireMapChangeEvent(InternalNativeMapsImpl.this.mapId, (int) position.zoom, position.target.latitude, position.target.longitude);
}
});
mapInstance.setOnMapClickListener(new GoogleMap.OnMapClickListener() {
public void onMapClick(LatLng point) {
Point p = mapInstance.getProjection().toScreenLocation(point);
MapContainer.fireTapEventStatic(InternalNativeMapsImpl.this.mapId, p.x, p.y);
}
});
mapInstance.setOnMapLongClickListener(new GoogleMap.OnMapLongClickListener() {
public void onMapLongClick(LatLng point) {
Point p = mapInstance.getProjection().toScreenLocation(point);
MapContainer.fireLongPressEventStatic(InternalNativeMapsImpl.this.mapId, p.x, p.y);
}
});
mapInstance.setMyLocationEnabled(showMyLocation);
mapInstance.getUiSettings().setRotateGesturesEnabled(rotateGestureEnabled);
}
});
}
Secondly i added a addTilexyz method also in the same way as addMarkers
public long addTilexyz(final String Turl) {
uniqueIdCounter++;
final long key = uniqueIdCounter;
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
TileProvider tileProvider;
tileProvider = new UrlTileProvider(256, 256) {
// Tileurl = "http://t1.openseamap.org/seamark/";
#Override
public synchronized URL getTileUrl(int x, int y, int zoom) {
// The moon tile coordinate system is reversed. This is not normal.
int reversedY = (1 << zoom) - y - 1;
String s = String.format(Locale.US, Turl , zoom, x, reversedY);
URL url = null;
try {
url = new URL(s);
} catch (MalformedURLException e) {
throw new AssertionError(e);
}
return url;
}
};
mMoonTiles = mapInstance.addTileOverlay(new TileOverlayOptions().tileProvider(tileProvider));
}
});
return key;
}
In the InternalNativeMaps file i added te method
public long addTilexyz(String Turl);
And in the Mapcontainer file i added
public MapObject addTilexyz(String Turl) {
if(internalNative != null) {
MapObject o = new MapObject();
Long key = internalNative.addTilexyz(Turl);
o.mapKey = key;
markers.add(o);
return o;
} else {
}
MapObject o = new MapObject();
return o;
}
I am puzzeled what is wrong with the code. I wonder if the commands
Long key = internalNative.addTilexyz(Turl);
and
mMoonTiles = mapInstance.addTileOverlay(new TileOverlayOptions().tileProvider(tileProvider));
put the tileoverlay on the googlemap. Or is the tileurl wrong. http://t1.openseamap.org/seamark/z/x/y.png is correct.
We don't expose layers in the native maps at this time, you can fork the project and just add an API to support that to the native implementations.
I tried to bind a widget to a viewmodel property but I'm getting an exception
MvxBind:Warning: 14.76 Failed to create target binding for binding Signature for Order.ClientSignature
[0:] MvxBind:Warning: 14.76 Failed to create target binding for binding Signature for Order.ClientSignature
04-26 21:02:15.380 I/mono-stdout(32490): MvxBind:Warning: 14.76 Failed to create target binding for binding Signature for Order.ClientSignature
The widget is courtesy of Al taiar
The axml is
<SignatureWidget
android:layout_width="match_parent"
android:layout_height="100dp"
android:id="#+id/signatureWidget1"
android:layout_marginRight="5dp"
android:layout_marginLeft="5dp"
android:layout_marginBottom="5dp"
local:MvxBind="Signature Order.ClientSignature" />
The code for the view is
using Android.Content;
using Android.Graphics;
using Android.Util;
using Android.Views;
using Core.Models;
using System;
public class SignatureWidget
: View
{
#region Implementation
private Bitmap _bitmap;
private Canvas _canvas;
private readonly Path _path;
private readonly Paint _bitmapPaint;
private readonly Paint _paint;
private float _mX, _mY;
private const float TouchTolerance = 4;
#endregion
public Signature Signature;
public event EventHandler SignatureChanged;
public SignatureWidget(Context context, IAttributeSet attrs)
: base(context, attrs)
{
Signature = new Signature();
_path = new Path();
_bitmapPaint = new Paint(PaintFlags.Dither);
_paint = new Paint
{
AntiAlias = true,
Dither = true,
Color = Color.Argb(250, 00, 0, 0)
};
_paint.SetStyle(Paint.Style.Stroke);
_paint.StrokeJoin = Paint.Join.Round;
_paint.StrokeCap = Paint.Cap.Round;
_paint.StrokeWidth = 5;
}
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);
_bitmap = Bitmap.CreateBitmap(w, (h > 0 ? h : ((View)this.Parent).Height), Bitmap.Config.Argb8888);
_canvas = new Canvas(_bitmap);
}
protected override void OnDraw(Canvas canvas)
{
canvas.DrawColor(Color.White);
canvas.DrawBitmap(_bitmap, 0, 0, _bitmapPaint);
canvas.DrawPath(_path, _paint);
}
private void TouchStart(float x, float y)
{
_path.Reset();
_path.MoveTo(x, y);
_mX = x;
_mY = y;
Signature.AddPoint(SignatureState.Start, (int)x, (int)y);
}
private void TouchMove(float x, float y)
{
float dx = Math.Abs(x - _mX);
float dy = Math.Abs(y - _mY);
if (dx >= TouchTolerance || dy >= TouchTolerance)
{
_path.QuadTo(_mX, _mY, (x + _mX) / 2, (y + _mY) / 2);
Signature.AddPoint(SignatureState.Move, (int)x, (int)y);
_mX = x;
_mY = y;
}
}
private void TouchUp()
{
if (!_path.IsEmpty)
{
_path.LineTo(_mX, _mY);
_canvas.DrawPath(_path, _paint);
}
else
{
_canvas.DrawPoint(_mX, _mY, _paint);
}
Signature.AddPoint(SignatureState.End, (int)_mX, (int)_mY);
_path.Reset();
}
public override bool OnTouchEvent(MotionEvent e)
{
var x = e.GetX();
var y = e.GetY();
switch (e.Action)
{
case MotionEventActions.Down:
TouchStart(x, y);
Invalidate();
break;
case MotionEventActions.Move:
TouchMove(x, y);
Invalidate();
break;
case MotionEventActions.Up:
TouchUp();
Invalidate();
break;
}
RaiseSignatureChangedEvent();
return true;
}
public void ClearCanvas()
{
_canvas.DrawColor(Color.White);
Invalidate();
}
public Bitmap CanvasBitmap()
{
return _bitmap;
}
public void Clear()
{
ClearCanvas();
Signature.Clear();
RaiseSignatureChangedEvent();
}
private void RaiseSignatureChangedEvent()
{
var handler = SignatureChanged;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
And the code for the model is
public class Signature
{
private List<Point> _currentPath;
private readonly List<List<Point>> _paths;
public event EventHandler PointAdded;
public Signature()
{
_currentPath = new List<Point>();
_paths = new List<List<Point>>();
}
public IReadOnlyList<IReadOnlyList<Point>> Paths
{
get { return _paths; }
}
public Point LastPoint()
{
if (_currentPath != null && _currentPath.Count > 0)
{
return _currentPath.Last();
}
return new Point(0, 0);
}
public void Clear()
{
_paths.Clear();
_currentPath.Clear();
}
public void AddPoint(SignatureState state, int x, int y)
{
if (state == SignatureState.Start)
{
_currentPath = new List<Point>();
}
if (x != 0 && y != 0)
{
_currentPath.Add(new Point(x, y));
}
if (state == SignatureState.End)
{
if (_currentPath != null)
{
_paths.Add(_currentPath);
}
}
RaisePointAddedEvent();
}
public int Length
{
get { return _paths.Count; }
}
protected void RaisePointAddedEvent()
{
if (PointAdded != null)
PointAdded(this, EventArgs.Empty);
}
}
I will need two-way binding for this widget. Anyone care to help???
I will also need to add a "Clear" text as an overlay on the view. Clicking this text will trigger a command to clear the widget. Any clue how to do this?
P.S:
I've followed the informative post and I still cannot get it to work. I've added the following.
public class SignatureWidgetSignatureTargetBinding
: MvxPropertyInfoTargetBinding<SignatureWidget>
{
public SignatureWidgetSignatureTargetBinding(object target, PropertyInfo targetPropertyInfo)
: base(target, targetPropertyInfo)
{
View.SignatureChanged += OnSignatureChanged;
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.TwoWay; }
}
private void OnSignatureChanged(object sender, EventArgs eventArgs)
{
FireValueChanged(View.Signature);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (isDisposing)
{
View.SignatureChanged -= OnSignatureChanged;
}
}
}
and registered using
registry.RegisterFactory(new MvxSimplePropertyInfoTargetBindingFactory(typeof(SignatureWidgetSignatureTargetBinding), typeof(SignatureWidget), "Signature"));
MvvmCross will automatically bind a View property if you model it using the format:
public foo Bar {
get { /* ... your code ... */ }
set { /* ... your code ... */ }
}
public event EventHandler BarChanged;
Based on this I think your problem is that you are trying to use a field - public Signature Signature; - try using a property instead.
I think the binding mode you are looking for is also the unusual OneWayToSource instead of TwoWay
Is it somehow possible to bind view properties to ICommand.CanExecute?
I'd for example like to be able to do something like this in a touch view:
this
.CreateBinding(SignInWithFacebookButton)
.For(b => b.Enabled)
.To((SignInViewModel vm) => vm.SignInWithFacebookCommand.CanExecute)
.Apply();
I've already read How to use CanExecute with Mvvmcross, but unfortunately it skips the questions and instead just proposes another implementation.
One way of doing this is to use your own custom button inheriting from UIButton.
For Android, I've got an implementation of this to hand - it is:
public class FullButton : Button
{
protected FullButton(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
Click += OnClick;
}
public FullButton(Context context) : base(context)
{
Click += OnClick;
}
public FullButton(Context context, IAttributeSet attrs) : base(context, attrs)
{
Click += OnClick;
}
public FullButton(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs, defStyle)
{
Click += OnClick;
}
private IDisposable _subscription;
private object _commandParameter;
public object CommandParameter
{
get { return _commandParameter; }
set
{
_commandParameter = value;
UpdateEnabled();
}
}
private ICommand _command;
public ICommand Command
{
get { return _command; }
set
{
if (_subscription != null)
{
_subscription.Dispose();
_subscription = null;
}
_command = value;
if (_command != null)
{
var cec = typeof (ICommand).GetEvent("CanExecuteChanged");
_subscription = cec.WeakSubscribe(_command, (s, e) =>
{
UpdateEnabled();
});
}
UpdateEnabled();
}
}
private void OnClick(object sender, EventArgs eventArgs)
{
if (Command == null)
return;
if (Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
}
private void UpdateEnabled()
{
Enabled = ShouldBeEnabled();
}
private bool ShouldBeEnabled()
{
if (_command == null)
return false;
return _command.CanExecute(CommandParameter);
}
}
and this can be bound as:
<FullButton
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Show Detail"
local:MvxBind="Command ShowDetailCommand; CommandParameter CurrentItem" />
For iOS, I'd expect the same type of technique to work... inheriting from a UIButton and using TouchUpInside instead of Click - but I'm afraid I don't have this code with me at the moment.