I'm using google_maps_flutter and wish to perform an action when the user performs a gesture on the map, whether it be zoom/tilt/move/rotate. However I'm unable to use the onCameraMoveStarted property in GoogleMap class as it also recognizes non-gesture user action caused as well as programmed animations (which my app utilizes), with no way (as far as I know, please correct me otherwise), to differentiate between them.
Thus I thought of using the flutter widget GestureDetector, wrapping the map inside it so that I would be able to change variables based on the gestures detected by GestureDetector to cause changes indirectly in the map.
No problems initially, it acts as a transparent layer and the map can be moved/tilted/rotated/zoomed normally. However, upon adding a function to execute through onPanStart, onPanUpdate, or onPanEnd all make the map unable to be interacted with through gestures. I suppose it is all being captured by the GestureDetector, but is there no way I can do said extra task asynchronously while passing the gesture along to the child anyway?
Here's the structure, btw:
build(context) {
return Scaffold(
body: GestureDetector(
behavior: HitTestBehavior.deferToChild,
onPanStart: {...}
child:
GoogleMap(...),
),
...
);
}
Thanks in advance, any help much appreciated.
I've found a solution, which might work for you.
class Test extends DragGestureRecognizer {
Function _test;
Test(this._test);
#override
void resolve(GestureDisposition disposition) {
super.resolve(disposition);
this._test();
}
}
...
return GoogleMap(
...
gestureRecognizers: Set()
..add(Factory<DragGestureRecognizer>(() => Test(() {
if (_focusEnabled) {
setState(() {
_focusEnabled = false;
});
}
})),
);
This runs your function on every interaction with the map.
But i did'nt find a way to differentiate between the events.
This may help some, saw this somewhere and forgot about it. Wrap the map in a listener:
Listener(
onPointerDown: (e) {
print("USER IS DRAGGING");
print(e);
},
Related
I think the title sumes it up pretty well, how do I run some peice of code when ever a page is loaded in flutter?
Or how to set the scroll ofset of a listview to be a certain % of the page using : "MediaQuery.of(context).size.height/3", the reason this dosn't work is because I can't use the (context) in the listview, because there's no context yet.
But if it's not posible to do the listview thing, then I would also be able to do with some code that runs when ever the page loades :)
WidgetsBinding offers a way to add a one time post frame callback.
WidgetsBinding.instance.addPostFrameCallback((_) {
// executes after build
})
As this callback will only be called a single time, you will want to add it every time you build:
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => afterBuild);
return Container(); // widget tree
}
void afterBuild() {
// executes after build is done
}
Isn't it possible to use DidChangeDependencies() function to solve your problem? Since it's called immediately after initState() is done :)
#override
void didChangeDependencies() {
super.didChangeDependencies();
//Code here
}
All of my property values require me to click them in order to see them. How can I fix this?
The object I'm trying to view is this Query Object. It seems to do this with most Arcgis objects I'm trying to view.
You can try putting it through JSON stringify which will call all the getters:
console.log(JSON.parse(JSON.stringify(myObj)));
The issue is, calling a getter can have side effects e.g.
class Dog {
get paws() {
console.log('paws!'); //side effect
this.paws++; // side effect
if(this.paws > 4) {
throw Error('oh no'); // side effect
}
return this.paws;
}
}
Every getter can alter the state of the app or break it while you are trying to debug it. That's why DevTools ask you to invoke these getters manually. Even if your getter returns a static value, DevTools have no way of knowing that.
If you really want to invoke all getters and have a quick overview of the values, you can create yourself a helper:
class Dog {
get _debug() {
return {
paws: this.paws,
//...
};
}
}
This will add a new getter that will invoke all other getters for you and give you their values with a single click (instead of n clicks).
You can work-around this, by running a script to auto-invoke the getters. To do this:
Open DevTools in a separate window.
Press CTRL+SHIFT+I
Switch to the console tab (of the devtools inspecting devtools)
Evaluate the below, and close the window
setInterval(() => {
[...document.querySelectorAll(".source-code")]
.map(s => [
...(s.shadowRoot?.querySelectorAll(
".object-value-calculate-value-button"
) || [])
])
.flat()
.forEach(s => s.click());
}, 500);
This will search for the invoke property button every 500ms. and click it for you.
I would like load two models and hide some nodes directly after the models are loaded.
I add event listeners to GEOMETRY_LOADED_EVENT and OBJECT_TREE_CREATED_EVENT to see when the loading is finished. Because the loading is done asynchroniously, either one of the models can be loaded last.
So I set the model I want to hide the nodes from active with. And after that hide the nodes I want to hide.
viewer.modelstructure.setModel(instanceTree);
This works some of the time, but it does not seem to work all the time. Is there some other event I should listen to in order to know the loading is finished? Or is there some way or event to make sure setModel(instanceTree) has finished?
Is your question about identifying the model which events are fired for ?
In the latest versions of the viewer API, viewer.loadModel takes a callback that returns the model instance being loaded, this should let you know the model before those 2 other events are fired:
Viewer3D.prototype.loadModel = function(url, options, onSuccessCallback, onErrorCallback, onWorkerStart) {
// ...
function onSuccess(model) {
self.model = model;
self.impl.addModel(self.model);
if (self.loadSpinner)
self.loadSpinner.style.display = "None";
if (self.model.is2d())
self.activateLayerState("Initial");
registerDimensionSpecificHotkeys();
if (onSuccessCallback) {
onSuccessCallback(self.model);
}
}
// ...
}
And in the event handlers the argument also contain the model instance for which the event is fired:
this.viewer.addEventListener(
Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, (args) => {
console.log(args)
})
this.viewer.addEventListener(
Autodesk.Viewing.GEOMETRY_LOADED_EVENT, (args) => {
console.log(args)
})
Let me know if I'm missing something and if it doesn't work please provide some code that illustrates your workflow.
Thanks
In the N+1 video #34 (Progress), there was an example of using CreateBindingSet() for the Android version, which is not typical. But the narrator also mentioned briefly that the same can be done on the Windows platform.
As much as I tried, however, I am unable to get a View's property to be bound to its ModelView on the Windows Phone. I always get a NullReferenceException.
The closest I came was the code below, including suggestions from ReSharper. Here's my FirstView.xaml.cs:
using Cirrious.MvvmCross.Binding.BindingContext;
using Whatever.ViewModels;
namespace Whatever {
// inheriting from IMvxBindingContextOwner was suggested by ReSharper also
public partial class FirstView : BaseView, IMvxBindingContextOwner {
public class MyBindableMediaElement
{
private string _theMediaSource = "whatever";
public string TheMediaSource
{
get
{
return _theMediaSource;
}
set
{
_theMediaSource = value;
}
}
}
public FirstView()
{
InitializeComponent();
_mediaElement = new MyBindableMediaElement(this.theMediaElement);
var set = this.CreateBindingSet<FirstView, FirstViewModel>();
// the corresponding view model has a .SongToPlay property with get/set defined
set.Bind(_mediaElement).For(v => v.TheMediaSource).To(vm => vm.SongToPlay);
set.Apply();
}
public IMvxBindingContext BindingContext { get; set; } // this was suggested by ReSharper
}
I get a NullReferenceException in MvxBaseFluentBindingDescription.cs as soon as the view is created. The exact location is below:
protected static string TargetPropertyName(Expression<Func<TTarget, object>> targetPropertyPath)
{
var parser = MvxBindingSingletonCache.Instance.PropertyExpressionParser; // <----- exception here**
var targetPropertyName = parser.Parse(targetPropertyPath).Print();
return targetPropertyName;
}
I have not seen a working example of creating a binding set on a Windows Phone emulator. Has anyone gotten this to work? Thanks.
I can confirm that the narrator said that remark a little too flippantly without actually thinking about how he might do it...
However, with a little effort, you definitely can get the CreateBindingSet to work in Windows if you want to.
Before you start, do consider some alternatives - in particular, I suspect most people will use either Windows DependencyProperty binding or some hand-crafted code-behind with a PropertyChanged event subscription.
If you do want to add CreateBindingSet code to a Windows project then:
Add the Binding and BindingEx assemblies to your Ui project - the easiest way to do this is using nuget to add the BindingEx package.
In your Setup class, override InitializeLastChance and use this opportunity to create a MvxWindowsBindingBuilder instance and to call DoRegistration on that builder. Both these first two steps are covered in the n=35 Tibet binding video - and it's this second step that will initialise the binding framework and help you get past your current 'NullReferenceException' (for the code, see BindMe.Store/Setup.cs)
In your view, you'll need to implement the IMvxBindingContextOwner interface and you'll need to ensure the binding context gets created. You should be able to do this as simply as BindingContext = new MvxBindingContext();
In your view, you'll need to make sure the binding context is given the same DataContext (view model) as the windows DataContext. For a Phone Page, the easiest way to do this is probably just to add BindingContext.DataContext = this.ViewModel; to the end of your phone page's OnNavigatedTo method. Both steps 3 and 4 could go in your BaseView if you intend to use Mvx Binding in other classes too.
With this done, you should be able to use the CreateBindingSet code - although do make sure that all binding is done after the new MvxBindingContext() has been created.
I've not got a windows machine with me right now so I'm afraid this answer code comes untested - please do post again if it does or doesn't work.
I can confirm it works almost perfectly; the only problem is, there are no defaults register, so one has to do the full binding like:
set.Bind(PageText).For(c => c.Text).To(vm => vm.Contents.PageText).OneTime();
to fix this, instead of registering MvxWindowsBindingBuilder, I am registering the following class. Note: I have just created this class, and needs testing.
public class UpdatedMvxWindowsBindingBuilder : MvxWindowsBindingBuilder
{
protected override void FillDefaultBindingNames(IMvxBindingNameRegistry registry)
{
base.FillDefaultBindingNames(registry);
registry.AddOrOverwrite(typeof(Button), "Command");
registry.AddOrOverwrite(typeof(HyperlinkButton), "Command");
//registry.AddOrOverwrite(typeof(UIBarButtonItem), "Clicked");
//registry.AddOrOverwrite(typeof(UISearchBar), "Text");
//registry.AddOrOverwrite(typeof(UITextField), "Text");
registry.AddOrOverwrite(typeof(TextBlock), "Text");
//registry.AddOrOverwrite(typeof(UILabel), "Text");
//registry.AddOrOverwrite(typeof(MvxCollectionViewSource), "ItemsSource");
//registry.AddOrOverwrite(typeof(MvxTableViewSource), "ItemsSource");
//registry.AddOrOverwrite(typeof(MvxImageView), "ImageUrl");
//registry.AddOrOverwrite(typeof(UIImageView), "Image");
//registry.AddOrOverwrite(typeof(UIDatePicker), "Date");
//registry.AddOrOverwrite(typeof(UISlider), "Value");
//registry.AddOrOverwrite(typeof(UISwitch), "On");
//registry.AddOrOverwrite(typeof(UIProgressView), "Progress");
//registry.AddOrOverwrite(typeof(IMvxImageHelper<UIImage>), "ImageUrl");
//registry.AddOrOverwrite(typeof(MvxImageViewLoader), "ImageUrl");
//if (_fillBindingNamesAction != null)
// _fillBindingNamesAction(registry);
}
}
This is a skeleton from Touch binding, and so far I have only updated three controls to test out (Button, HyperButton and TextBlock)
I've been reading the Google Maps API docs to see if it's possible to tell the difference between a system event vs. a user one?
For example, the zoom_changed event gets triggered when you use methods like setZoom, fitBounds, etc, which in my implementation is unsavoury, as I just want to know when the user actually changes the zoom level.
Unfortunately, the click event is only fired on the map itself, not the controls, so you can't rely on that method to help detect the users input.
Ideas?
Although I haven't been able to solve this using the Google Maps API, I have created a workaround which involves me calling this method before I change the map zoom or positioning without user interaction:
MapGraph.prototype.systemMove = function() {
var _this = this;
this.isMoving = true;
return setTimeout(function() {
return _this.isMoving = false;
}, 500);
};
And my event bindings look like this:
google.maps.event.addListener(this.map, 'dragend', function(event) {
if (!_this.isMoving) return _this.mapChanged();
});
Not perfect, but it does work.
Would love to see any other implementations though.
You may also consider an alternate solution I proposed in this Stack Overflow answer, which does not rely on mouse events to recognize user-initiated changes.
Instead of trying to recognize user events, add a flag to the map whenever a programmatic change is initiated with setZoom or fitBounds.
map.systemChange = true
map.setZoom()
Then check for (and reset) the flag in the event listener.
map.addListener('zoom_changed', function () {
if (map.systemChange) {
map.systemChange = false // Reset the flag for a system-initiated event
} else {
// Handle the user-initiated event
}
});