Unable to capture audio from webview - xamarin

I have a Xamarin Forms app that needs to capture audio from a webview. I have the following set in my manifest
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.AUDIO_CAPTURE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
<uses-permission android:name="android.permission.MICROPHONE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-feature android:name="android.hardware.audio.low_latency" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-feature android:name="android.hardware.audio.pro" />
<uses-permission android:name="android.webkit.resource.AUDIO_CAPTURE" />
I have the following in the android webview custom renderer
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
Control.Download += OnDownload;
Control.Settings.JavaScriptEnabled = true;
Control.Settings.SetGeolocationEnabled(true);
Control.Settings.SetGeolocationDatabasePath(Control.Context.FilesDir.Path);
Control.Settings.SetSupportZoom(false);
Control.Settings.AllowFileAccess = true;
Control.Settings.AllowContentAccess =true;
Control.Settings.LoadWithOverviewMode = true;
Control.Settings.SetPluginState(Android.Webkit.WebSettings.PluginState.On);
if (e.OldElement == null)
{
ResetCookieManagerSettings();
var chromeClient = new ArenaWebChromeClient((uploadMsg, acceptType, capture) =>
{
var webViewClient = new WebViewClient(this);
if (App.Self.IsConnected)
{
Control.SetWebChromeClient(chromeClient);
webViewClient.PageLoadStarted += RaiseLoadStarted;
webViewClient.PageLoadFinished += RaiseLoadFinished;
Control.SetWebViewClient(webViewClient);
}
else
{
webViewClient.PageLoadStarted -= RaiseLoadStarted;
webViewClient.PageLoadFinished -= RaiseLoadFinished;
}
}
}
This is all fine. My Chrome code for checking the permissions is this
public override void OnPermissionRequest(PermissionRequest request)
{
base.OnPermissionRequest(request);
if (request != null)
{
permissionRequest = request;
var res = request.GetResources();
if (res.Length != 0)
{
foreach(var r in res)
{
if (r.Contains("AUDIO_CAPTURE"))
{
Device.BeginInvokeOnMainThread(async () =>
{
var perms = await Permissions.CheckStatusAsync<Permissions.Media>();
if (perms != Xamarin.Essentials.PermissionStatus.Granted)
{
var request = await Permissions.RequestAsync<Permissions.Media>();
if (request == Xamarin.Essentials.PermissionStatus.Granted)
{
var mod = await Permissions.CheckStatusAsync<Permissions.Microphone>();
if (mod != Xamarin.Essentials.PermissionStatus.Granted)
{
var t = await Xamarin.Essentials.PermissionRequestAsync<Permissions.Media>();
#if DEBUG
Console.WriteLine("Microphone set");
#endif
}
}
}
else
{
var mod = await Permissions.CheckStatusAsync<Permissions.Microphone>();
if (mod != Xamarin.Essentials.PermissionStatus.Granted)
{
var t = await Xamarin.Essentials.Permissions.RequestAsync<Permissions.Media>();
#if DEBUG
Console.WriteLine("Microphone set");
#endif
}
}
});
}
}
}
}
}
This all works. However. when I try to record via the webview, I'm getting the following error.
chromium] [INFO:CONSOLE(76)] "The following error occurred: NotAllowedError: Permission denied", source: https://mywebsite/myurl (76)
I'm not sure if it's on the server side or app side as when the user on the website tries to record, a recording is made.

Related

Files Chooser for Xamarin Forms Not adding the selected image taken from camera and file

I'm creating a mobile application using Xamarin forms, As part of my implementation, I have a requirement to to show file chooser on clicking on camera icon which is loaded in webView and load the image to webView. currently file chooser is being shown, but image is not getting added.
public override bool OnShowFileChooser(Android.Webkit.WebView webView, Android.Webkit.IValueCallback filePathCallback, FileChooserParams fileChooserParams) {
Intent takePictureIntent = new Intent(MediaStore.ActionImageCapture);
if (takePictureIntent.ResolveActivity(mContext.PackageManager) != null) {
Java.IO.File photoFile = null;
try {
string folder = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath;
photoFile = new Java.IO.File(folder, "image" + DateTime.Now.Millisecond + ".png");
takePictureIntent.PutExtra("PhotoPath", mCameraPhotoPath);
//photoFile = createImageFile();
//takePictureIntent.PutExtra("PhotoPath", mCameraPhotoPath);
} catch (Exception e) {
Console.WriteLine("catch the Exception" + e);
}
if (photoFile != null) {
mCameraPhotoPath = "file:" + photoFile.AbsolutePath;
//pictureUri = FileProvider.GetUriForFile(mContext, "asdasd", photoFile);
takePictureIntent.PutExtra(Android.Provider.MediaStore.ExtraOutput, photoFile);
} else {
takePictureIntent = null;
}
}
Intent contentSelectionIntent = new Intent(Intent.ActionGetContent);
contentSelectionIntent.AddCategory(Intent.CategoryOpenable);
contentSelectionIntent.SetType(file_type);
Intent[] intentArray;
if (takePictureIntent != null) {
intentArray = new Intent[] {
takePictureIntent
};
} else {
intentArray = new Intent[0];
}
Intent chooserIntent = new Intent(Intent.ActionChooser);
chooserIntent.PutExtra(Intent.ExtraIntent, contentSelectionIntent);
chooserIntent.PutExtra(Intent.ExtraTitle, "File chooser");
chooserIntent.PutExtra(Intent.ExtraInitialIntents, intentArray);
//mContext.StartActivity(chooserIntent);
mContext.StartActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);
return true;
}
private Java.IO.File createImageFile() {
// Create an image file name
string timeStamp = Android.OS.SystemClock.CurrentThreadTimeMillis().ToString();
string imageFileName = "JPEG_" + timeStamp + "_";
Java.IO.File storageDir;
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Gingerbread) {
storageDir = mContext.CacheDir;
} else {
storageDir = mContext.GetExternalFilesDir(Android.OS.Environment.DirectoryPictures);
}
//new Java.IO.File(mContext.GetExternalFilesDir(null).AbsolutePath);
Java.IO.File imageFile = Java.IO.File.CreateTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
return imageFile;
}
At first, please add the following code into the AndroidManifest.xml to declare the permissions:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.Read_EXTERNAL_STORAGE" />
And then request the permission in the MainActivity's construction method.
var read = await Permissions.RequestAsync<Permissions.StorageRead>();
var write = await Permissions.RequestAsync<Permissions.StorageWrite>();
Finally, please override the OnActivityResult method of the Mainactivity:
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if (data != null)
{
if (requestCode == INPUT_FILE_REQUEST_CODE)// the value of the INPUT_FILE_REQUEST_CODE
{
if (null == this.message)
{
return;
}
this.message.OnReceiveValue(WebChromeClient.FileChooserParams.ParseResult((int)resultCode, data));
this.message = null;
}
}
}

How can I add a location button to xamarin forms maps

I implemented XF Maps, and added permissions from XF.Essentials
On ANdroid is verything fine, but on iOS I cant see location button, after I clicked approve for permissions?
What else I need to add in order to see button for location (user geolocation)?
...
private async void GetPermissions()
{
var status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();
if (status != PermissionStatus.Granted)
{
status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
}
if (status != PermissionStatus.Granted)
{
await Shell.Current.DisplayAlert("Permission Denied", "We Need to access your Location. But it is not granted", "OK");
}
}
...
my iOS renderer
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace OperaMobile.iOS.Renderers
{
public class CustomMapRenderer : MapRenderer
{
UIStackView customPinView;
ObservableCollection<CustomPin> customPins;
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
var nativeMap = Control as MKMapView;
nativeMap.GetViewForAnnotation = null;
nativeMap.CalloutAccessoryControlTapped -= OnCalloutAccessoryControlTapped;
nativeMap.DidSelectAnnotationView -= OnDidSelectAnnotationView;
nativeMap.DidDeselectAnnotationView -= OnDidDeselectAnnotationView;
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
var nativeMap = Control as MKMapView;
customPins = formsMap.CustomPins;
nativeMap.GetViewForAnnotation = GetViewForAnnotation;
nativeMap.CalloutAccessoryControlTapped += OnCalloutAccessoryControlTapped;
nativeMap.DidSelectAnnotationView += OnDidSelectAnnotationView;
nativeMap.DidDeselectAnnotationView += OnDidDeselectAnnotationView;
}
}
protected override MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation)
{
MKAnnotationView annotationView = null;
if (annotation is MKUserLocation)
return null;
var customPin = GetCustomPin(annotation as MKPointAnnotation);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
annotationView = mapView.DequeueReusableAnnotation(customPin.Label);
if (annotationView == null)
{
annotationView = new CustomMKAnnotationView(annotation, customPin.Label);
annotationView.Image = UIImage.FromFile("pin.png");
annotationView.CalloutOffset = new CGPoint(0, 0);
UIImageView uIImageView = new UIImageView(UIImage.FromFile("monkey.png"));
uIImageView.Frame = new CGRect(0, 0, 75, 100);
annotationView.LeftCalloutAccessoryView = uIImageView;
//annotationView.LeftCalloutAccessoryView = new UIImageView(UIImage.FromFile("monkey.png"));
//annotationView.RightCalloutAccessoryView = UIButton.FromType(UIButtonType.DetailDisclosure);
((CustomMKAnnotationView)annotationView).Name = customPin.Label;
//((CustomMKAnnotationView)annotationView).Url = customPin.Url;
customPinView = new UIStackView();
foreach (var item in customPin.InfoBox.DetailsObjectInfos)
{
var label = new UILabel();
label.Text = item.BoldLabelTitle + item.LabelValue;
label.BackgroundColor = UIColor.White;
label.Font.WithSize(36);
customPinView.AddArrangedSubview(label);
}
customPinView.Frame = new CGRect(0, 0, 300, 84);
customPinView.Axis = UILayoutConstraintAxis.Vertical;
customPinView.Distribution = UIStackViewDistribution.EqualSpacing;
customPinView.Spacing = 1;
customPinView.Alignment = UIStackViewAlignment.Fill;
annotationView.DetailCalloutAccessoryView = customPinView;
UITapGestureRecognizer tapGestureRecognizer = new
UITapGestureRecognizer((gesture) =>
{
Shell.Current.GoToAsync(Routes.ObjectParametersPage);
});
annotationView.DetailCalloutAccessoryView.AddGestureRecognizer(tapGestureRecognizer);
}
annotationView.CanShowCallout = true;
return annotationView;
}
protected virtual void OnCalloutAccessoryControlTapped(object sender, MKMapViewAccessoryTappedEventArgs e)
{
Shell.Current.GoToAsync(Routes.ObjectParametersPage);
//(App.Current as App).NavigationPage.Navigation.PushAsync(new ContentPage());
}
void OnDidSelectAnnotationView(object sender, MKAnnotationViewEventArgs e)
{
CustomMKAnnotationView customView = e.View as CustomMKAnnotationView;
}
void OnDidDeselectAnnotationView(object sender, MKAnnotationViewEventArgs e)
{
if (!e.View.Selected)
{
customPinView.RemoveFromSuperview();
customPinView.Dispose();
customPinView = null;
}
}
CustomPin GetCustomPin(MKPointAnnotation annotation)
{
var position = new Position(annotation.Coordinate.Latitude, annotation.Coordinate.Longitude);
foreach (var pin in customPins)
{
if (pin.Position == position)
{
return pin;
}
}
return null;
}
}}
Not sure how to add the location button in your project, maybe you can add a customed ImageButton with location icon in Xaml .
Xaml code:
<RelativeLayout>
<local:CustomMap x:Name="customMap"
IsShowingUser="True"
MapType="Street"
IsVisible="true"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent,
Property=Width,
Factor=0,
Constant=0}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent,
Property=Height,
Factor=0,
Constant=0}"
RelativeLayout.WidthConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=1,Constant=0}"
RelativeLayout.HeightConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=1,Constant=0}" />
<ImageButton Source="location.png"
Clicked="Button_Clicked"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent,
Property=Width,
Factor=0.6,
Constant=100}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent,
Property=Height,
Factor=0.8,
Constant=80}" />
</RelativeLayout>
The contentpage.cs :
private async void Button_Clicked(object sender, System.EventArgs e)
{
var request = new GeolocationRequest(GeolocationAccuracy.Medium);
var location = await Geolocation.GetLocationAsync(request);
if (location != null)
{
Console.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude: {location.Altitude}");
}
customMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(location.Latitude, -location.Longitude), Distance.FromMiles(1.0)));
}
The effect:
==================================Update #1=================================
In xamarin forms , you can hide the xaml location button only in Android , then iOS will show the xaml location button.
protected override void OnAppearing()
{
base.OnAppearing();
if(Device.RuntimePlatform == "Android")
{
LocationButton.IsVisible = false;
}
}
The LocationButton is defined in Xaml : <ImageButton x:Name="LocationButton" ... />
==================================Update #2=================================
If need to test location in ios simulator, you need to choose the location manually. Clicking settings of simulatoras follow:
Choose a location:

How to access microphone in Xamarin emulator

Is it possible or is there anyway that I can access the Microphone and use it to test my code?
I have enabled all of this in the extended controls of my emulator for microphone:
I also have these permissions in my AndroidManifest.xml:
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
This is the code:
using System;
using System.ComponentModel;
using Plugin.AudioRecorder;
using Xamarin.Forms;
namespace AudioDemo
{
[DesignTimeVisible(true)]
public partial class MainPage : ContentPage
{
AudioRecorderService recorder;
AudioPlayer player;
public MainPage()
{
InitializeComponent();
recorder = new AudioRecorderService
{
StopRecordingAfterTimeout = true,
TotalAudioTimeout = TimeSpan.FromSeconds(15),
AudioSilenceTimeout = TimeSpan.FromSeconds(2)
};
player = new AudioPlayer();
player.FinishedPlaying += Finaliza_Reproducao;
}
async void Record_Clicked(object sender, EventArgs e)
{
try
{
if (!recorder.IsRecording)
{
recorder.StopRecordingOnSilence = TimeoutSwitch.IsToggled;
RecordButton.IsEnabled = false;
PlayButton.IsEnabled = false;
//Start recording
var audioRecordTask = await recorder.StartRecording();
RecordButton.Text = "Parar Gravação";
RecordButton.IsEnabled = true;
await audioRecordTask;
RecordButton.Text = "Record";
PlayButton.IsEnabled = true;
}
else
{
RecordButton.IsEnabled = false;
//stop recording ...
await recorder.StopRecording();
RecordButton.IsEnabled = true;
}
}
catch (Exception ex)
{
//blow up the app!
await DisplayAlert("Erro", ex.Message, "OK");
}
}
async void Play_Clicked(object sender, EventArgs e)
{
try
{
var filePath = recorder.GetAudioFilePath();
if (filePath != null)
{
PlayButton.IsEnabled = false;
RecordButton.IsEnabled = false;
player.Play(filePath);
}
}
catch (Exception ex)
{
//blow up the app!
await DisplayAlert("Error", ex.Message, "OK");
}
}
void Finaliza_Reproducao(object sender, EventArgs e)
{
PlayButton.IsEnabled = true;
RecordButton.IsEnabled = true;
}
}
}
This code works when I use a phone to run it, but when I just use (my laptop) emulator, it does not work.
Not able to record sound in the emulator because the android emulator doesn’t support it yet. This code should only work on the phone.
Note: The Android Emulator cannot record audio. Be sure to test your
code on a real device that can record.
This is the official document
https://developer.android.com/guide/topics/media/mediarecorder?hl=en

Plugin.Media Not Working on Prism Xamarin Forms (TakePhotoAsync)

I know this question is not new, but I am really stuck on this for days, and I need your support.
My problem is:
I can't take pictures in my prism xamarin forms project using the plugin media for Android (it works for selecting images from gallery though).
The Error I get is:
unable to get file location xam plugin media site
I would love to hear from you guys, and thanks in advance for your support.
Here i my code:
Android Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.niternational.Hope" android:installLocation="auto">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<application android:label="Hope.Android" android:icon="#mipmap/ic_launcher">
<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.niternational.Hope.fileprovider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="#xml/file_paths"></meta-data>
</provider>
</application>
</manifest>
file_paths file on xml folder on Resources
<?xml version="1.0" encoding="utf-8" ?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="Pictures" />
<external-files-path name="my_movies" path="Movies" />
</paths>
Main Activity
[Activity(Label = "Hope", Icon = "#mipmap/ic_launcher", Theme = "#style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
CrossCurrentActivity.Current.Init(this, bundle);
LoadApplication(new App(new AndroidInitializer()));
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Android.Content.PM.Permission[] grantResults)
{
Plugin.Permissions.PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
public class AndroidInitializer : IPlatformInitializer
{
public void RegisterTypes(IContainerRegistry container)
{
// Register any platform specific implementations
}
}
AssemblyInfo file
I have added:
[assembly: UsesFeature("android.hardware.camera", Required = false)]
[assembly: UsesFeature("android.hardware.camera.autofocus", Required = false)]
Code Behind
private async void TakeImage()
{
var actions = await Application.Current.MainPage.DisplayActionSheet("ActionSheet: Image?", "Cancel", null, "Take Picture", "From Gallery");
switch (actions)
{
case "Take Picture":
await CrossMedia.Current.Initialize();
if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsPickPhotoSupported)
{
var message = "No Camera available";
//DependencyService.Get<IMessage>().ShortAlert(message);
return;
}
var mediaOptions = new StoreCameraMediaOptions
{
Directory = "UploaDoc",
Name = $"{DateTime.UtcNow}.jpg",
SaveToAlbum = true,
CompressionQuality = 75,
CustomPhotoSize = 50,
PhotoSize = PhotoSize.MaxWidthHeight,
MaxWidthHeight = 2000,
DefaultCamera = CameraDevice.Front
};
// Take a photo of the business receipt.
var file = await CrossMedia.Current.TakePhotoAsync(mediaOptions);
if (file == null)
return;
image.Source = ImageSource.FromStream(() =>
{
var stream = file.GetStream();
return stream;
});
var memoryStream = new MemoryStream();
file.GetStream().CopyTo(memoryStream);
file.Dispose();
imgAsBytes = memoryStream.ToArray();
memoryStream.Dispose();
break;
case "From Gallery":
if (!CrossMedia.Current.IsPickPhotoSupported)
{
var message = "Picking image is not supported";
//DependencyService.Get<IMessage>().ShortAlert(message);
return;
}
var files = await CrossMedia.Current.PickPhotoAsync();
if (files == null)
return;
image.Source = ImageSource.FromStream(() =>
{
var stream = files.GetStream();
return stream;
});
var ms = new MemoryStream();
files.GetStream().CopyTo(ms);
files.Dispose();
imgAsBytes = ms.ToArray();
ms.Dispose();
break;
}

segment control custom renderer for xamarin forms pcl android is not working

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.

Resources