I am implementing google maps in my application. My requirement is to display custom infoWindow when click on marker.
My custom infoWindow contains a listview and i need to perform some operation based on clicked item.
i am getting click event of whole infoWindow (using OnInfoWindowClickListener) but the problem is, i am not getting click event for list view inside infoWindow.
As per description here
Note: The info window that is drawn is not a live view. The view is rendered as an image (using View.draw(Canvas)) at the time it is returned. This means that any subsequent changes to the view will not be reflected by the info window on the map. To update the info window later (for example, after an image has loaded), call showInfoWindow(). Furthermore, the info window will not respect any of the interactivity typical for a normal view such as touch or gesture events. However you can listen to a generic click event on the whole info window as described in the section below.
So is there any way by which i can get click event of each clicked item inside custom infoWindow ?
Screenshot :
The best approach is probably by wrapping the map in layout. Where you will set listener to touch evetns and then propagate the events to saved instance of infowindows View.
See answer: https://stackoverflow.com/a/15040761/838424
This works for buttons, but from unknown reason it does not works for ListView (but clicks into ListView are propagated).
Update:
The clicks are not propagated to items because of function isAttachedToWindow returns false (in AbsListView.onTouchEvent). Which blocks processing the click event.
Workaround is to use code in wrapper on dispatchTouchEvent
int pos = myListView.pointToPosition((int)copyEv.getX(), (int)copyEv.getY());
my complete workaround:
if (copyEv.getX() >= 0 && copyEv.getY() >= 0
&& copyEv.getX() < infoWindow.getWidth()
&& copyEv.getY() < infoWindow.getHeight())
{
// event is inside infoWindow
final int actionMasked = ev.getActionMasked();
switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
int pos = vItems.pointToPosition((int)copyEv.getX(), (int)copyEv.getY());
if (pos >= 0) {
vItems.performItemClick(infoWindow, pos, 0);
ret = true;
}
break;
case MotionEvent.ACTION_UP: {
break;
}
}
}
Related
Samsung phones have this feature where you can hold your finger on an item then drag it down, i can multi select items quickly like in the gif
Is there any way i can implement this feature in my apps even if it's just for Samsung devices?
Example image
even if it's just for samsung devices
You can implement this feature on any device. I first found this feature in Mi devices.
It is somewhat complicated. But you can use libraries for this purpose like DragSelectRecyclerView.
Add following code to your build.gradle:
dependencies {
implementation 'com.github.MFlisar:DragSelectRecyclerView:0.3'
}
In your java file, add following code:
Create a touch listener.
mDragSelectTouchListener = new DragSelectTouchListener()
.withSelectListener(onDragSelectionListener)
// following is all optional
.withMaxScrollDistance(distance) // default: 16; defines the speed of the auto scrolling
.withTopOffset(toolbarHeight) // default: 0; set an offset for the touch region on top of the RecyclerView
.withBottomOffset(toolbarHeight) // default: 0; set an offset for the touch region on bottom of the RecyclerView
.withScrollAboveTopRegion(enabled) // default: true; enable auto scrolling, even if the finger is moved above the top region
.withScrollBelowTopRegion(enabled) // default: true; enable auto scrolling, even if the finger is moved below the top region
.withDebug(enabled);
Attach this touch listener to the RecyclerView.
recyclerView.addOnItemTouchListener(mDragSelectTouchListener);
On item long press, inform the listener to start the drag selection.
mDragSelectTouchListener.startDragSelection(position);
Use the DragSelectionProcessor, it implements the above mentioned interface and can be set up with 4 modes:
Simple: simply selects each item you go by and unselects on move back
ToggleAndUndo: toggles each items original state, reverts to the original state on move back
FirstItemDependent: toggles the first item and applies the same state to each item you go by and applies inverted state on move back
FirstItemDependentToggleAndUndo: toggles the item and applies the same state to each item you go by and reverts to the original state on move back
Provide a ISelectionHandler in it's constructor and implement these functions:
onDragSelectionListener = new DragSelectionProcessor(new DragSelectionProcessor.ISelectionHandler() {
#Override
public Set<Integer> getSelection() {
// return a set of all currently selected indizes
return selection;
}
#Override
public boolean isSelected(int index) {
// return the current selection state of the index
return selected;
}
#Override
public void updateSelection(int start, int end, boolean isSelected, boolean calledFromOnStart) {
// update your selection
// range is inclusive start/end positions
// and the processor has already converted all events according to it'smode
}
})
// pass in one of the 4 modes, simple mode is selected by default otherwise
.withMode(DragSelectionProcessor.Mode.FirstItemDependentToggleAndUndo);
You can see a demo activity here.
Is there any other library for the same purpose?
Yes, you can also use Drag Select Recycler View by afollestad. And you can see practical implementation for afollestad's Drag select Recycler View here.
I'm making a messaging app in Xamarin.Forms (Android and iOS).
Messages are being shown in a ListView.
Now I want to add a new feature. When I drag the message balloon to the side, it shows the message info.
The user holds the message, drags to right or left.
If he drags enough, a new page with the info shows
If he releases before this "threshold", the balloon bounces back to its place.
I tried implementing this with a PanGestureRecognizer (PGR in this context) on the balloon, but it worked partially.
Android
I can drag, release, release before the threshold, and it works! But if I drag vertically while dragging to horizontally, the ListView "steals" the pan gesture, the PGR "forgets" it and doesn't return any event. So the balloon just keeps there, a bit to the side.
-- Initial state.
-- Dragging the message to the right
-- I've already released the message, but it is still to the right.
This doesn't happen if I release the finger without dragging down.
It may look obvious: "Why don't you just make it go back when the finger is "forgotten"?"
Because when the problem happens, it doesn't generate another event. (And the ListView doesn't have the Scrolled event as the ScrollView)
iOS
When I try to use the PGR, the ListView doesn't work. But the side drag works. Looks like the PGRs are grabbing the pan gesture always first.
Is there any way to make the PGR and the ListView work simultaneously?
For iOS you can try adding:
Application.Current.On<iOS>().SetPanGestureRecognizerShouldRecognizeSimultaneously(true)
References: https://github.com/xamarin/Xamarin.Forms/issues/2299#issuecomment-399578363
https://learn.microsoft.com/ru-ru/xamarin/xamarin-forms/platform/ios/application-pan-gesture
For Android i made a workaround with timer:
private System.Threading.Timer timer;
private void PanGesture_PanUpdated(object sender, PanUpdatedEventArgs e)
{
var statusType = e.StatusType;
if (statusType != GestureStatus.Started && timer == null)
{
// after gesture was broken down, next event will be with wrong
// status, like the gesture is continues. So, reset status to Started,
// consider it is new gesture:
statusType = GestureStatus.Started;
}
var slidingView = yourView;
switch (statusType)
{
case GestureStatus.Started:
// do any initializations..
timer = new System.Threading.Timer(_ => {
finishTranslation(slidingView);
});
break;
case GestureStatus.Running:
{
// do any animations..
timer.Change(TimeSpan.FromMilliseconds(100), TimeSpan.Zero);
}
break;
}
}
private void finishTranslation(View slidingView)
{
timer = null;
// finish your animation
}
But problem with tap gesture still here((
I'm writing a cross-platform mobile app with Titanium (v5.0.4, sdk 5.1.2GA). The app displays a map with markers for points of interest close to current location. If the user moves the map to a new region, all previously displayed markers are removed and a new set of markers is calculated and displayed, according to the new location. The user can click on a marker, and have an infoWindow appear. Clicking the infoWindow opens a new window for further informations.
This works fine on IOS (with MapKit). However, on Android (with Google Maps), clicking on a marker displays the infoWindow, but also centers the map on the marker, which triggers 'regionchanged' event, which causes my app to remove markers and display a new set. Basically, the user can never click the infoWindow nor reach the new window for further informations.
What I'd like to do: prevent Google Maps from auto panning to a marker when clicking on it.
Alternatively, is there a way to make a distinction between a 'regionchanged' event fired after clicking a marker, and a 'regionchanged' event triggered by the user moving the map? This would allow me to react only on the latter.
I've found this (Impossible to prevent auto panning to a marker when clicking on it (google maps)) but this is ruby on rails. Something else I came accross (can't find back the link, though) involved overloading the 'click' event handler or using the disableAutoPan infoWindow option (Google Maps: How to prevent InfoWindow from shifting the map), but this were all for the Javascript API.
So, has someone any idea?
I recently had the same problem. Although it's been a long time since you asked the question, I think the solution is worth noting here.
Make the map view respond to both click and regionchanged events
<View id="mapView"
module="ti.map"
onClick="click"
onRegionchanged="regionChanged"
method="createView">
</View>
Use a flag variable that both event listeners have access to.
var clicked = 0;
click event sets the variable.
var click = function(e) {
clicked = 1;
...
}
Wrap the functionality of regionchanged event with _.defer(...).
var regionChanged = function(e) {
_.defer(function(){
... (regionchanged code goes here)
});
}
regionchanged code should not execute if the variable is set.
var regionChanged = function(e) {
_.defer(function(){
if (clicked == 0) {
... (regionchanged code goes here)
}
});
}
Unset the variable at the end of the regionchanged code.
var regionChanged = function(e) {
_.defer(function(){
if (clicked == 0) {
... (regionchanged code goes here)
}
clicked = 0;
});
}
Here defer somehow makes sure when both events are fired (it seems to) regionchanged event comes after the click event. I think that may not always be the case but my tests didn't fail me yet.
And I know I could use boolean for clicked :)
I am using the Drag & Drop API's introduced in 3.0 - however, I only want the drag shadow to follow the user's finger around while they are inside one particular area of the screen. If they drift outside that, I would like the shadow to go away and the drag event to end. I have already done some searching and seen that the API does not support updating the drag shadow after creation, which was my first plan, but is there any way to stop the DragEvent without the user actually lifting their finger?
There are two API calls for API 24+ that should be useful:
View#cancelDragAndDrop
Cancels an ongoing drag and drop operation.
A DragEvent object with DragEvent.getAction() value of DragEvent.ACTION_DRAG_ENDED and DragEvent.getResult() value of false will be sent to every View that received DragEvent.ACTION_DRAG_STARTED even if they are not currently visible.
This method can be called on any View in the same window as the View on which startDragAndDrop was called.
and View#updateDragShadow
Updates the drag shadow for the ongoing drag and drop operation.
Which you use and how you use them will depend upon your intentions. If you simply need the drag shadow to disappear when it is moved outside a defined area without ending the drag and drop operation so that the drag shadow will appear again if the user moves it back into the area of interest, updateDragShadow should suffice. If you want to cancel the drag and drop operation completely, use cancelDragAndDrop.
The question doesn't define what "inside one particular area of the screen" means. I assume that it is part of a view. In the drag listener, you can use the drag view coordinates to determine if the drag view is inside or outside the area. The drag view coordinates can be found in DragEvent. Another option would be to define an empty view that covers the area of the screen and take action when the drag view exits that view. (You will need to set a drag and drop listener on that empty view as well.)
The above does not apply to API 23 and below and the solution is not immediately obvious for these lower APIs. The drag shadow has an internal representation that is not accessible through the API as far as I can tell, although you might be able to do something with Reflection. View.java has internal information about the drag and drop operation.
The question is old but since there is no answer; I want to bring attention that this is now the default behaviour according to the official documentation and API here
This behavior can be achieved using the following quoted code:
val imageView = ImageView(this)
// Set the drag event listener for the View.
imageView.setOnDragListener { v, e ->
// Handles each of the expected events.
when (e.action) {
DragEvent.ACTION_DRAG_STARTED -> {
// Determines if this View can accept the dragged data.
if (e.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
// As an example of what your application might do, applies a blue color tint
// to the View to indicate that it can accept data.
(v as? ImageView)?.setColorFilter(Color.BLUE)
// Invalidate the view to force a redraw in the new tint.
v.invalidate()
// Returns true to indicate that the View can accept the dragged data.
true
} else {
// Returns false to indicate that, during the current drag and drop operation,
// this View will not receive events again until ACTION_DRAG_ENDED is sent.
false
}
}
DragEvent.ACTION_DRAG_ENTERED -> {
// Applies a green tint to the View.
(v as? ImageView)?.setColorFilter(Color.GREEN)
// Invalidates the view to force a redraw in the new tint.
v.invalidate()
// Returns true; the value is ignored.
true
}
DragEvent.ACTION_DRAG_LOCATION ->
// Ignore the event.
true
DragEvent.ACTION_DRAG_EXITED -> {
// Resets the color tint to blue.
(v as? ImageView)?.setColorFilter(Color.BLUE)
// Invalidates the view to force a redraw in the new tint.
v.invalidate()
// Returns true; the value is ignored.
true
}
DragEvent.ACTION_DROP -> {
// Gets the item containing the dragged data.
val item: ClipData.Item = e.clipData.getItemAt(0)
// Gets the text data from the item.
val dragData = item.text
// Displays a message containing the dragged data.
Toast.makeText(this, "Dragged data is $dragData", Toast.LENGTH_LONG).show()
// Turns off any color tints.
(v as? ImageView)?.clearColorFilter()
// Invalidates the view to force a redraw.
v.invalidate()
// Returns true. DragEvent.getResult() will return true.
true
}
DragEvent.ACTION_DRAG_ENDED -> {
// Turns off any color tinting.
(v as? ImageView)?.clearColorFilter()
// Invalidates the view to force a redraw.
v.invalidate()
// Does a getResult(), and displays what happened.
when(e.result) {
true ->
Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG)
else ->
Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG)
}.show()
// Returns true; the value is ignored.
true
}
else -> {
// An unknown action type was received.
Log.e("DragDrop Example", "Unknown action type received by View.OnDragListener.")
false
}
}
}
I have one activity, which contains header (title bar). This header also contains the image of Sync feature of my app.
As per requirement of client, I want that when the user tap on this button, the animation of this image (button) starts, which is rotating animation (I've achieved this animation). But while this animation is being performed, I want to freeze the whole screen so the user can not tap on other views while sync is in progress.
Till now I've used the following method but with no success :
private boolean stopUserInteractions = false;
public boolean dispatchTouchEvent(int i) {
View v = imgSync;
if (v.getId() == R.list.imgSync) {
System.out.println("UI Blocked...");
return false;
}
else {
return super.dispatchTouchEvent(ev);
}
return false;
}
Can anyone guide me for this? Any trick/snippet or any other hint.
EDIT : I dont want to use ProgressDialog while this sync process is being happening.
Have an OnTouchListener class within your activity, or let your Activity implement OnTouchListener. All children of the activity's layout should set a single object of this if they want to listen touch events. Within listener, check whether you should process the click or not.