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((
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 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 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.
I'm having a bit of a problem with this. I wanted to make a push button. However, I don't know how to use the Button class with OpenGL. I am not using the "R" class within Java instead I am using the old "assets" folder for compatibility.
I have it setup to find if you have touched the button and on "touch up" load the next screen. The flaw in this is that you can touch the screen and then drag your finger over to the button and then lift your finger. The next screen will load, because it has registered the touch up event at that position.
The easiest way to fix this would be to use the Button class, but how do I use it (especially because I won't be able to use findViewById)?
This is the code I was using but when onTouchUp check for a collision touchDown has magically changed to be the same as TouchUp?
private void onTouchDown(Vector2 point)
{
if (test.justUp)
{
test.setTouchDown(point);
test.justUp = false;
}
}
private void onTouchUp(Vector2 point)
{
test.setTouchUp(point);
test.justUp = true;
if(OverlapTester.pointInRectangle(test.bounds, test.touchUp) &&
OverlapTester.pointInRectangle(test.bounds, test.touchDown))
{
game.setScreen(new LevelSelect(game));
return;
}
}
When creating your own button class, register the "touch down" position and the "touch up" position. If they've both been registered inside your button graphic area, the button is pressed.
I am new to jQuery Mobile 1.4.5, when I try to implement changePage() triggered by swipeleft, it works perfect with following code:
$("#mypage").on("swipeleft", function(e){
log("swipeleft");
if(event.handled !== true) // This will prevent event triggering more then once
{
$.mobile.changePage('#mypage2', {transition: "slide"});
event.handled = true;
}
return false;
});
However, once the changePage() event is triggered, it can't go back until the next page is completely showed. I was wondering is it possible to do like other "swipe to change page" function e.g. Facebook slide to show friend list. I can slide to show part of target page, and the edge of page is just followed where my finger is. Instead of changing whole page
Thanks for your help.