I am developing a Xamarin Forms which writes successfully an image to external storage and then should use it as Background of a ContentPage.
In the constructor of the ContentPage I wrote this:
this.BackgroundImage = "/storage/emulated/0/DCIM/D72D01AEF71348CDBFEED9D0B2F259F7.jpg"
but the background image never shows.
I checked the Android Manifest and the permissions of read and write external storage are set correctly.
What am I missing?
The problem with your code is that BackgroundImage expects an image that's bundled with your app. Android implementation for updating the background image is here:
void UpdateBackgroundImage(Page view)
{
if (!string.IsNullOrEmpty(view.BackgroundImage))
this.SetBackground(Context.Resources.GetDrawable(view.BackgroundImage));
}
GetDrawable method expects an image from your application's Resources which obviously doesn't exist in your case.
What you should do, is create a custom renderer with a new BindableProperty called ExternalBackgroundImage. Then you could handle loading of the external image as a background in the Android specific custom renderer.
PCL project
Remember to change your current page from ContentPage to ExternalBackgroundImagePage so that you have access to the ExternalBackgroundImage property.
public class ExternalBackgroundImagePage : ContentPage
{
public static readonly BindableProperty ExternalBackgroundImageProperty = BindableProperty.Create("ExternalBackgroundImage", typeof(string), typeof(Page), default(string));
public string ExternalBackgroundImage
{
get { return (string)GetValue(ExternalBackgroundImageProperty); }
set { SetValue(ExternalBackgroundImageProperty, value); }
}
}
Android project
[assembly:ExportRenderer (typeof(ExternalBackgroundImagePage), typeof(ExternalBackgroundImagePageRenderer))]
namespace YourProject.Droid
{
public class ExternalBackgroundImagePageRenderer : PageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
Page view = e.NewElement;
base.OnElementChanged(e);
UpdateExternalBackgroundImage(view);
}
void UpdateExternalBackgroundImage(Page view)
{
if (string.IsNullOrEmpty(view.ExternalBackgroundImage))
return;
// Retrieve a bitmap from a file
var background = BitmapFactory.DecodeFile(view.ExternalBackgroundImage);
// Convert to BitmapDrawable for the SetBackground method
var bitmapDrawable = new BitmapDrawable(background);
// Set the background image
this.SetBackground(bitmapDrawable);
}
}
}
Usage
this.ExternalBackgroundImage = "/storage/emulated/0/DCIM/D72D01AEF71348CDBFEED9D0B2F259F7.jpg"
Related
I have found that on iOS, OnAppearing is called when the page literally appears on the screen, whereas on Android, it's called when it's created.
I'm using this event to lazily construct an expensive to construct view but obviously the Android behaviour defeats this.
Is there some way of knowing on Android when a screen literally appears on the screen?
You can use the event:
this.Appearing += YourPageAppearing;
Otherwise, you should use the methods of the Application class that contains the lifecycle methods:
protected override void OnStart()
{
Debug.WriteLine ("OnStart");
}
protected override void OnSleep()
{
Debug.WriteLine ("OnSleep");
}
protected override void OnResume()
{
Debug.WriteLine ("OnResume");
}
On Android, Xamarin.Forms.Page.OnAppearing is called immediately before the page's view is shown to user (not when the page is "created" (constructed)).
If you want an initial view to appear quickly, by omitting an expensive sub-view, use a binding to make that view's IsVisible initially be "false". This will keep it out of the visual tree, avoiding most of the cost of building it. Place the (invisible) view in a grid cell, whose dimensions are constant (either in DPs or "*" - anything other than "Auto".) So that layout will be "ready" for that view, when you make it visible.
APPROACH 1:
Now you just need a binding in view model that will change IsVisible to "true".
The simplest hack is to, in OnAppearing, fire an action that will change that variable after 250 ms.
APPROACH 2:
The clean alternative is to create a custom page renderer, and override "draw".
Have draw, after calling base.draw, check an action property on your page.
If not null, invoke that action, then clear it (so only happens once).
I do this by inheriting from a custom page base class:
XAML for each of my pages (change "ContentPage" to "exodus:ExBasePage"):
<exodus:ExBasePage
xmlns:exodus="clr-namespace:Exodus;assembly=Exodus"
x:Class="YourNamespace.YourPage">
...
</exodus:ExBasePage>
xaml.cs:
using Exodus;
// After creating page, change "ContentPage" to "ExBasePage".
public partial class YourPage : ExBasePage
{
...
my custom ContentPage. NOTE: Includes code not needed for this, related to iOS Safe Area and Android hardward back button:
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace Exodus
{
public abstract partial class ExBasePage : ContentPage
{
public ExBasePage()
{
// Each sub-class calls InitializeComponent(); not needed here.
ExBasePage.SetupForLightStatusBar( this );
}
// Avoids overlapping iOS status bar at top, and sets a dark background color.
public static void SetupForLightStatusBar( ContentPage page )
{
page.On<Xamarin.Forms.PlatformConfiguration.iOS>().SetUseSafeArea( true );
// iOS NOTE: Each ContentPage must set its BackgroundColor to black or other dark color (when using LightContent for status bar).
//page.BackgroundColor = Color.Black;
page.BackgroundColor = Color.FromRgb( 0.3, 0.3, 0.3 );
}
// Per-platform ExBasePageRenderer uses these.
public System.Action NextDrawAction;
/// <summary>
/// Override to do something else (or to do nothing, i.e. suppress back button).
/// </summary>
public virtual void OnHardwareBackButton()
{
// Normal content page; do normal back button behavior.
global::Exodus.Services.NavigatePopAsync();
}
}
}
renderer in Android project:
using System;
using Android.Content;
using Android.Views;
using Android.Graphics;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Exodus;
using Exodus.Android;
[assembly: ExportRenderer( typeof( ExBasePage ), typeof( ExBasePageRenderer ) )]
namespace Exodus.Android
{
public class ExBasePageRenderer : PageRenderer
{
public ExBasePageRenderer( Context context ) : base( context )
{
}
protected override void OnElementChanged( ElementChangedEventArgs<Page> e )
{
base.OnElementChanged( e );
var page = Element as ExBasePage;
if (page != null)
page.firstDraw = true;
}
public override void Draw( Canvas canvas )
{
try
{
base.Draw( canvas );
var page = Element as ExBasePage;
if (page?.NextDrawAction != null)
{
page.NextDrawAction();
page.NextDrawAction = null;
}
}
catch (Exception ex)
{
// TBD: Got Disposed exception on Android Bitmap, after rotating phone (in simulator).
// TODO: Log exception.
Console.WriteLine( "ExBasePageRenderer.Draw exception: " + ex.ToString() );
}
}
}
}
To do some action after the first time the page is drawn:
public partial class YourPage : ExBasePage
{
protected override void OnAppearing()
{
// TODO: OnPlatform code - I don't have it handy.
// On iOS, we call immediately "DeferredOnAppearing();"
// On Android, we set this field, and it is done in custom renderer.
NextDrawAction = DeferredOnAppearing;
}
void DeferredOnAppearing()
{
// Whatever you want to happen after page is drawn first time:
// ((MyViewModel)BindingContext).ExpensiveViewVisible = true;
// Where MyViewModel contains:
// public bool ExpensiveViewVisible { get; set; }
// And your XAML contains:
// <ExpensiveView IsVisible={Binding ExpensiveViewVisible}" ... />
}
}
NOTE: I do this differently on iOS, because Xamarin Forms on iOS (incorrectly - not to spec) calls OnAppearing AFTER the page is drawn.
So I have OnPlatform logic. On iOS, OnAppearing immediately calls DeferredOnAppearing. On Android, the line shown is done.
Hopefully iOS will eventually be fixed to call OnAppearing BEFORE,
for consistency between the two platforms.
If so, I would then add a similar renderer for iOS.
(The current iOS implementation means there is no way to update a view before it appears a SECOND time, due to popping the nav stack.
instead, it appears with outdated content, THEN you get a chance
to correct it. This is not good.)
I'm trying to embed Forms XAML view into Xamarin Native application with MvvmCross 6.0.
I tried to reproduce effect of this solution but I got stuck on registering IMvxFormsViewPresenter. I also followed this tutorial.
I have simple MainContainerActivity with MainFragment (with corresponding MainViewModel in Core) that contains a button to Forms SettingsActivity/SettingsViewModel.
Full source can be found in the test repository.
Currently, I struggle with an exception thrown in base.OnCreate(bundle); while navigating to SettingsViewModel:
MvvmCross.Exceptions.MvxIoCResolveException has been thrown
Failed to resolve type MvvmCross.Forms.Presenters.IMvxFormsViewPresenter
I cannot find a way to register this Forms Presenter. I tried to do it in Setup but with no success.
I also tried to resolve MvxFormsAndroidViewPresenter in SplashActivity but Mvx.Resolve<IMvxViewPresenter>() as MvxFormsAndroidViewPresenter; yields null.
Do you have any idea what should I do to incorporate Forms views into MvvmCross 6.0 native application?
namespace MvvmCrossFormsEmbedding.Droid.Views
{
[Activity(Theme = "#style/AppTheme",
Label = "SettingsActivity")]
public class SettingsActivity : MvxFormsAppCompatActivity<SettingsViewModel>
{
public static SettingsActivity Instance { get; private set; }
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// #1 Initialize
Forms.Init(this, null);
SetContentView(Resource.Layout.activity_settings);
var toolbar = FindViewById<Toolbar>(Resource.Id.layout_toolbar);
SupportActionBar.Title = "Settings";
Instance = this;
// #2 Use it
var frag = new SettingsView().CreateFragment(this);
var ft = FragmentManager.BeginTransaction();
ft.Replace(Resource.Id.fragment_frame_layout, frag, "main");
ft.Commit();
}
}
}
I'm trying to get this animation/transition working in my Xamarin Android application with Mvx.
I have a recyclerview with cards. When tapping on a card, I now call:
private void TimeLineAdapterOnItemClick(object sender, int position)
{
TimeLineAdapter ta = (TimeLineAdapter) sender;
var item = ta.Items[position];
int photoNum = position + 1;
Toast.MakeText(Activity, "This is photo number " + photoNum, ToastLength.Short).Show();
ViewModel.ShowDetails(item.Id);
}
I'm trying to find out how to translate this java navigation with transition to Xamarin with Mvvmcross:
ActivityOptionsCompat options =
ActivityOptionsCompat.MakeSceneTransitionAnimation(this, imageView, getString(R.string.activity_image_trans));
startActivity(intent, options.toBundle());
I know that within Mvx you can make use of custom presenters, but how do I get hold of, for example, the ImageView of the tapped Card within the RecyclerView which I would like to 'transform' to the new ImageView on the new Activity?
Thanks!
.
Is there a Xamarin Mvvmcross Android Shared Element Navigation
example?
I do not believe so.
I know that within Mvx you can make use of custom presenters, but how
do I get hold of, for example, the ImageView of the tapped Card within
the RecyclerView which I would like to 'transform' to the new
ImageView on the new Activity?
The easiest way that I can think of to achieve the sharing of control elements you want to transition is via the use of view tags and a presentation bundle when using ShowViewModel.
I would suggest making some changes to your Adapter Click handler to include the view of the ViewHolder being selected (See GitHub repo for example with EventArgs). That way you can interact with the ImageView and set a tag that can be used later to identity it.
private void TimeLineAdapterOnItemClick(object sender, View e)
{
var imageView = e.FindViewById<ImageView>(Resource.Id.imageView);
imageView.Tag = "anim_image";
ViewModel.ShowDetails(imageView.Tag.ToString());
}
Then in your ViewModel, send that tag via a presentationBundle.
public void ShowDetails(string animationTag)
{
var presentationBundle = new MvxBundle(new Dictionary<string, string>
{
["Animate_Tag"] = animationTag
});
ShowViewModel<DetailsViewModel>(presentationBundle: presentationBundle);
}
Then create a custom presenter to pickup the presentationBundle and handle the creating of new activity with the transition. The custom presenter which makes use of the tag to find the element that you want to transition and include the ActivityOptionsCompat in the starting of the new activity. This example is using a MvxFragmentsPresenter but if you are not making use of fragments and using MvxAndroidViewPresenter the solution would be almost identical (Override Show instead and no constructor required).
public class SharedElementFragmentsPresenter : MvxFragmentsPresenter
{
public SharedElementFragmentsPresenter(IEnumerable<Assembly> AndroidViewAssemblies)
: base(AndroidViewAssemblies)
{
}
protected override void ShowActivity(MvxViewModelRequest request, MvxViewModelRequest fragmentRequest = null)
{
if (InterceptPresenter(request))
return;
Show(request, fragmentRequest);
}
private bool InterceptPresenter(MvxViewModelRequest request)
{
if ((request.PresentationValues?.ContainsKey("Animate_Tag") ?? false)
&& request.PresentationValues.TryGetValue("Animate_Tag", out var controlTag))
{
var intent = CreateIntentForRequest(request);
var control = Activity.FindViewById(Android.Resource.Id.Content).FindViewWithTag(controlTag);
control.Tag = null;
var transitionName = control.GetTransitionNameSupport();
if (string.IsNullOrEmpty(transitionName))
{
Mvx.Warning($"A {nameof(transitionName)} is required in order to animate a control.");
return false;
}
var activityOptions = ActivityOptionsCompat.MakeSceneTransitionAnimation(Activity, control, transitionName);
Activity.StartActivity(intent, activityOptions.ToBundle());
return true;
}
return false;
}
}
GetTransitionNameSupport is an extension method that just does a platform API check when getting the TransitionName.
public static string GetTransitionNameSupport(this ImageView imageView)
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop)
return imageView.TransitionName;
return string.Empty;
}
The final step would be to register the custom presenter in you Setup.cs
protected override IMvxAndroidViewPresenter CreateViewPresenter()
{
var mvxPresenter = new SharedElementFragmentsPresenter(AndroidViewAssemblies);
Mvx.RegisterSingleton<IMvxAndroidViewPresenter>(mvxPresenter);
return mvxPresenter;
}
You can check the repo on GitHub which demonstrates this example. The solution is designed so that the presenter does not have to care about the type of the control that is being transitioned. A control only requires a tag used to identify it. The example in the repo also allows for specifying multiple control elements that you want to transition (I did not want to include more complexity in the example above).
Currently my code creates an instance of a hidden class and initializes a WebView that should download JavaScript from the server. The problem is that if the WebView is not part of the main screen that the user is looking at, it doesn't load the URL.
My Simple App:
public partial class App : Application
{
// set up Hidden WebView
CompanySDK.FooClass foo = new CompanySDK.FooClass();
public App()
{
InitializeComponent();
var page = new ContentPage();
page = new DemoAppPage();
MainPage = page;
}
protected override void OnStart()
{
// test a method call
foo.test();
}
}
Hidden WebView Class:
public class FooClass
{
WebView browser = new WebView();
public FooClass()
{
browser.Source = "https://companyserver.com/xyz";
// make the browser invisible
browser.SetValue(IsVisibleProperty, false);
System.Diagnostics.Debug.WriteLine("WebView Started for platform: " + Device.OS.ToString().ToLower());
}
public void test()
{
System.Diagnostics.Debug.WriteLine("Test Method ");
String js = "alert('Test Method');";
browser.Eval(js);
}
}
If I change FooClass to be a ContentPage and add it as the main app view, I see calls to companyserver.com, but I can't have the WebView as part of the UI, as it should remain in the background / hidden. My debug prints show up in all cases, so I know the code is being called.
Is there a way to achieve what I'm after?
I new in Xamarin form. On Android, I used ViewPager to load images and the user swipe around the pages. Since Android has adapter, all the views are not initialized at once. Now I want to move to Xamarin form and seeing there is Carousel Page. Does it behave the same as ViewPager only load pages as needed?
Xamarin.Forms CarouselPage does not support UI virtualization (recycling).
Initialization performance and memory usage can be a problem depending upon the number of pages/children.
The new preferred VisualElement to use is the CarouselView that is basically superseding CarouselPage and it has been optimized for each platform.
Blog: Xamarin.Forms CarouselView
Nuget: Xamarin.Forms.CarouselView (Currently in pre-release)
FYI: I just looked the source for for the Android renderer (CarouselViewRenderer.cs) and it does indeed implement RecyclerView...
If you prevent the call to InitializeComponent in the constructor of the page you might have an effect on the load time.
public interface CarouselChildPage {
void childAppearing();
void childDissapearing();
}
public partial class MainPage : CarouselPage {
CarouselPageChild previousPage;
protected override void OnCurrentPageChanged() {
base.OnCurrentPageChanged();
if (previousPage != null)
previousPage.childDissapearing();
int index = Children.IndexOf(CurrentPage);
CarouselPageChild childPage = Children[index] as CarouselPageChild;
childPage.childAppearing();
previousPage = childPage;
}
}
public partial class FriendsListPage : ContentPage, CarouselPageChild {
bool isLoaded = false;
public FriendsListPage() {
// Remove Initialise Component Here
}
public void childAppearing() {
Logger.log("My Appearing");
if (!isLoaded){
InitializeComponent();
isLoaded = true;
}
}
public void childDissapearing() {
Logger.log("My Disappearing");
}
}