I have a ExpandableListView declared as follow :
<ExpandableListView
android:id="#+id/ListBExpandable"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
and I have Child Item layout declared as follow :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:minWidth="25px"
android:minHeight="25px" >
<CheckBox
android:text="CheckBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/checkBoxBListItem" />
</LinearLayout>
When I am checking the child's checkboxes and expand or close some group element the checked checkbox start jumping in seems like random position.
This is the source of the adapter :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
namespace LEX.Droid
{
public class BActivityGroupedAdapter : BaseExpandableListAdapter
{
public static List<BNodeProxy> bNodes = bew List<BNodeProxy>();
public static Dictionary<int, BGroupProxy> bGroups = new Dictionary<int, BGroupProxy>();
Activity context;
List<int> groupKeys;
List<int> filteredGroupKeys;
Dictionary<int,List<int>> filteredItems;
public BActivityGroupedAdapter( Activity context, Dictionary<int,List<int>> filteredItems )
{
this.context = context;
groupKeys = bGroups.Keys.ToList();
this.filteredGroupKeys = filteredGroupKeys;
}
public override Java.Lang.Object GetChild( int groupPosition, int childPosition )
{
int nodeId = filteredItems[ filteredGroupKeys[ groupPosition ] ][ childPosition ];
return bNodes.Where( o => o.id == nodeId ).First().label;
}
public override long GetChildId( int groupPosition, int childPosition )
{
return childPosition;
}
public override int GetChildrenCount( int groupPosition )
{
return filteredItems[ filteredGroupKeys[ groupPosition ] ].Count();
}
public override View GetChildView( int groupPosition, int childPosition, bool isLastChild, View convertView, ViewGroup parent )
{
View view;
int nodeId = filteredItems[ filteredGroupKeys[ groupPosition ] ][ childPosition ];
var item = bNodes.Where( o => o.id == nodeId ).First();
if( convertView == null )
{
view = context.LayoutInflater.Inflate( Resource.Layout.ListChildItem, null );
}
else
{
view = convertView;
}
try
{
var checkBoxSelect = view.FindViewById<CheckBox>( Resource.Id.checkBoxListItem );
checkBoxSelect.Text = item.label;
checkBoxSelect.Selected = item.selected;
checkBoxSelect.Tag = item.id;
checkBoxSelect.Click -= theClickEvent;
checkBoxSelect.Click += theClickEvent;
}
catch( Exception ex )
{
}
view.Clickable = false;
view.Focusable = false;
view.FocusableInTouchMode = false;
view.LongClickable = false;
return view;
}
public override Java.Lang.Object GetGroup( int groupPosition )
{
return bGroups[ filteredGroupKeys[ groupPosition ] ].label;
}
public override long GetGroupId( int groupPosition )
{
return groupPosition;
}
public override View GetGroupView( int groupPosition, bool isExpanded, View convertView, ViewGroup parent )
{
View view;
var item = bGroups[ filteredGroupKeys[ groupPosition ] ].label;
if( convertView == null )
{
var inflater = context.GetSystemService(Context.LayoutInflaterService) as LayoutInflater;
view = inflater.Inflate( Resource.Layout.ListGroupItem, null );
}
else
{
view = convertView;
}
try
{
var textBox = view.FindViewById<TextView>( Resource.Id.groupItemListText );
textBox.SetText( item, TextView.BufferType.Normal );
}
catch( Exception ex )
{
}
return view;
}
public override bool IsChildSelectable( int groupPosition, int childPosition )
{
return true;
}
public override int GroupCount
{
get
{
return filteredGroupKeys.Count;
}
}
public override bool HasStableIds
{
get
{
return true;
}
}
private void theClickEvent( object sender, EventArgs e )
{
var selectionState = ( (CheckBox)sender ).Checked;
int tagId = (int)( (CheckBox)sender ).Tag;
bNodes.Where( o => o.id == tagId ).First().selected = selectionState;
}
}
}
How to prevent the checkbox from this strange behavior ?
Found the bug..
checkBoxSelect.Selected = item.selected;
actually should be:
checkBoxSelect.Checked = item.selected;
seems like my stack went overflow :)
Related
i'm having a problem with receiving data from HC-05 bluetooth module. Sending data from mobile to module works fine, but i can't figure out how to receive data from module. I think there will be problem in with Thread which contains listener function.
Thank's for help
here's the code:
using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Android.Bluetooth;
using Microcharts;
using Entry = Microcharts.Entry;
using Microcharts.Droid;
using SkiaSharp;
using System.Linq;
using System.Collections.Generic;
namespace BLE
{
[Activity(Label = "BLE", MainLauncher = true)]
public class MainActivity : Activity
{
BluetoothConnection myConnection = new BluetoothConnection();
protected override void OnCreate(Bundle savedInstanceState)
{
var metrics = Resources.DisplayMetrics;
var width = metrics.WidthPixels;
var height = metrics.HeightPixels;
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
Button buttonConnect = FindViewById<Button>(Resource.Id.button1);
Button buttonDisconnect = FindViewById<Button>(Resource.Id.button2);
buttonConnect.LayoutParameters.Width = Convert.ToInt32(width * 0.5);
buttonDisconnect.LayoutParameters.Width = Convert.ToInt32(width * 0.5);
TextView connected = FindViewById<TextView>(Resource.Id.textView1);
BluetoothSocket _socket = null;
System.Threading.Thread listenThread = new System.Threading.Thread(listener);
listenThread.Abort();
buttonConnect.Click += delegate
{
try
{
buttonDisconnect.Enabled = false;
buttonConnect.Enabled = true;
listenThread.Abort();
myConnection.thisDevice.Dispose();
myConnection.thisSocket.OutputStream.WriteByte(187);
myConnection.thisSocket.OutputStream.Close();
myConnection.thisSocket.Close();
myConnection = new BluetoothConnection();
_socket = null;
connected.Text = "Disconnected!";
}
catch { }
listenThread.Start();
myConnection = new BluetoothConnection();
myConnection.thisSocket = null;
_socket = null;
myConnection.getAdapter();
myConnection.thisAdapter.StartDiscovery();
try
{
myConnection.getDevice();
myConnection.thisDevice.SetPairingConfirmation(false);
myConnection.thisDevice.Dispose();
myConnection.thisDevice.SetPairingConfirmation(true);
myConnection.thisDevice.CreateBond();
}
catch (Exception deviceEX)
{
}
myConnection.thisAdapter.CancelDiscovery();
_socket = myConnection.thisDevice.CreateRfcommSocketToServiceRecord(Java.Util.UUID.FromString("00001101-0000-1000-8000-00805f9b34fb"));
myConnection.thisSocket = _socket;
try
{
myConnection.thisSocket.Connect();
connected.Text = "Connected!";
buttonDisconnect.Enabled = true;
buttonConnect.Enabled = false;
if (listenThread.IsAlive == false)
{
listenThread.Start();
}
}
catch (Exception CloseEX)
{
}
};
buttonDisconnect.Click += delegate
{
try
{
buttonConnect.Enabled = true;
listenThread.Abort();
myConnection.thisDevice.Dispose();
myConnection.thisSocket.OutputStream.WriteByte(187);
myConnection.thisSocket.OutputStream.Close();
myConnection.thisSocket.Close();
myConnection = new BluetoothConnection();
_socket = null;
connected.Text = "Disconnected!";
}
catch { }
};
void listener()
{
TextView readTextView = FindViewById<TextView>(Resource.Id.textView2);
while (true)
{
try
{
byte[] buffer = new byte[1];
myConnection.thisSocket.InputStream.Read(buffer, 0, 1);
myConnection.thisSocket.InputStream.Close();
String dispString = System.Text.ASCIIEncoding.Default.GetString(buffer);
RunOnUiThread(() =>
{
//readTextView.Text = dispString;
System.Console.WriteLine(dispString);
});
}
catch (Java.IO.IOException)
{
RunOnUiThread(() =>
{
readTextView.Text = string.Empty;
});
break;
}
}
}
}
public class BluetoothConnection
{
public void getAdapter() { this.thisAdapter = BluetoothAdapter.DefaultAdapter; }
public void getDevice() { this.thisDevice = (from bd in this.thisAdapter.BondedDevices where bd.Name == "HC-05" select bd).FirstOrDefault(); }
public BluetoothAdapter thisAdapter { get; set; }
public BluetoothDevice thisDevice { get; set; }
public BluetoothSocket thisSocket { get; set; }
}
}
}
`
Thank's for help
I don't understand what this is used for in buttonConnect.Click event
try
{
buttonDisconnect.Enabled = false;
buttonConnect.Enabled = true;
listenThread.Abort();
myConnection.thisDevice.Dispose();
myConnection.thisSocket.OutputStream.WriteByte(187);
myConnection.thisSocket.OutputStream.Close();
myConnection.thisSocket.Close();
myConnection = new BluetoothConnection();
_socket = null;
connected.Text = "Disconnected!";
}
catch { }
and usually receive data like this(Simple usage):
System.Threading.Thread listenThread = new System.Threading.Thread(Listener);
buttonConnect.Click += delegate {
myConnection = new BluetoothConnection();
myConnection.getAdapter();
myConnection.getDevice();
_socket = myConnection.thisDevice.CreateRfcommSocketToServiceRecord(Java.Util.UUID.FromString("00001101-0000-1000-8000-00805f9b34fb"));
myConnection.thisSocket = _socket;
myConnection.thisSocket.Connect();
listenThread.Start();
}
private void Listener()
{
while (true)
{
try
{
byte[] buffer = new byte[1];
TextView readTextView = FindViewById<TextView>(Resource.Id.textView2);
myConnection.thisSocket.InputStream.Read(buffer, 0, 1);
myConnection.thisSocket.InputStream.Close();
String dispString = System.Text.ASCIIEncoding.Default.GetString(buffer);
RunOnUiThread(() =>
{
readTextView.Text = dispString;
System.Console.WriteLine(dispString);
});
}
catch (Java.IO.IOException)
{
TextView readTextView = FindViewById<TextView>(Resource.Id.textView2);
RunOnUiThread(() =>
{
readTextView.Text = string.Empty;
});
break;
}
}
}
}
public class BluetoothConnection
{
public void getAdapter() { this.thisAdapter = BluetoothAdapter.DefaultAdapter; }
public void getDevice() { this.thisDevice = (from bd in this.thisAdapter.BondedDevices where bd.Name == "hc-05" select bd).FirstOrDefault(); }
public BluetoothAdapter thisAdapter { get; set; }
public BluetoothDevice thisDevice { get; set; }
public BluetoothSocket thisSocket { get; set; }
}
I just recently used android:TabbedPage.ToolbarPlacement="Bottom". I used to have the following code:
void TabLayout.IOnTabSelectedListener.OnTabUnselected(TabLayout.Tab tab)
{
var playPage = Element.CurrentPage as NavigationPage;
if (!(playPage.RootPage is PhrasesFrame))
return;
var tabLayout = (TabLayout)ViewGroup.GetChildAt(1);
var playTab = tabLayout.GetTabAt(4);
tab.SetText("Play");
tab.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);
App.pauseCard = true;
}
Anyone knows how can I implement this with ToolbarPlacement="Bottom" ? I have implemented both BottomNavigationView.IOnNavigationItemSelectedListener, BottomNavigationView.IOnNavigationItemReselectedListener but can't find any reference for UnselectedTab if there is any.
Edit:
Previous custom renderer using the default tab position and implementing TabLayout:
namespace Japanese.Droid
{
public class MyTabbedPageRenderer: TabbedPageRenderer, TabLayout.IOnTabSelectedListener
{
ViewPager viewPager;
TabLayout tabLayout;
bool setup;
public MyTabbedPageRenderer(Context context): base(context){ }
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
// More codes here
}
void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
{
UpdateTab(tab);
}
void TabLayout.IOnTabSelectedListener.OnTabSelected(TabLayout.Tab tab)
{
UpdateTab(tab);
}
void TabLayout.IOnTabSelectedListener.OnTabUnselected(TabLayout.Tab tab)
{
var playPage = Element.CurrentPage as NavigationPage;
if (!(playPage.RootPage is PhrasesFrame))
return;
var tabLayout = (TabLayout)ViewGroup.GetChildAt(1);
var playTab = tabLayout.GetTabAt(4);
tab.SetText("Play");
tab.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);
App.pauseCard = true;
}
void UpdateTab(TabLayout.Tab tab)
{
// To have the logic only on he tab on position 1
if (tab == null || tab.Position != 4)
{
return;
}
if (tab.Text == "Play")
{
tab.SetText("Pause");
tab.SetIcon(Resource.Drawable.ionicons_2_0_1_pause_outline_22);
App.pauseCard = false;
}
else
{
tab.SetText("Play");
tab.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);
App.pauseCard = true;
}
}
}
}
Current custom renderer using the ToolbarPlacement="Bottom":
namespace Japanese.Droid
{
public class BottomTabPageRenderer : TabbedPageRenderer, BottomNavigationView.IOnNavigationItemSelectedListener, BottomNavigationView.IOnNavigationItemReselectedListener
{
public BottomTabPageRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
{
base.OnElementChanged(e);
// More codes here
}
bool BottomNavigationView.IOnNavigationItemSelectedListener.OnNavigationItemSelected(IMenuItem item)
{
base.OnNavigationItemSelected(item);
UpdateTab(item)
}
void BottomNavigationView.IOnNavigationItemReselectedListener.OnNavigationItemReselected(IMenuItem item)
{
UpdateTab(item);
}
void UpdateTab(IMenuItem item)
{
var playTabId = 4;
var title = item.TitleFormatted.ToString();
if (item == null || item.ItemId != playTabId)
{
return;
}
if (item.ItemId == playTabId)
{
if (title == "Play")
{
item.SetTitle("Pause");
item.SetIcon(Resource.Drawable.ionicons_2_0_1_pause_outline_22);
App.pauseCard = false;
}
else
{
item.SetTitle("Play");
item.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);
App.pauseCard = true;
}
}
}
}
}
So now my problem is I don't have any idea how will I implement the TabLayout.IOnTabSelectedListener.OnTabUnselected in the new custom renderer.
There is no official stuff for OnTabReselected event for TabbedPage's bottom navigation or
BottomNavigationView because It doesn't use TabLayout.Tab for a start. Many overridden methods of TabbedPageRenderer not being called like SetTabIcon. If you are using IOnTabSelectedListener interface(As your first part of code) you have three methods to use.
void OnTabReselected(Tab tab);
void OnTabSelected(Tab tab);
void OnTabUnselected(Tab tab);
But when it comes to BottomNavigationView interface you have only two methods
void OnNavigationItemReselected
bool OnNavigationItemSelected
So we don't have built in OnTabUnselected method. Here you need to write custom code to make unseleted event.
I have tried this code without using custom renderer using 4 tabs pages & the xaml of tabbed written in MailPage.xaml file. First declare List<string> in App.xaml.cs file to store Title of all tabs
public static List<string> Titles {get;set;}
Add tabs pages title in above list from MainPage.xaml.cs file's OnAppearing method
protected override void OnAppearing()
{
for (int i = 0; i < this.Children.Count; i++)
{
App.Titles.Add(this.Children[i].Title);
}
}
Now go to your MyTabbedPage class in which is available in shared project.
public class MyTabbedPage : Xamarin.Forms.TabbedPage
{
string selectedTab = string.Empty;
string unSelectedTab = string.Empty;
bool isValid;
public MyTabbedPage()
{
On<Xamarin.Forms.PlatformConfiguration.Android>().SetToolbarPlacement(ToolbarPlacement.Bottom);
this.CurrentPageChanged += delegate
{
unSelectedTab = selectedTab;
selectedTab = CurrentPage.Title;
if (App.Titles != null)
isValid = true;
else
App.Titles = new List<string>();
if (isValid)
{
MoveTitles(selectedTab);
//Pass 0 index for tab selected & 1 for tab unselected
var unSelecteTabTitle = App.Titles[1];
//TabEvents(1); here you know which tab unseleted call any method
}
};
}
//This method is for to moving selected title on top of App.Titles list & unseleted tab title automatic shifts at index 1
void MoveTitles(string selected)
{
var holdTitles = App.Titles;
if (holdTitles.Count > 0)
{
int indexSel = holdTitles.FindIndex(x => x.StartsWith(selected));
holdTitles.RemoveAt(indexSel);
holdTitles.Insert(0, selected);
}
App.Titles = holdTitles;
}
}
Or you can make swith case like this
void TabEvents(int index)
{
switch (index)
{
case 0:
//Tab selected
break;
case 1:
//Tab unselected
break;
}
}
Few things I should mention that MainPage.xaml.cs file inheriting MyTabbedPage
public partial class MainPage : MyTabbedPage
Structure of MainPage.xaml file
<?xml version="1.0" encoding="utf-8" ?>
<local:MyTabbedPage
<TabbedPage.Children>
<NavigationPage Title="Browse">
</NavigationPage>
</TabbedPage.Children>
</local:MyTabbedPage>
Answer seems long but hope it help you.
As per G.Hakim's suggestion, I was able to do what I wanted to do by capturing the tab item I wanted to work on and do the necessary actions in BottomNavigationView.IOnNavigationItemSelectedListener.OnNavigationItemSelected.
namespace Japanese.Droid
{
public class BottomTabPageRenderer : TabbedPageRenderer, BottomNavigationView.IOnNavigationItemSelectedListener, BottomNavigationView.IOnNavigationItemReselectedListener
{
// same as above
bool BottomNavigationView.IOnNavigationItemSelectedListener.OnNavigationItemSelected(IMenuItem item)
{
base.OnNavigationItemSelected(item);
if(item.ItemId == 4 && item.TitleFormatted.ToString() == "Play")
{
item.SetTitle("Pause");
item.SetIcon(Resource.Drawable.ionicons_2_0_1_pause_outline_22);
App.pauseCard = false;
playTab = item;
}
if(item.ItemId !=4 && playTab.TitleFormatted.ToString() == "Pause")
{
playTab.SetTitle("Play");
playTab.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);
App.pauseCard = true;
}
return true;
}
// same as above
}
}
If an object is clicked, the next page should not be called immediately. But the click should remain on the object until you scroll through a wipe to the next page.
How can it hold the click command on an Item?
How can it swipe from the clicked Item to an other Page?
Update
Click one item > OnHold> swipe from the holded item to the left and right.
This is the actual behavior:
private int index = -1;
break;
}
return true;
}
}
To highlight the item when it is clicked, you can set background color to the item's view, to perform a swipe gesture for each item, I think you will need to implement IOnTouchListener for each item. Here I created an adapter to implement this feature:
public class LVAdapter : BaseAdapter<ListItemModel>, View.IOnTouchListener
{
private List<ListItemModel> items = new List<ListItemModel>();
private Activity context;
private int index = -1;
public enum SwipeAction
{
LR, // Left to Right
RL, // Right to Left
TB, // Top to bottom
BT, // Bottom to Top
None // when no action was detected
}
private int MIN_DISTANCE = 100;
private float downX, downY, upX, upY;
private SwipeAction maction = SwipeAction.None;
public LVAdapter(Activity context, List<ListItemModel> items) : base()
{
this.context = context;
this.items = items;
}
public override ListItemModel this[int position]
{
get { return items[position]; }
}
public override int Count
{
get { return items.Count; }
}
public override long GetItemId(int position)
{
return position;
}
private void SetSelectedItem(int position)
{
index = position;
NotifyDataSetChanged();
}
private class MyViewHolder : Java.Lang.Object
{
public TextView Name { get; set; }
public TextView Description { get; set; }
public int index { get; set; }
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
MyViewHolder holder = null;
var view = convertView;
if (view != null)
holder = view.Tag as MyViewHolder;
if (holder == null)
{
holder = new MyViewHolder();
view = context.LayoutInflater.Inflate(Resource.Layout.ItemCell, null);
holder.Name = view.FindViewById<TextView>(Resource.Id.nametxt);
holder.Description = view.FindViewById<TextView>(Resource.Id.detailtxt);
holder.index = position;
view.Tag = holder;
}
holder.Name.Text = items[position].Name;
holder.Description.Text = items[position].Description;
if (index != -1 && position == index)
{
holder.Name.SetBackgroundColor(Android.Graphics.Color.Red);
holder.Description.SetBackgroundColor(Android.Graphics.Color.Pink);
}
else
{
holder.Name.SetBackgroundColor(Android.Graphics.Color.RoyalBlue);
holder.Description.SetBackgroundColor(Android.Graphics.Color.SeaGreen);
}
view.SetOnTouchListener(this);
return view;
}
public bool OnTouch(View v, MotionEvent e)
{
switch (e.Action)
{
case MotionEventActions.Down:
downX = e.GetX();
downY = e.GetY();
maction = SwipeAction.None;
break;
case MotionEventActions.Move:
upX = e.GetX();
upY = e.GetY();
var deltaX = downX - upX;
var deltaY = downY - upY;
if (Math.Abs(deltaX) > MIN_DISTANCE)
{
if (deltaX < 0)
{
maction = SwipeAction.LR;
}
else if (deltaX > 0)
{
maction = SwipeAction.RL;
}
return true;
}
else if (Math.Abs(deltaY) > MIN_DISTANCE)
{
if (deltaY < 0)
{
maction = SwipeAction.TB;
}
else if (deltaY > 0)
{
maction = SwipeAction.BT;
}
return false;
}
break;
case MotionEventActions.Up:
var holder = v.Tag as MyViewHolder;
if (maction == SwipeAction.None)
{
SetSelectedItem(holder.index);
}
else if (maction == SwipeAction.LR | maction == SwipeAction.RL)
{
if (holder.index == index)
context.StartActivity(typeof(Activity1));
}
break;
}
return true;
}
}
The ListItemModel is quite simple by my side:
public class ListItemModel
{
public string Name { get; set; }
public string Description { get; set; }
}
You can try to modify the model and holder as you need.
Hi everyone, i had to create custom info-window in xamarin forms as shown in the screenshot above. I have created a custom renderer for that but the problem i am having right now is that buttons are not getting clicked. Xamarin is taking entire info-window as a clickable view. Please guide me what i am doing wrong or is it possible to achieve button clicks in Xamarin forms. Thanks in advance.
Here is the code for custom renderer:
using System;
using System.Collections.Generic;
using Android.Content;
using Android.Gms.Maps;
using Android.Gms.Maps.Model;
using Xamarin.Forms;
using Xamarin.Forms.Maps;
using Xamarin.Forms.Maps.Android;
using SalesApp.Droid.CustomRenderer;
using Android.Widget;
using SalesApp.CustomControls;
using SalesApp.Droid.Listeners;
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace SalesApp.Droid.CustomRenderer
{
public class CustomMapRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter, IOnMapReadyCallback
{
GoogleMap map;
List<CustomPin> customPins;
bool isDrawn;
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
map.InfoWindowClick -= OnInfoWindowClick;
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
customPins = formsMap.CustomPins
Control.GetMapAsync(this);
}
}
public void OnMapReady(GoogleMap googleMap)
{
map = googleMap;
map.InfoWindowClick += OnInfoWindowClick;
map.SetInfoWindowAdapter(this);
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName.Equals("VisibleRegion") && !isDrawn)
{
map.Clear();
if (customPins != null)
{
foreach (var pin in customPins)
{
var marker = new MarkerOptions();
marker.SetPosition(new LatLng(pin.Pin.Position.Latitude, pin.Pin.Position.Longitude));
marker.SetTitle(pin.Pin.Label);
marker.SetSnippet(pin.Pin.Address);
marker.SetIcon(BitmapDescriptorFactory.FromResource((int)typeof(Resource.Drawable).GetField(pin.Image).GetValue(null)));
map.AddMarker(marker);
}
isDrawn = true;
}
}
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
if (changed)
{
isDrawn = false;
}
}
void OnInfoWindowClick(object sender, GoogleMap.InfoWindowClickEventArgs e)
{
var customPin = GetCustomPin(e.Marker);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
//if (!string.IsNullOrWhiteSpace(customPin.Url))
//{
// var url = Android.Net.Uri.Parse(customPin.Url);
// var intent = new Intent(Intent.ActionView, url);
// intent.AddFlags(ActivityFlags.NewTask);
// Android.App.Application.Context.StartActivity(intent);
//}
Android.App.Application.Context.StartActivity(new Intent(Android.App.Application.Context, typeof(DialogActivity)));
}
void IOnMapReadyCallback.OnMapReady(GoogleMap googleMap)
{
InvokeOnMapReadyBaseClassHack(googleMap);
map = googleMap;
map.SetInfoWindowAdapter(this);
map.InfoWindowClick += OnInfoWindowClick;
}
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 == "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 address = view.FindViewById<TextView>(Resource.Id.Address);
var contactPerson = view.FindViewById<TextView>(Resource.Id.ContactPerson);
var phone = view.FindViewById<TextView>(Resource.Id.Phone);
var zip = view.FindViewById<TextView>(Resource.Id.Zip);
var email = view.FindViewById<TextView>(Resource.Id.Email);
var cvr = view.FindViewById<TextView>(Resource.Id.CVR);
var turnover = view.FindViewById<TextView>(Resource.Id.Turnover);
var noOfEmp = view.FindViewById<TextView>(Resource.Id.NoOfEmp);
var leadStatus = view.FindViewById<TextView>(Resource.Id.LeadStatus);
var category = view.FindViewById<TextView>(Resource.Id.Cat);
if (infoTitle != null)
{
infoTitle.Text = customPin.LeedsAndCustomersData.FullName;
}
if (address != null)
{
address.Text = customPin.LeedsAndCustomersData.Address + " " + customPin.LeedsAndCustomersData.CityName;
}
if (contactPerson != null)
{
contactPerson.Text = customPin.LeedsAndCustomersData.FirstName;
}
if (phone != null)
{
phone.Text = customPin.LeedsAndCustomersData.Phone;
}
if (zip != null)
{
zip.Text = customPin.LeedsAndCustomersData.ZipCode;
}
if (email != null)
{
email.Text = customPin.LeedsAndCustomersData.Email;
}
if (cvr != null)
{
cvr.Text = customPin.LeedsAndCustomersData.CVR;
}
if (turnover != null)
{
turnover.Text = customPin.LeedsAndCustomersData.Turnover;
}
if (noOfEmp != null)
{
noOfEmp.Text = customPin.LeedsAndCustomersData.NoOfEmployees.ToString();
}
if (leadStatus != null)
{
leadStatus.Text = customPin.LeedsAndCustomersData.LeadStatus;
}
if (category != null)
{
category.Text = customPin.LeedsAndCustomersData.BusinessType;
}
//add listeners
OnTouchPhoneListener callButtonListener = new OnTouchPhoneListener();
phone.SetOnTouchListener(callButtonListener);
return view;
}
return null;
}
public Android.Views.View GetInfoWindow(Marker marker)
{
return null;
}
CustomPin GetCustomPin(Marker annotation)
{
var position = new Position(annotation.Position.Latitude, annotation.Position.Longitude);
foreach (var pin in customPins)
{
if (pin.Pin.Position == position)
{
return pin;
}
}
return null;
}
void InvokeOnMapReadyBaseClassHack(GoogleMap googleMap)
{
System.Reflection.MethodInfo onMapReadyMethodInfo = null;
Type baseType = typeof(MapRenderer);
foreach (var currentMethod in baseType.GetMethods(System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.DeclaredOnly))
{
if (currentMethod.IsFinal && currentMethod.IsPrivate)
{
if (string.Equals(currentMethod.Name, "OnMapReady", StringComparison.Ordinal))
{
onMapReadyMethodInfo = currentMethod;
break;
}
if (currentMethod.Name.EndsWith(".OnMapReady", StringComparison.Ordinal))
{
onMapReadyMethodInfo = currentMethod;
break;
}
}
}
if (onMapReadyMethodInfo != null)
{
onMapReadyMethodInfo.Invoke(this, new[] { googleMap });
}
}
}
}
OnInfoWindowElemTouchListener:
using Android.OS;
using Android.Views;
using Android.Widget;
using System.Threading.Tasks;
using Android.Gms.Maps.Model;
using Android.Graphics.Drawables;
using Java.Lang;
namespace SalesApp.Droid.Listeners
{
public abstract class OnInfoWindowElemTouchListener : Java.Lang.Object, View.IOnTouchListener
{
private View view;
private Drawable bgDrawableNormal;
private Drawable bgDrawablePressed;
private Handler handler = new Handler();
private Marker marker;
private static bool endPressStatus = false;
private bool pressed = false;
//public OnInfoWindowElemTouchListener(View view, Drawable bgDrawableNormal, Drawable bgDrawablePressed)
//{
// this.view = this.view;
// this.bgDrawableNormal = this.bgDrawableNormal;
// this.bgDrawablePressed = this.bgDrawablePressed;
//}
public OnInfoWindowElemTouchListener(View view)
{
this.view = this.view;
}
public OnInfoWindowElemTouchListener(Button button)
{
}
public OnInfoWindowElemTouchListener()
{
}
public void setMarker(Marker marker)
{
this.marker = this.marker;
}
/*public bool OnTouch(View v, MotionEvent e)
{
if (e.Action == MotionEventActions.Down)
{
// do stuff
return true;
}
if (e.Action == MotionEventActions.Up)
{
// do other stuff
return true;
}
return false;
}*/
public bool OnTouch(View vv, MotionEvent e)
{
if (0 <= e.GetX() && e.GetX() <= vv.Width && 0 <= e.GetY() && e.GetY() <= vv.Height)
{
switch (e.ActionMasked)
{
case MotionEventActions.Down:
startPress();
break;
// We need to delay releasing of the view a little so it shows the
// pressed state on the screen
case MotionEventActions.Up:
//handler.PostDelayed(ConfirmClickRunnable, 150);
//Task.Factory.StartNew(() =>onClickConfirmed(view, marker));
Task.Factory.StartNew(() => onClickConfirmed());
Task.Delay(150);
break;
case MotionEventActions.Cancel:
endPress();
break;
default:
break;
}
}
else
{
// If the touch goes outside of the view's area
// (like when moving finger out of the pressed button)
// just release the press
endPress();
}
return false;
}
private void startPress()
{
if (!pressed)
{
pressed = true;
//handler.RemoveCallbacks(ConfirmClickRunnable);
if ((marker != null))
{
marker.ShowInfoWindow();
}
}
}
public bool endPress()
{
if (pressed)
{
this.pressed = false;
//handler.RemoveCallbacks(ConfirmClickRunnable);
view.SetBackgroundColor(Android.Graphics.Color.Green);
if ((marker != null))
{
marker.ShowInfoWindow();
}
endPressStatus = true;
return true;
}
else
{
endPressStatus = false;
return false;
}
}
//private Runnable confirmClickRunnable = new RunnableAnonymousInnerClassHelper(this);
private Runnable ConfirmClickRunnable = new Java.Lang.Runnable(() =>
{
if (endPressStatus)
{
//onClickConfirmed(view, marker);
}
});
/*private class RunnableAnonymousInnerClassHelper : Java.Lang.Object, Java.Lang.IRunnable
{
private readonly Context outerInstance;
public RunnableAnonymousInnerClassHelper(Context outerInstance)
{
this.outerInstance = outerInstance;
}
public void Run()
{
if (endPressStatus)
{
onClickConfirmed();
}
}
}*/
//public abstract void onClickConfirmed(View v, Marker marker);
public abstract void onClickConfirmed();
}
}
OnTouchPhoneListener:
using Android.Widget;
using System;
namespace SalesApp.Droid.Listeners
{
public class OnTouchPhoneListener : OnInfoWindowElemTouchListener
//public class OnTouchPhoneListener
{
Button button;
public OnTouchPhoneListener(Button button)
{
}
public OnTouchPhoneListener()
{
}
public override void onClickConfirmed() {
Console.WriteLine("call Button Clicked");
}
}
}
Why is the info window implemented in your custom renderer? This makes the class CustomMapRenderer do (at least) do two things, violate the SRP and makes the code way harder to understand.
Instead I'd go with the custom renderer just for the map. Then in Xamarin.Forms you can implement the overlay with Xamarin.Forms means (e.g. the map view in an absolute or relative layout and the overlay in the same layout but a reduced size). Please see the following pseudo-XAML
<ContentPage [...]>
<AbsoluteLayout>
<local:MapView x:Name="MapView" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1" />
<local:MapOverlay AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds=".5, .5, .5, .5" />
</AbsoluteLayout>
</ContentPage>
Then you can introduce a viewmodel PinInfoViewModel which is exposed by MapView.SelectedPinInfo and can be set as MapOverlay.BindingContext
<local:MapOverlay AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds=".5, .5, .5, .5" BindingContext="{Binding Source={x:Reference MapView}, Path=SelectedPinInfo}" />
MapOverlay - in turn - binds to all the properties of PinInfoViewModel. Last thing we'll have to do is making the overlay invisible if no pin is selected. For this purpose we expose MapView.IsPinSelected and bind MapOverlay.IsVisible to it
<local:MapOverlay AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds=".5, .5, .5, .5" BindingContext="{Binding Source={x:Reference MapView}, Path=SelectedPinInfo}" IsVisible="{Binding Source=MapView, Path=IsPinSelected}" />
While up to this point we have not done anything to solve your issue, you can now implement the overlay way simpler, e.g. with a StackLayout. And you can bind the Buttons commands to do whatever you'd like to do.
Please find the code below for the custom renderer for Android for segmented control. I copied this from one post in stack overflow. It works fine for IOS but fails for android. Anyone has any idea? Am i missing anything.
This is the event
protected override void OnElementChanged(ElementChangedEventArgs<SegmentedControl> e)
{
base.OnElementChanged(e);
var layoutInflater = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
var g = new RadioGroup(Context);
g.Orientation = Orientation.Horizontal;
g.CheckedChange += (sender, eventArgs) =>
{
var rg = (RadioGroup)sender;
if (rg.CheckedRadioButtonId != -1)
{
var id = rg.CheckedRadioButtonId;
var radioButton = rg.FindViewById(id);
var radioId = rg.IndexOfChild(radioButton);
var btn = (RadioButton)rg.GetChildAt(radioId);
var selection = (String)btn.Text;
e.NewElement.SelectedValue = selection;
}
};
for (var i = 0; i < e.NewElement.Children.Count; i++)
{
var o = e.NewElement.Children[i];
var v = (SegmentedControlButton)layoutInflater.Inflate(Resource.Layout.SegmentedControl, null);
//Error at this above line
v.Text = o.Text;
if (i == 0)
v.SetBackgroundResource(Resource.Drawable.segmented_control_first_background);
else if (i == e.NewElement.Children.Count - 1)
v.SetBackgroundResource(Resource.Drawable.segmented_control_last_background);
g.AddView(v);
}
SetNativeControl(g);
}
}
Its happening at this line.
var v = (SegmentedControlButton)layoutInflater.Inflate(Resource.Layout.SegmentedControl, null);
v.Text = o.Text;
Error
Android.Views.InflateException: Binary XML file line #1: Binary XML file line #1:
Error inflating class SegmentedControl.Android.SegmentedControlButton ---> Android.Views.InflateException:
Binary XML file line #1: Error inflating class SegmentedControl.Android.SegmentedControlButton ---> Java.Lang.ClassNotFoundException:
Didn't find class "SegmentedControl.Android.SegmentedControlButton" on path: DexPathList[[zip file "/data/app/com.mytestapp.myfirstapp-1/base.apk"],
nativeLibraryDirectories=[/data/app/com.mytestapp.myfirstapp-1/lib/x86, /data/app/com.mytestapp.myfirstapp-1/base.apk!/lib/x86, /vendor/lib, /system/lib]]
Try following SegmentControlRenderer. I have added required files as well.
Use following in your xaml file
<StackLayout BackgroundColor="#0A0E3F" Padding="10" Spacing="0" Grid.Row="0">
<local:CustomSegmentedControl SelectedValue="Offices" x:Name="segmentControl" HorizontalOptions="FillAndExpand">
<local:CustomSegmentedControl.Children>
<local:CustomSegmentedControlOption Text="Control1" />
<local:CustomSegmentedControlOption Text="Control2" />
<local:CustomSegmentedControlOption Text="Control3" />
</local:CustomSegmentedControl.Children>
</local:CustomSegmentedControl>
</StackLayout>
Make sure you place all these 7 files in proper directories. Do let me if anything comes up
SegmentedControlRenderer.cs (to be placed in Droid modules)
using System;
using Xamarin.Forms.Platform.Android;
using Android.Widget;
using Android.Content;
using Android.Util;
using Android.Graphics;
using Android.Views;
using System.Collections.Generic;
[assembly: Xamarin.Forms.ExportRenderer(typeof(App.CustomSegmentedControl), typeof(App.Droid.SegmentedControlRenderer))]
namespace App.Droid
{
public class SegmentedControlRenderer : ViewRenderer<CustomSegmentedControl, RadioGroup>
{
RadioGroup g = null;
List<CustomSegmentedControlButton> listSegmentControl = new List<CustomSegmentedControlButton>();
public SegmentedControlRenderer()
{
}
protected override void OnConfigurationChanged(Android.Content.Res.Configuration newConfig)
{
base.OnConfigurationChanged(newConfig);
if (listSegmentControl == null)
return;
foreach (var control in listSegmentControl)
{
control.SetWidth(Resources.DisplayMetrics.WidthPixels / listSegmentControl.Count);
}
}
protected override void OnElementChanged(ElementChangedEventArgs<TPSegmentedControl> e)
{
base.OnElementChanged(e);
var layoutInflater = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
g = new RadioGroup(Context);
g.Orientation = Orientation.Horizontal;
g.CheckedChange += (sender, eventArgs) =>
{
var rg = (RadioGroup)sender;
if (rg.CheckedRadioButtonId != -1)
{
var id = rg.CheckedRadioButtonId;
var radioButton = rg.FindViewById(id);
var radioId = rg.IndexOfChild(radioButton);
var btn = (RadioButton)rg.GetChildAt(radioId);
for (int i = 0; i < g.ChildCount; i++)
{
g.GetChildAt(i).SetBackgroundResource(Resource.Drawable.segment_control_option_bg);
}
btn.SetBackgroundResource(btn.Checked ? Resource.Drawable.segment_control_selected_option_bg : Resource.Drawable.segment_control_option_bg);
var selection = (String)btn.Text;
e.NewElement.SelectedValue = selection;
}
};
for (var i = 0; i < e.NewElement.Children.Count; i++)
{
var o = e.NewElement.Children[i];
var v = (TPSegmentedControlButton)layoutInflater.Inflate(Resource.Layout.SegmentedControl, null);
v.Text = o.Text;
int minWidth = Resources.DisplayMetrics.WidthPixels / e.NewElement.Children.Count;
v.SetWidth(minWidth);
v.SetBackgroundResource(v.Checked ? Resource.Drawable.segment_control_selected_option_bg : Resource.Drawable.segment_control_option_bg);
g.AddView(v);
listSegmentControl.Add(v);
}
try
{
g.GetChildAt(0).PerformClick();
}
catch (Exception ex)
{
}
SetNativeControl(g);
}
}
public class CustomSegmentedControlButton : RadioButton
{
private int lineHeightSelected;
private int lineHeightUnselected;
private Paint linePaint;
public CustomSegmentedControlButton(Context context) : this(context, null)
{
}
public CustomSegmentedControlButton(Context context, IAttributeSet attributes) : this(context, attributes, Resource.Attribute.segmentedControlOptionStyle)
{
}
public CustomSegmentedControlButton(Context context, IAttributeSet attributes, int defStyle) : base(context, attributes, defStyle)
{
Initialize(attributes, defStyle);
}
private void Initialize(IAttributeSet attributes, int defStyle)
{
var a = this.Context.ObtainStyledAttributes(attributes, Resource.Styleable.SegmentedControlOption, defStyle, Resource.Style.SegmentedControlOption);
var lineColor = Color.ParseColor("#4aa3f4");
linePaint = new Paint();
linePaint.Color = lineColor;
lineHeightUnselected = a.GetDimensionPixelSize(Resource.Styleable.SegmentedControlOption_lineHeightUnselected, 0);
lineHeightSelected = a.GetDimensionPixelSize(Resource.Styleable.SegmentedControlOption_lineHeightSelected, 0);
a.Recycle();
}
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
if (linePaint.Color != 0 && (lineHeightSelected > 0 || lineHeightUnselected > 0))
{
var lineHeight = Checked ? lineHeightSelected : lineHeightUnselected;
if (lineHeight > 0)
{
var rect = new Rect(0, Height - lineHeight, Width, Height);
canvas.DrawRect(rect, linePaint);
}
}
}
}
}
CustomSegmentedControl.cs (To be placed in shared app module)
using System;
using Xamarin.Forms;
using System.Collections.Generic;
namespace App
{
public class CustomSegmentedControl : View, IViewContainer<CustomSegmentedControlOption>
{
public IList<CustomSegmentedControlOption> Children { get; set; }
public TPSegmentedControl()
{
Children = new List<CustomSegmentedControlOption>();
}
public event ValueChangedEventHandler ValueChanged;
public delegate void ValueChangedEventHandler(object sender, EventArgs e);
private string selectedValue;
public string SelectedValue
{
get { return selectedValue; }
set
{
selectedValue = value;
if (ValueChanged != null)
ValueChanged(this, EventArgs.Empty);
}
}
}
public class CustomSegmentedControlOption : View
{
public static readonly BindableProperty TextProperty = BindableProperty.Create<CustomSegmentedControlOption, string>(p => p.Text, "");
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public TPSegmentedControlOption()
{
}
}
}
CustomSegmentedView.cs (To be placed in share app module)
using System;
using XLabs.Forms.Controls;
namespace App
{
public class CustomSegmentedView : SegmentedControlView
{
public ISegmentedControlView Listener { get; set;}
public CustomSegmentedView()
{
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == "SelectedItem") {
if (Listener == null)
return;
Listener.SegmentedControlOnValueChanged(SelectedItem);
}
}
}
public interface ISegmentedControlView {
void SegmentedControlOnValueChanged(int selectedIndex);
}
}
SegmentedControl.axml (To be placed in Droid/Resources/layout)
<?xml version="1.0" encoding="utf-8"?>
<App.Droid.CustomSegmentedControlButton
style="#style/SegmentedControlOption" />
attrs.xml (To be placed in Droid/Resources/values)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SegmentedControlOption">
<attr name="segmentedControlOptionStyle" format="string" />
<attr name="lineColor" format="color" />
<attr name="lineHeightUnselected" format="dimension" />
<attr name="lineHeightSelected" format="dimension" />
</declare-styleable>
<declare-styleable name="ScaleImageView">
</declare-styleable>
</resources>
segment_control_option_bg.xml (To be placed in Droid/Resources/layout)
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#4aa3f4" />
<stroke
android:width="0.5dp"
android:color="#0a0e3f" />
<corners
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"
android:topLeftRadius="0dp"
android:topRightRadius="0dp" />
</shape>
segment_control_selected_option_bg.xml (To be placed in Droid/Resources/layout)
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#0271d5" />
<stroke
android:width="0.5dp"
android:color="#0a0e3f" />
<corners
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"
android:topLeftRadius="0dp"
android:topRightRadius="0dp" />
</shape>
The problem was in the segmented control.axml i gave the wrong namespace.
<?xml version="1.0" encoding="utf-8"?>
<*MyFirstApp*.Droid.SegmentedControlButton
style="#style/SegmentedControlOption" />
It solved everything. #Needle in the haystack#
#user12345 i already have the correct code in post whatever you mentioned. Thank you for your neat explaination.