Xamarin Forms - attaching scrollview.Scrolled to a function - c#

When I attach a scrollview to a function like this
textScroll.Scrolled += (sender, e) => { onScrolled(); };
Each time I scroll up or down, OnScrolled() is called multiple times. I know I can get the size of the content and compare it to the ScrollY value, obviously the ScrollY value changes each time, but as far as I can see I won't know when the last call happens (per user scroll).
I only want to call this once per scroll, failing that call it each time as is happening now, but only act when I know I'm on the last call.
Is this possible?
thanks

It is possible, but with a custom renderer for each platform.
On iOS: you will want to implement delegates for DecelerationEnded and WillEndDragging. The reason for also implementing DecelerationEnded is to allow for a fling by the user and waiting for the velocity to come to 0.
On Android it is a bit more complicated. Here is a native Android SO post I followed and translated into c# in a renderer. Works pretty well for me.
Android: Detect when ScrollView stops scrolling
After having both implemented, you can call to your Xamarin.Forms view in order to notify that the view has Stopped scrolling (i.e. final call)

Related

Is my code legal in Android usage?

I asked a question recently about how to disable the back button is Android, after a while I got it working with these lines of code
public override void OnBackPressed ()
{
// base.OnBackPressed (); /* Comment this base call to avoid calling Finish() */
// Do nothing
}
And just recently someone commented this
Disabling the back button is counter-intuitive and breaks the device
usage contract imposed by Android. So i suggest you rethink.
-Question-
What would be a possible change to this? I dont want to be able to press the back button when playing my quiz game because that would make be able to cheat. New to android Development
Instead of simply making the back button do nothing, you could have it create a popup asking something along the lines of "Are you sure you want to leave the quiz? (This will count as a loss)". And have it take the user back to the main page of your app if he confirms (instead of back to the previous page).
Why not imitate what many websites do and make it so going 'back' to a page works but doesn't display any information?
It depends on your code, but perhaps you can make your buttons and text (or whatever it is you don't want them interacting with) change to be unseen whenever they move on to a new page. Or just throw up a message that says 'You can't do that' to cover the page that they'll only ever see if they go back to view it again.

LayoutParams change only takes effect in fullscreen

im using Xamarin with MvvmCross.
Ive done a FragmentDialog with a recyclerView inside, the list is populated via bindings on xml file, so i have no adapter and i should keep it this way.
If im not wrong, theres no built in way to make the recyclerView take only the size needed for its content, this should not be a problem, but in this case i need the list to start from bottom...
So i did this (its a custom fullscreen dialog) :
MvxRecyclerView list = Dialog.FindViewById<MvxRecyclerView>(Resource.Id.recyclerview);
list.LayoutChange += List_LayoutChange;
Then in layoutChange
private void List_LayoutChange(object sender, View.LayoutChangeEventArgs e)
{
MvxRecyclerView list = Dialog.FindViewById<MvxRecyclerView>(Resource.Id.recyclerview);
int itemHeight = list.GetChildAt(0).Height;
if (itemHeight != 0)
{
ViewGroup.LayoutParams prms = list.LayoutParameters;
prms.Height = itemHeight * list.GetAdapter().ItemCount;
list.LayoutParameters = prms;
list.LayoutChange -= List_LayoutChange;
list.RequestLayout();
}
}
That was working fine, the list get exactly the height needed and the list looks like it starts from bottom.
Now the client tell me that he doesnt like the fullscreen dialog and wants the status bar, i think that should be easy, just to remove this line at the dialog creation right?
dialog.Window.AddFlags(WindowManagerFlags.Fullscreen);
But looks like its not that easy, when the dialog its not fullscreen the layoutParams change seems to have no effect, it just dont do nothing.
My method is being called and i get the right item height, it just dont change the recyclerview height.
Notice that setting fullscreen at creation and clearing the flag after the recyclerview params change works
So looks like it only works during fullscreen mode.
Can someone throw some light at this?
Thanks in advance.
As you said, RecyclerView was not aware of its size.
Since last update to the support lib, it is !
http://android-developers.blogspot.fr/2016/02/android-support-library-232.html
The RecyclerView widget provides an advanced and flexible base for creating lists and grids as well as supporting animations. This release brings an exciting new feature to the LayoutManager API: auto-measurement! This allows a RecyclerView to size itself based on the size of its contents. This means that previously unavailable scenarios, such as using WRAP_CONTENT for a dimension of the RecyclerView, are now possible. You’ll find all built in LayoutManagers now support auto-measurement.
I would suggest to wait for the Xamarin wrapped lib (there is already a beta https://www.nuget.org/packages/Xamarin.Android.Support.v4/23.2.0-beta1)

How to get a default iOS navigation back button

How can I get the default back button on an iOS UINavigationController? I have the button being added in code, but I want the default style. Is there an Enum of System Images that contains the '<', or is there not really a way, and Ill have to design my own?
Heres the code to make the button:
controller.DetailViewController.NavigationItem.SetLeftBarButtonItem (new UIBarButtonItem(UIBarButtonSystemItem.Stop, (sender, args) => {
controller.DetailViewController.NavigationController.PopViewController(true);
}), true);
Its written in C# using mono touch, and Xamarin.iOS, but I can work on porting Objective-c/Swift code if needed. Im just stuck at this point and can't seem to figure it out at all.
If you push your view controller then you will get default back button with navigation bar. If you don't want to push to push viewController then you need to add it on storyBoard. For pushing the view controller you write the code as
Your_ViewController *viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"Your_ViewController"];
[self.navigationController pushViewController:viewController animated:YES];

Implementing Tab-like Navigation model in Windows Phone 8 — How?

I am currently trying to implement a navigation scheme that closely resembles that of the Internet Explorer app on Windows Phone 8.
The IE app can have multiple tabs that the user can switch between. Each of these tabs has its own history. Hitting the Back Button on the phone takes you to the previous page in that tab's Navigation history (Not the PhoneApplicationFrame.BackStack). If there are no previous pages, the back button takes you to the previous opened tab or, if none, exits the app.
Why this is troubling me
Application.RootVisual can only be set once. So you can't have two PhoneApplicationFrames, each with its own BackStack, to swap RootVisual between the two.
You cannot traverse the BackStack (it is a Stack, after all). Can only call GoBack(). Calling GoForward() will throw an Exception.
PhoneApplicationFrame.GoBack() removes entries from the BackStack which can only be added again through the PhoneApplicationFrame.Navigate(...) method. So, manipulating the BackStack is a no-go.
Bright Ideas
Keep a Dictionary<enum, List<string>> which is updated with each call to a custom NavigationService.Navigate(tabTypeEnum, uriString, params). This will keep the Navigation history for each tabType, allowing us to possibly Navigate through the current Tab's history when the BackKeyPress event is handled. Bad thing is, calling Navigate(...) to go to previous pages (instead of GoBack) will add to the BackStack. So requires maintenance that hurts my brain right now.
Create a custom NavigationAwareTabPage : PhoneApplicationPage, which keeps track of its own navigation history and fakes navigation by animating a transition when its Content is changed. The only time we call a true Navigate is when we switch from one tab to another. (I think this is what the IE app does.) And the BackKeyPress would have to look like below.
This:
void RootFrame_BackKeyPress(object sender, CancelEventArgs e)
{
var rootFrame = sender as PhoneApplicationFrame;
if (rootFrame.CanGoBack)
{
// Get the NavigationAwarePage
var navAwarePage = rootFrame.Content as NavigationAwareTabPage;
if(navAwarePage.CanGoBack())
{
// This method "navigates" to the next page
// by changing the navAwarePage.Content
navAwarePage.GoBackToPreviousPage();
e.Cancel = true;
}
}
}
Has anyone been down this road?
All the magic of how ReactiveUI overrides the Back button is here:
https://github.com/reactiveui/ReactiveUI/blob/master/ReactiveUI.Mobile/WP8AutoSuspendApplication.cs#L91
The way that this works in ReactiveUI is that there is a content control named RoutedViewHost that is listening to the Back being signaled (you can do whatever you want in response to the hardware Back button and cancel the default action). ReactiveUI maintains its own ViewModel-based back stack and manipulates that instead of using WP8s, and you never call WP8s navigation methods.
This effectively means that, from WP8's perspective, there is only ever one page in the entire application. WP8 really wants to create that page itself, and it's specified in WMAppManifest.xml.
Don't try to participate in WP8's Frame system, it really wants to work its own way and you won't be able to convince it otherwise.
One last important thing, if you're at the bottom of your back stack, you must allow the default Back action to happen (i.e. what WP8 wanted to do, take you out of the app). Otherwise you'll probably fail Certification and you're Doing It Wrong™.

Map.SetView() on Windows Phone 8

Is there a specific time in the page's lifecycle that the Map.SetView() function should be called? In our app we use this on various map objects and it seems to work randomly, sometimes perfectly and sometimes with no effect but also no exception.
example code:
RouteMap.SetView(LocationRectangle.CreateBoundingRectangle(DirectionCoordinates));
Where RouteMap is the mapping component and DirectionCoordinates contains the start/end coordinates for the map.
I can see that the bounding box is being created properly, but the map's positioning is not always being affected even loading the same data. If I add a break point it does seem to work, so I was assuming it had something to do with the map loading, but adding the SetView() functionality to the Loaded event has the same issue. We currently process the map information in the page Loaded event.
Update
I've been testing more and added events to what I could, I know for a fact that the MapLoaded event is being called before SetView. After SetView is called, it is working sometimes and not others. Neither ViewChanging or ViewChanged events are called.
This is obviously not the best solution, but there must be something that is not quite finished loading when the Loaded event is called that is preventing this from finishing.
I added a 100ms sleep to the Map_Loaded event and it has solved the problem I was having.
System.Threading.Thread.Sleep(500);
update
100ms isn't working for some people, you may want to play around with the numbers, 200, 500 etc. It's still a very short delay on the map's load time. I have contacted Microsoft about this and they have told me that they are looking into the issue and we will hopefully have some sort of response from them shortly.
update and edit
Use the following code instead to prevent UI hanging:
await Task.Delay(250);
I tackled this issue using ResolveCompleted event and boolean flag.
private void Map_ResolveCompleted(object sender, MapResolveCompletedEventArgs e)
{
if (this.zoomReq)
{
this.zoomReq = false;
if (this.locationList != null && this.locationList.Count > 0)
{
var viewRect = LocationRectangle.CreateBoundingRectangle(this.locationList);
this.Map.SetView(viewRect);
}
}
}
There is noticeable pause before map zooms but at least this seems to work all the time. The flag is needed because ResolveCompleted is fired every time the map moves.
I was both constructing a map layer (Microsoft.Phone.Maps.Controls.MapLayer) and setting the view (public void SetView(LocationRectangle boundingRectangle);) in an async method:
public async Task CreateMap()
{
map.Add(mapLayer);
map.SetView(locationRectangle);
}
I was doing some loading, that's why I used async.
This would only set the view once, the first time I navigated to the page.
The solution was to dispatch the set view call:
public async Task CreateMap()
{
map.Add(mapLayer);
Dispatcher.BeginInvoke(() =>
{
map.SetView(locationRectangle);
});
}
Hope that helps.
The Loaded event is the proper place for SetView(). You could try creating your rectangle in you OnNavigatedTo method. When I'm working with locations I always start my watcher in OnNavigatedTo and work with any map layers in _Loaded.
I worked myself some time at this problem. It didn't help to put most of the stuff to load into the constructor of the page. I tried to the trick with System.Threading.Thread.Sleep(500) but it took far beyond 500ms to take effect and this wasn't acceptable for me. For some people it helped to trigger an ZoomLevelChanged event and set the view in it. For myself I used a DispatcherTimer in which I used SetView() and fired an `ViewChanging´ event to stop the timer. If you use an animation the difference is pretty small.
I had this problem for MapAnimationKind.Linear but for MapAnimationKind.None it works without any problem
map.SetView(LocationRectangle.CreateBoundingRectangle(...), MapAnimationKind.None);
I had a very similar problem. Basically the setview of map would work the first time a page loaded (i.e. after all the data had finished loading) but if I left the page and came back and did not need to reload all the data, it did not work. While debugging, it seemed like I was setting the information for the map before it was finished loading.
So what I did to resolve the challenge was:
In the XAML - I added an event handler for the Loaded event of the map.
Example: Loaded="myMap_Loaded"
In the myMap_Loaded event, I simply called an async method to wait for the data to load then map
it.
Example:
private void myMap_Loaded(object sender, RoutedEventArgs e)
{
WaitAndLoadMap();
}
Coded the WaitAndLoadMap method to wait for the data to finish loading before loading the
map.
private async void WaitAndLoadMap()
{
//Check if the data is loaded and if it is not - loop.
while (!App.NearbyLocationsViewModel.IsLocationDataLoaded)
await Task.Delay(250);
//Load the map content and set the mapview.
}
It seems to be working. Hope this helps others.

Categories