Getting screen size of android fragment with xamarin - c#

I am currently working on an questionnaire program for android using xamarin and I'm using fragments to display the questions, as there are different question types. I need to make sure there is enough screen space for the questions but unfortunately I can't seem to find anything useful for it or find a version that works with xamarin.
The program is meant to check if there is enough room to draw the question, if there is it draws the question if not then it gets displayed on the next page. The questions are dynamically drawn so the program doesn't know before runtime if there is going to be space or not.
I have managed to get the screen size for the device but when I use similar code for the fragments it returns 0.
This is the code I used for the device and it works fine.
Point size = new Point();
this.WindowManager.DefaultDisplay.GetSize(size);
System.Diagnostics.Debug.WriteLine("Panel " + size.ToString());

I have managed to get the screen size for the device but when I use similar code for the fragments it returns 0.
You can get the fragment's size in your fragment like this:
public class MyFragment:Fragment
{
...
public override void OnViewCreated(View view, Bundle savedInstanceState)
{
base.OnViewCreated(view, savedInstanceState);
//add global layout event listener
view.ViewTreeObserver.GlobalLayout += ViewTreeObserver_GlobalLayout;
}
private void ViewTreeObserver_GlobalLayout(object sender, EventArgs e)
{
//get the size of fragment
var width=this.View.Width;
var height = this.View.Height;
}
}

Related

Displaying Image on navigation bar and page xamarin forms

Using Xamarin Forms 2.5.0.122203, I want to achieve to add an image that is half over navigation bar and another half in the page like this image
I tried Negative margin and it only worked on UWP. Unfortunately, iOS and Android cut image.
I was thinking of creating a custom navigation bar but the thing is UWP has this bug that doesn't hide native Navigation right.
Any solution?
Thank you
I ended up Copying SNavigation.Forms to my projects.
After following their documentation.
In the Page I override
protected override void OnBindingContextChanged()
{
if (BindingContext == null)
{
return;
}
base.OnBindingContextChanged();
SNavigationPage.SetNavContent(this, new LogoHeader()
{
BindingContext = BindingContext,
});
}
The LogoHeader is the intended layout to be postioned in the Navigation (Better use a Grid)
Visually I have one Navigation Header but technically it is cut in half.
It took a lot of time to get the perfect result because you have to remove all navigation shadows from Android and iOS but it is a reliable solution

How to swipe back in Xamarin.iOS?

I have a ContentPage with a WebView in my shared Xamarin project.
To enable Android's back button within the WebView, I used the following code:
protected override bool OnBackButtonPressed()
{
_myWebView.GoBack();
return true;
}
As we all know, iOS does not have a back button. I want to enable swiping left in order to navigate to the previous Webview page.
What is the easiest way or approach?
I would suggest implementing a custom renderer for the WebView and attach a gesture recognizer to the underlying UIWebView. This is how the WebViewRenderer for iOS is implemented in Xamarin Forms.
The Xamarin Forms WebView is actually a UIWebView on iOS. So you can implement the Swift code from the following answer in your renderer: https://stackoverflow.com/a/32012660/6192895. Converting this Swift code to C# is fairly trivial, but this is also a potential solution:
Basic solution
public class SwipeWebViewRenderer : WebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
var view = NativeView as UIWebView;
var swipeBackRecognizer = new UISwipeGestureRecognizer(HandleSwipeBack);
swipeBackRecognizer.Direction = UISwipeGestureRecognizerDirection.Right;
view.AddGestureRecognizer(swipeBackRecognizer);
}
void HandleSwipeBack()
{
(Element as WebView).GoBack();
}
}
This solution is fairly basic, it just does the GoBack() action without actually showing the swipe of the page like the normal Safari browser does. So you might have to add some more code if that's what you need.
Transitioning
You can make use of CoreAnimation for this. Use this as your HandleSwipeBack method:
void HandleSwipeBack()
{
CATransition animation = new CATransition();
animation.Type = CAAnimation.TransitionPush;
animation.Subtype = CAAnimation.TransitionFromLeft;
animation.Duration = 0.50;
animation.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseInEaseOut);
(NativeView as UIWebView).Layer.AddAnimation(animation, CALayer.Transition);
(Element as WebView).GoBack();
}
This will not get you the exact same behaviour as the Safari browser, as it will not cancel the animation if you stop the swipe halfway for example. It is possible to do so, but it would require more code.
Extra information
You might also be able to draw some inspiration from this Pull Request for a SwipeGestureRecognizer, which was not included in Xamarin Forms because unit tests and UI tests were missing.
Unfortunately the default Xamarin Forms GestureRecognizers are limited to Tap, Pinch and Pan for the moment.
Can you try the following
NavigationPage.SetBackButtonTitle (this, "")
or alternatively you can add tool bar item and then try something like this
ToolbarItems.Add(new ToolbarItem("Back", "BackIcon.png", () => { _webview.GoBack(); }));

How can I know when a view is finished rendering?

I've noticed that when OnElementPropertyChanged is fired on a VisualElement like a BoxView, the properties of the underlying platform view are not updated at that time.
I want to know when the VisualElement's corresponding platform view is finished rendering, something like:
this.someBoxView.ViewHasRendered += (sender, e) => {
// Here I would know underlying UIView (in iOS) has finished rendering
};
Looking through some code inside of Xamarin.Forms, namely VisualElementRenderer.cs, it would seem that I could raise an event after OnPropertyChanged has finished. Something like:
protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
SetBackgroundColor(Element.BackgroundColor);
else if (e.PropertyName == Layout.IsClippedToBoundsProperty.PropertyName)
UpdateClipToBounds();
else if (e.PropertyName == PlatformConfiguration.iOSSpecific.VisualElement.BlurEffectProperty.PropertyName)
SetBlur((BlurEffectStyle)Element.GetValue(PlatformConfiguration.iOSSpecific.VisualElement.BlurEffectProperty));
// Raise event
VisualElement visualElement = sender as VisualElement;
visualElement.ViewHasRendered();
}
Naturally there's a few more complexities to adding an event to the VisualElement class, as it would need to be subclassed. But I think you can see what I'm after.
While poking around I've noticed properties on VisualElement like IsInNativeLayout. But that only seems to be implementing in Win/WP8. Also, UpdateNativeWidget on VisualElementRenderer as well, however I can't figure out the proper way to leverage them.
Any ideas?
Much appreciated.
TL;DR : Run away, do not go down this path...
On iOS everything that displays content to the screen happens within a UIView (or subclass) and drawRect: is the method that does the drawing. So when drawRect: is done, the UIView is drawing is done.
Note: Animations could be occurring and you might see hundreds of completed rendering cycles completed. You might need to hook into every animation's completion handler to determine when things really are done "rendering".
Note: The drawing is done off-screen and depending upon the iDevice, the screen refresh Hz could 30FPS, 60FPS or in the case of iPad Pro it is variable (30-60hz)...
Example:
public class CustomRenderer : ButtonRenderer
{
public override void Draw(CGRect rect)
{
base.Draw(rect);
Console.WriteLine("A UIView is finished w/ drawRect: via msg/selector)");
}
}
On Android the common way to draw content is via a View or subclass, you could obtain a surface, draw/bilt via OpenGL to a screen, etc... and that might not be within a View, but for your use-case, think Views..
Views have Draw methods you can override, and you can also hook into ViewTreeObserver and monitor OnPreDraw and OnDraw, etc, etc, etc... Sometimes you have to monitor the View's parent (ViewGroup) to determine when drawing is going to be done or when is completed.
Also all standard Widgets are completely inflated via xml resources and that is optimized so you will never see a Draw/OnDraw method call (Note: You should always(?) get a OnPreDraw listener call if you force it).
Different Views / Widgets behave differently and there no way to review all the challenges you will have determining when a View is really done "rendering"...
Example:
public class CustomButtonRenderer : Xamarin.Forms.Platform.Android.AppCompat.ButtonRenderer,
ViewTreeObserver.IOnDrawListener, ViewTreeObserver.IOnPreDrawListener
{
public bool OnPreDraw() // IOnPreDrawListener
{
System.Console.WriteLine("A View is *about* to be Drawn");
return true;
}
public void OnDraw() // IOnDrawListener
{
System.Console.WriteLine("A View is really *about* to be Drawn");
}
public override void Draw(Android.Graphics.Canvas canvas)
{
base.Draw(canvas);
System.Console.WriteLine("A View was Drawn");
}
protected override void Dispose(bool disposing)
{
Control?.ViewTreeObserver.RemoveOnDrawListener(this);
Control?.ViewTreeObserver.RemoveOnPreDrawListener(this);
base.Dispose(disposing);
}
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
Control?.SetWillNotDraw(false); // force the OnPreDraw to be called :-(
Control?.ViewTreeObserver.AddOnDrawListener(this); // API16+
Control?.ViewTreeObserver.AddOnPreDrawListener(this); // API16+
System.Console.WriteLine($"{Control?.ViewTreeObserver.IsAlive}");
}
}
}
Misc:
Note: Layout Optimizations, Content caching, GPU caching, is hardware acceleration enabled in the Widget/View or not, etc... can prevent the Draw methods from being called...
Note: Animation, effects, etc... can cause these these methods to be call many, many, many times before an area of the screen is completely finished displaying and ready for user interaction.
Personal Note: I've gone down this path once due to a crazy client requirements, and after banging my head on the desk for some time, a review of the actual goal of that area of the UI was done and I re-wrote the requirement and never tried this again ;-)
I'm going to answer my own question it hopes that the solution will help someone who is struggling with this issue in the future.
Follow #SushiHangover's advice and RUN don't WALK away from doing something like this. (Although his recommendation will work and is sound). Attempting to listen/be notified when the platform has finished rendering a view, is a terrible idea. As #SushiHangover mentions there's simply too many things that can go wrong.
So what brought me down this path?
I have a requirement for a pin code UI similar to the one in iOS to unlock your device, and in many other apps. When a user presses a digit on the pad I want to update the corresponding display "box" (boxes above the pad). When the user inputs the last digit I want the last "box" to be filled in, in my case a Background color change, and then execution to continue in which the view would transition to the next screen in the workflow.
A problem arose as I tried to set the BackgroundColor property on the fourth box and then transition the screen. However, since execution doesn't wait for the property to change the screen transitions before the change is rendered. Naturally this makes for a bad user experience.
In an attempt to fix it, I thought "Oh! I simply need to be notified when the view has been rendered". Not smart, as I've mentioned.
After looking at some objective C implementations of similar UIs I realizes that the fix it quite simple. The UI should wait for a brief moment and allow the BackgroundColor property to render.
Solution
private async Task HandleButtonTouched(object parameter)
{
if (this.EnteredEmployeeCode.Count > 4)
return;
string digit = parameter as string;
this.EnteredEmployeeCode.Add(digit);
this.UpdateEmployeeCodeDigits();
if (this.EnteredEmployeeCode.Count == 4) {
// let the view render on the platform
await Task.Delay (1);
this.SignIn ();
}
}
A small millisecond delay is enough to let the view finished rendering without having to go down a giant rabbit hole and attempt to listen for it.
Thanks again to #SushiHangover for his detailed response. You are awesome my friend! :D

Xamarin Forms Android System Exception "Android only allows one navigation page on screen at a time"

Recently i decided to give Xamarin Forms a try, so i started migrating my Windows Phone / Android Xamarin project, to the new "Forms" format.
After migrating two simple screens (views), the code runs just fine on my Windows Phone, but it crashes on the Android Platform. I am using two devices to test the code, with no emulator to avoid unecessary headaches.
The exception raised on the android platform is: System.Exception: Android only allows one navigation page on screen at a time.
The problem is not related to the "View" class itself, since it doesn`t happen once i set it as the "start screen" for the Android.
I'm afraid the problem is related to the fact that i am "Pushing" two screens via the "Navigation.PushAsync(...)" method provided by Xamarin.
Any ideas on how do actually fix it ?
EDIT:
Code Sample
Overview: The "MainView.cs" is just a content page with a label on the top, followed by two buttons alligned vertically.
How to reproduce the exception: Click on "Get All Users" button on the mainview. I have modified the code to push the same view once the button is clicked, instead of pushing another view (The view that has a listview of users, which i haven't included, just to make it simple). Once the button is clicked, it will navigate to a new instance of "MainView" by calling "Navigation.PushAsync(...)". This will trigger the exception after the screen is loaded.
You are nesting Navigation pages. This is not necessary. Try this instead:
public class ViewsHandler
{
public static Page GetMainPage()
{
return new MainView();
}
public static Page GetUsersListPage()
{
return new UsersListView ();
}
}
public class MainActivity : AndroidActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Initializing Xamarin Form
Xamarin.Forms.Forms.Init (this, bundle);
// create a single NavigationPage wrapping your content
SetPage (new NavigationPage(ViewsHandler.GetMainPage()));
}
}

Value always return null

First off, I'll explain my program.
I have one winform that I use as my control panel. From here I have the ability to take a screen shot of my desktop using the mouse to define the area. Much like how the snipping tool works within windows.
The screen shot is generated using my screen shot class which is ran by another form called Form1. This second form has no other code in it but is simply used to generate the rectangle the screen shot will use. From here, the taken screen shot is stored in the clip board and passed back to my Screenshot class.
Now, from here what I want is a picture box in my control panel to display this taken image. Exactly like how the snipping tool works. However, the code I have wrote to pass this image to the control panel from the screen shot class complains that the event handler is always returning as null.
The code I have wrote for this is as follows:
Image img = (Image)bitmap;
if (OnUpdateStatus == null) return;
ProgressEventArgs args = new ProgressEventArgs(img);
OnUpdateStatus(this, args);
I've tried commenting out the if statement but then the processing OnUpdateStatus throws an exception saying it no longer exists.
Now, in my control panel form I am trying to grab that image and display with the following code:
private ScreenCapture _screenCap;
public ControlPanel()
{
InitializeComponent();
_screenCap = new ScreenCapture();
_screenCap.OnUpdateStatus += _screen_CapOnUpdateStatus;
}
private void _screen_CapOnUpdateStatus(object sender, ProgressEventArgs e)
{
imagePreview.Image = e.CapturedImage;
}
I've spent hours on this but i cannot work out why the image won't display in the image box on my ControlPanel. Can anyone with a fresh set of eyes help me out? Why is it the screen shot image I take, isn't being displayed in my picture box held on my ControlPanel?
This is the problem, I think (there's a lot of code to skim through - a short but complete example would have been better):
ScreenCapture capture = new ScreenCapture();
capture.CaptureImage(...);
That's a new instance of ScreenCapture. It doesn't have any event handlers attached to it (which is why you're seeing a value of null for OnUpdateStatus. If you want to use the instance that you've created in the ControlPanel class, you'll want to pass that to your Form1 (e.g. in the constructor).

Categories

Resources