Loading an image URL from Xamarin Forms PCL to PageRenderer in Android project fails - xamarin

I am passing an URL string of an image from a website, these images appear in a listview on the PCL project, the URL of the image passes fine to the Android Custom Renderer, but loading the image with MonoDroidToolKit fails with the following error:
Unhandled Exception:
System.ArgumentNullException: Value cannot be null.
Parameter name: key
From PCL(passing attachment information):
public partial class ImageViewPage : ContentPage
{
public ImageViewPage(Attachment imageItem)
{
if (imageItem == null)
throw new ArgumentNullException();
BindingContext = imageItem;
_imageItem = imageItem.url;
InitializeComponent();
}
public string _imageItem { get; private set; }
}
In Android Custom Renderer:
[assembly: ExportRenderer(typeof(ImageViewPage),typeof(ImageViewRenderer))]
namespace BibleCodesApp.Droid
{
public class ImageViewRenderer : PageRenderer
{
global::Android.Views.View view;
ImageLoader imageLoader;
Activity activity;
ImageView imageView;
private string imageItem
{
get
{
var imageViewPage = Element as ImageViewPage;
return imageViewPage == null
? null
: imageViewPage._imageItem;
}
}
protected override void OnElementChanged(ElementChangedEventArgs<Page>e)
{
base.OnElementChanged(e);
activity = this.Context as Activity;
view = activity.LayoutInflater.Inflate
(Resource.Layout.ImageView,this,false);
imageLoader = new ImageLoader(activity, 512);
imageView = FindViewById<ImageView>(Resource.Id.image_view);
imageLoader.DisplayImage(imageItem, imageView, -1);
}
}
}
The layout in Android:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5dp">
<BibleCodesApp.Droid.ScaleImageView
android:id="#+id/image_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="center"
android:adjustViewBounds="true" />
</LinearLayout>
</RelativeLayout>

I see it now:
You are inflating your new view, but never actually adding it to the page!
You should call
this.ViewGroup.AddView(view);
after your imageLoader.DisplayImage call at the end of OnElementChanged

This is the correct solution:
[assembly: ExportRenderer(typeof(ZoomImage), typeof(ZoomImageRenderer))]
namespace WPAppTemplate.Droid
{
public class ZoomImageRenderer : ImageRenderer
{
private ZoomImage _zoomImage;
private ScaleImageView _scaleImage;
protected async override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
_zoomImage = (ZoomImage)e.NewElement;
// create the scale image and set it as the native control so it's available
_scaleImage = new ScaleImageView(Context, null);
_scaleImage.ZoomImage = _zoomImage;
SetNativeControl(_scaleImage);
await LoadImage();
}
}
protected async override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == ZoomImage.AspectProperty.PropertyName
|| e.PropertyName == ZoomImage.HeightProperty.PropertyName
|| e.PropertyName == ZoomImage.WidthProperty.PropertyName)
{
_scaleImage.ZoomToAspect();
}
else if (e.PropertyName == ZoomImage.SourceProperty.PropertyName)
{
await LoadImage();
_scaleImage.ZoomToAspect();
}
else if (e.PropertyName == ZoomImage.CurrentZoomProperty.PropertyName)
{
_scaleImage.ZoomFromCurrentZoom();
}
else if (e.PropertyName == ZoomImage.MaxZoomProperty.PropertyName)
{
_scaleImage.UpdateMaxScaleFromZoomImage();
}
else if (e.PropertyName == ZoomImage.MinZoomProperty.PropertyName)
{
_scaleImage.UpdateMinScaleFromZoomImage();
}
}
private async Task LoadImage()
{
var image = await (new ImageLoaderSourceHandler()).LoadImageAsync(_zoomImage.Source, Context);
try
{
if (image != null && image.ByteCount > 0)
_scaleImage.SetImageBitmap(image);
}
catch (Exception e)
{
// catch an image loading failure
Console.WriteLine($"Unable to load bitmap. Exception: {e.Message}");
}
}
}

Related

Unable to get the trigger button event call back from Web view for Xamarin using hybridWebView

I have my mobile application build using Xamarin forms, which consist of a webView loaded into my application. The WebView Consist of buttons and icons being loaded. I have a requirement to trigger the button event Listener , so that I can do the corresponding functionality. Can anyone help me in how to get the button events being called in Xamarin forms. I have used HybridWebView as mentioned in the documents and sample ,but nothing seems to be working for me. Below is the code I have used for the same.
code:
HybridWebViewRenderer
using System;
using Android.Content;
using TestProject.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(Vinspector.HybridWebView), typeof(HybridWebViewRenderer))]
namespace TestProject.Droid
{
public class HybridWebViewRenderer : WebViewRenderer
{
const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);};";
Context _context;
public HybridWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
Console.WriteLine("print inside control is null!!!");
var webView = new Android.Webkit.WebView(_context);
webView.Settings.JavaScriptEnabled = true;
webView.Settings.DatabaseEnabled = true;
webView.Settings.DomStorageEnabled = true;
SetNativeControl(webView);
}
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if (e.NewElement != null)
{
Control.Settings.JavaScriptEnabled = true;
Control.Settings.DatabaseEnabled = true;
Control.Settings.DomStorageEnabled = true;
Control.SetWebViewClient(new JavascriptWebViewClient("javascript:(function getCloseCa(){var btns = document.getElementById('dvCloseBtn');})()"));
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl(“www.google.com”);
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
((HybridWebView)Element).Cleanup();
}
base.Dispose(disposing);
}
}
}
Code For JavascriptWebViewClient
namespace TestProject.Droid
{
public class JavascriptWebViewClient : WebViewClient
{
string _javascript;
public JavascriptWebViewClient(string javascript)
{
_javascript = javascript;
}
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
base.OnPageFinished(view, url);
if (Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat)
{
view.EvaluateJavascript(_javascript, null);
}
else {
view.LoadUrl(_javascript);
}
}
}
}
Code For JsBridge
namespace TestProject.Droid
{
public class JSBridge : Java.Lang.Object
{
readonly WeakReference<HybridWebViewRenderer> hybridWebViewRenderer;
public JSBridge(HybridWebViewRenderer hybridRenderer)
{
hybridWebViewRenderer = new WeakReference<HybridWebViewRenderer>(hybridRenderer);
}
[JavascriptInterface]
[Export("invokeAction")]
public void InvokeAction(string data)
{
HybridWebViewRenderer hybridRenderer;
if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
{
((HybridWebView)hybridRenderer.Element).InvokeAction(data);
}
}
}
}
WebPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:TestProject."
x:Class="TestProject..WebPage">
<StackLayout>
<ProgressBar Progress="0.2"
HorizontalOptions="FillAndExpand"
x:Name="progress"
IsVisible="True"/>
<local:HybridWebView
x:Name="hybridWebView"
HeightRequest="1000"
WidthRequest="1000"
Navigating="OnNavigating"
Navigated="OnNavigated"
VerticalOptions="FillAndExpand"
>
</local:HybridWebView>
</StackLayout>
</ContentPage>
WebPage.Xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Vinspector.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Newtonsoft.Json;
using TestProject.Models;
namespace TestProject
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class WebPage : ContentPage
{
public WebPage()
{
InitializeComponent();
hybridWebView.RegisterAction(data => Console.WriteLine("value of data is" + data));
}
protected async override void OnAppearing()
{
base.OnAppearing();
await progress.ProgressTo(0.9, 900, Easing.SpringIn);
}
protected void OnNavigating(object sender, WebNavigatingEventArgs e)
{
progress.IsVisible = true;
Console.WriteLine(" Result OnNavigating....");
}
protected void OnNavigated(object sender, WebNavigatedEventArgs e)
{
progress.IsVisible = false;
Console.WriteLine(" Result OnNavigated....");
var retorno = hybridWebView.EvaluateJavaScriptAsync("common.GetCurrentUserID();");
covertAsync(retorno);
}
private async Task covertAsync(Task<string> retorno)
{
string userID = await retorno;
string AccessToken = LoadApplicationProperty<string>("access_token");
Console.WriteLine(" Result UserID...." + userID + " " + AccessToken);
}
private T LoadApplicationProperty<T>(string key)
{
return (T)Xamarin.Forms.Application.Current.Properties[key];
}
}
}
You can use the javascript function like the below code to access the buttons and events clicked from webview:
using System;
using Android.Content;
using TestProject.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(Vinspector.HybridWebView), typeof(HybridWebViewRenderer))]
namespace TestProject.Droid
{
public class HybridWebViewRenderer : WebViewRenderer
{
const string JavascriptFunction= "document.getElementById('btnId').onclick= function(data){jsBridge.submissionAction(data);}";`
Context _context;
public HybridWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
Console.WriteLine("print inside control is null!!!");
var webView = new Android.Webkit.WebView(_context);
webView.Settings.JavaScriptEnabled = true;
webView.Settings.DatabaseEnabled = true;
webView.Settings.DomStorageEnabled = true;
SetNativeControl(webView);
}
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if (e.NewElement != null)
{
Control.Settings.JavaScriptEnabled = true;
Control.Settings.DatabaseEnabled = true;
Control.Settings.DomStorageEnabled = true;
webView.SetWebViewClient(new `JavascriptWebViewClient(this,string.Format("javascript: {0}", JavascriptFunction)));`
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl(“www.google.com”);
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
((HybridWebView)Element).Cleanup();
}
base.Dispose(disposing);
}
}
}

Customizing the custom picker in Xamarin

I hope everyone is doing great. Can someone help me on how can I remove the 'cancel' and 'Ok' buttons and allow/raise an event selection if I release the mouse on the item on the screen, Secondly I need help with changing the color of the borderlines and background color? I will appreciate your help thanks.
Picker :
public class BorderlessPickerRenderer : PickerRenderer
{
public static void Init() { }
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
Control.Background = Android.Graphics.Color.Rgb();
string fontFamily = e.NewElement?.FontFamily;
var layoutParams = new MarginLayoutParams(Control.LayoutParameters);
layoutParams.SetMargins(0, 0, 0, 0);
LayoutParameters = layoutParams;
Control.LayoutParameters = layoutParams;
Control.SetPadding(0, 0, 0, 0);
SetPadding(0, 0, 0, 0);
}
}
}
You could use Custon Remderer to define a custom Dialog.
how can I remove the 'cancel' and 'Ok' buttons and allow/raise an event selection if I release the mouse on the item on the screen,
The AlertDialog provides SetNegativeButton event to set the cancel and Ok button. Remove both in the view would be okay. The custom view is a listview. You could use the ItemClick of ListView.
Secondly I need help with changing the color of the borderlines and background color?
You could set the background color of the dialog to change. Add the android:divider to set the borderline color. And add the android:dividerHeight to set the borderline height.
Custom Renderer:
[assembly: ExportRenderer(typeof(Picker), typeof(BorderlessPickerRenderer))]
namespace App15.Droid
{
public class BorderlessPickerRenderer : PickerRenderer
{
AlertDialog listDialog;
string[] items;
public BorderlessPickerRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.Click += Control_Click1; ;
}
}
private void Control_Click1(object sender, EventArgs e)
{
Picker model = Element;
items = model.Items.ToArray();
AlertDialog.Builder builder = new AlertDialog.Builder(this.Context);
builder.SetTitle(model.Title ?? "");
//builder.SetNegativeButton("Cancel", (s, a) =>
//{
// Control?.ClearFocus();
// builder = null;
//});
Android.Views.View view = LayoutInflater.From(this.Context).Inflate(Resource.Layout.listview, null);
Android.Widget.ListView listView = view.FindViewById<Android.Widget.ListView>(Resource.Id.listView1);
MyAdapter myAdapter = new MyAdapter(items, Element.SelectedIndex);
listView.Adapter = myAdapter;
listView.ItemClick += ListView_ItemClick;
builder.SetView(view);
listDialog = builder.Create();
listDialog.Window.DecorView.SetBackgroundColor(Android.Graphics.Color.Pink); // set the dialog background color
listDialog.Show();
//Android.Widget.Button button = listDialog.GetButton((int)DialogButtonType.Negative);
//button.Text = "Cancel";
//button.Click += Button_Click;
//button.SetTextColor(Android.Graphics.Color.Blue); // set the button bottom color
}
//private void Button_Click(object sender, EventArgs e)
//{
// listDialog.Dismiss();
// listDialog = null;
//}
private void ListView_ItemClick(object sender, AdapterView.ItemClickEventArgs e)
{
Control.Text = items[e.Position];
Element.SelectedIndex = e.Position;
Console.WriteLine(items[e.Position]);
listDialog.Dismiss();
listDialog = null;
}
class MyAdapter : BaseAdapter
{
private string[] items;
private int selectedIndex;
public MyAdapter(string[] items)
{
this.items = items;
}
public MyAdapter(string[] items, int selectedIndex) : this(items)
{
this.selectedIndex = selectedIndex;
}
public override int Count => items.Length;
public override Java.Lang.Object GetItem(int position)
{
return items[position];
}
public override long GetItemId(int position)
{
return position;
}
public override Android.Views.View GetView(int position, Android.Views.View convertView, ViewGroup parent)
{
if (convertView == null)
{
convertView = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.listview_item, null);
}
TextView textView = convertView.FindViewById<TextView>(Resource.Id.textView1);
textView.Text = items[position];
return convertView;
}
}
}
}
Xaml:
<Picker x:Name="picker" Title="Title" VerticalOptions="CenterAndExpand" >
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Option 1</x:String>
<x:String>Option 2</x:String>
<x:String>Option 3</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
listview.xml: You could creat in your Resources/layout in android project.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="#+id/listView1"
android:divider="#android:color/holo_green_light"
android:dividerHeight="2dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
listview_item.xml : You could creat in your Resources/layout in android project.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

PageRenderer does not work with camera2 API

I am trying to get Camera2 API to work from Xamarin Forms through Page Rendering.
[assembly: ExportRenderer(typeof(CameraPage), typeof(CameraActivity))]
namespace BlueDemo.Droid
{
[Activity(Label = "CameraActivity")]
public class CameraActivity : PageRenderer
{
Activity activity;
TextureView textureView;
global::Android.Views.View view;
public CameraActivity(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
{
return;
}
try
{
SetupUserInterface();
AddView(view);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(#" ERROR: ", ex.Message);
}
}
void SetupUserInterface()
{
//activity = Context as Activity;
view = activity.LayoutInflater.Inflate(Resource.Layout.activity_camera, this, false);
activity.FragmentManager.BeginTransaction().Replace(Resource.Id.container, Camera2BasicFragment.NewInstance()).Commit();
}
The break point does hit the LayoutInflater but it does not open the view for the camera. The original Camera2 API: Camera2 API link for camera activity
This has SetContentView which does not work with Page Renderer.

How can I interact with a xamarin forms image in Android?

I have an image in Xamarin Forms. I want to use the native android features to interact with this image. For example, when the image is tapped, I want to know the x,y coordinates of where the image was tapped. I can use Android ImageView but I'm not sure how to cast the Xamarin Forms image to Android ImageView
[assembly: ExportRenderer(typeof(Image), typeof(FloorplanImageRenderer))]
namespace EmployeeApp.Droid.Platform
{
public class FloorplanImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
if (Control == null)
{
var imageView = (ImageView)e.NewElement; // This is not right
}
base.OnElementChanged(e);
}
}
}
But the Control is null....
No, it shouldn't be null. Back to your question, I think first of all, you will need to attach a touch event to the image control in PCL and create a property to hold the coordinate when image get touched. And I think here in your code:
[assembly: ExportRenderer(typeof(Image), typeof(FloorplanImageRenderer))]
I think the Image here should be your custom image control which inherits from Image in PCL.
Create a interface for touch event:
public interface IFloorplanImageController
{
void SendTouched();
}
Create a custom control for image:
public class FloorplanImage : Image, IFloorplanImageController
{
public event EventHandler Touched;
public void SendTouched()
{
Touched?.Invoke(this, EventArgs.Empty);
}
public Tuple<float, float> TouchedCoordinate
{
get { return (Tuple<float, float>)GetValue(TouchedCoordinateProperty); }
set { SetValue(TouchedCoordinateProperty, value); }
}
public static readonly BindableProperty TouchedCoordinateProperty =
BindableProperty.Create(
propertyName: "TouchedCoordinate",
returnType: typeof(Tuple<float, float>),
declaringType: typeof(FloorplanImage),
defaultValue: new Tuple<float, float>(0, 0),
propertyChanged: OnPropertyChanged);
public static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
}
}
Implement the custom renderer:
[assembly: ExportRenderer(typeof(FloorplanImage), typeof(FloorplanImageRenderer))]
namespace EmployeeApp.Droid.Platform
{
public class FloorplanImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
if (Control != null)
{
Control.Clickable = true;
Control.SetOnTouchListener(ImageTouchListener.Instance.Value);
Control.SetTag(Control.Id, new JavaObjectWrapper<FloorplanImage> { Obj = Element as FloorplanImage });
}
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (Control != null)
{
Control.SetOnTouchListener(null);
}
}
base.Dispose(disposing);
}
private class ImageTouchListener : Java.Lang.Object, Android.Views.View.IOnTouchListener
{
public static readonly Lazy<ImageTouchListener> Instance = new Lazy<ImageTouchListener>(
() => new ImageTouchListener());
public bool OnTouch(Android.Views.View v, MotionEvent e)
{
var obj = v.GetTag(v.Id) as JavaObjectWrapper<FloorplanImage>;
var element = obj.Obj;
var controller = element as IFloorplanImageController;
if (e.Action == Android.Views.MotionEventActions.Down)
{
var x = e.GetX();
var y = e.GetY();
element.TouchedCoordinate = new Tuple<float, float>(x, y);
controller?.SendTouched();
}
else if (e.Action == Android.Views.MotionEventActions.Up)
{
}
return false;
}
}
}
public class JavaObjectWrapper<T> : Java.Lang.Object
{
public T Obj { get; set; }
}
}
Use this control like this:
<local:FloorplanImage HeightRequest="300" x:Name="image" WidthRequest="300"
Aspect="AspectFit" Touched="image_Touched" />
code behind:
private void image_Touched(object sender, EventArgs e)
{
var cor = image.TouchedCoordinate;
}

MVVMCross Custom Control and Binding

I have created a custom control (CustomCard) which is a subclass of the CardView control. I would like to use this control within my project in different places.
For example, I may place the CustomCard within an xml layout manually, or I may want the CustomCard to be an item in an MvxListView. The key is that I would like to re-use the code as much as possible and benefit from having control over the CustomCard class.
When the CustomCard is instantiated, I am inflating it's layout using the standard layout inflater, see code:
using System;
using Android.Animation;
using Android.Content;
using Android.Support.V7.Widget;
using Android.Util;
using Android.Views;
using Android.Widget;
public class Card : CardView
{
private readonly Context _context;
public Card(Context context)
: base(context)
{
_context = context;
Init();
}
public Card(Context context, IAttributeSet attrs)
: base(context, attrs)
{
_context = context;
Init();
}
private void Init()
{
var inflater = (LayoutInflater) _context.GetSystemService(Context.LayoutInflaterService);
CardView = inflater.Inflate(Resource.Layout.base_card, this);
}
}
Within the layout base_card.xml, I have some elements that I would like to bind using MVVMCross, for example,
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/white">
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:id="#+id/basecard_title"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Title Text-->
<TextView
android:id="#+id/tv_basecard_header_title"
style="#style/card.title"
android:text="title text"
local:MvxBind="Text Title"
/>
<!-- ImageView -->
<MvxImageView
android:id="#+id/ib_basecard_header_button_expand"
style="#style/card.image"
local:MvxBind="Bitmap ImageBytes,Converter=InMemoryImage"/>
</RelativeLayout>
</FrameLayout>
My actual base_card layout is much more complex.
If I try to use my CustomCard within another XML Layout, none of the binding takes place. I think this is because I am using the standard layout inflater to inflate my base_card within my CustomCard rather than BindingInflate() but I can't be sure.
I have searched on SO and through the forums but I can't find any references to anyone using a custom control that inflates it's own view when instantiated with MVVMCross binding.
Has anyone done it, or am I trying to do something that isn't possible?
I ran into similar issue with CardView control. Since CardView directly inherits from FrameLayout I decided to use implementation almost identical to MvxFrameControl (Thanks Stuart for pointing out MvxFrameControl sample):
public class MvxCardView : CardView, IMvxBindingContextOwner
{
private object _cachedDataContext;
private bool _isAttachedToWindow;
private readonly int _templateId;
private readonly IMvxAndroidBindingContext _bindingContext;
public MvxCardView(Context context, IAttributeSet attrs)
: this(MvxAttributeHelpers.ReadTemplateId(context, attrs), context, attrs)
{
}
public MvxCardView(int templateId, Context context, IAttributeSet attrs)
: base(context, attrs)
{
_templateId = templateId;
if (!(context is IMvxLayoutInflater))
{
throw Mvx.Exception("The owning Context for a MvxCardView must implement LayoutInflater");
}
_bindingContext = new MvxAndroidBindingContext(context, (IMvxLayoutInflater)context);
this.DelayBind(() =>
{
if (Content == null && _templateId != 0)
{
Mvx.Trace("DataContext is {0}", DataContext == null ? "Null" : DataContext.ToString());
Content = _bindingContext.BindingInflate(_templateId, this);
}
});
}
protected MvxCardView(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
protected IMvxAndroidBindingContext AndroidBindingContext
{
get { return _bindingContext; }
}
public IMvxBindingContext BindingContext
{
get { return _bindingContext; }
set { throw new NotImplementedException("BindingContext is readonly in the list item"); }
}
protected View Content { get; set; }
protected override void Dispose(bool disposing)
{
if (disposing)
{
this.ClearAllBindings();
_cachedDataContext = null;
}
base.Dispose(disposing);
}
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
_isAttachedToWindow = true;
if (_cachedDataContext != null
&& DataContext == null)
{
DataContext = _cachedDataContext;
}
}
protected override void OnDetachedFromWindow()
{
_cachedDataContext = DataContext;
DataContext = null;
base.OnDetachedFromWindow();
_isAttachedToWindow = false;
}
[MvxSetToNullAfterBinding]
public object DataContext
{
get { return _bindingContext.DataContext; }
set
{
if (_isAttachedToWindow)
{
_bindingContext.DataContext = value;
}
else
{
_cachedDataContext = value;
if (_bindingContext.DataContext != null)
{
_bindingContext.DataContext = null;
}
}
}
}
}
Usage:
<YourNamespace.MvxCardView
android:layout_width="match_parent"
android:layout_height="match_parent"
local:MvxTemplate="#layout/base_card"
local:MvxBind="DataContext ." />
Note: Using custom implementation also solved my problem with binding click command to CardView control using local:MvxBind="Click MyCommand", which wasn't working until subclassing CardView.

Resources