Detect touch event outside of layout - android

I'm using an opensource numberpicker I found somewhere (credit to Jeffrey F. Cole) but I just found a bug.
The numberpicker has a handler to increase the number faster when you touch the button
long`private Handler repeatUpdateHandler = new Handler();
`
class RepetetiveUpdater implements Runnable {
public void run() {
if (autoIncrement) {
increment();
repeatUpdateHandler.postDelayed(new RepetetiveUpdater(),
REPEAT_DELAY);
} else if (autoDecrement) {
decrement();
repeatUpdateHandler.postDelayed(new RepetetiveUpdater(),
REPEAT_DELAY);
}
}
}
.....
public class NumberPicker extends LinearLayout {
.....
// Auto increment for a long click
increment.setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View arg0) {
autoIncrement = true;
repeatUpdateHandler.post(new RepetetiveUpdater());
return false;
}
});
// When the button is released, if we're auto incrementing, stop
increment.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP && autoIncrement) {
autoIncrement = false;
}
return false;
}
});
The problem is when you longclick the button the counter starts to increase, but when you hold your finger down and drag your finger across the screen the counter keeps adding up, even when you lift your finger.
So how can I detect that the finger gets out of my numberpicker layout and stop the counter?
Thx :)

I'm not so sure that's a bug and I'm not even sure it's coming from changes that this number picker made on top of the numberpicker from the API itself. I'm assumings that the buttons that handles incrementing and decrementing the pickers are set up to keep going until an ACTION_UP MotionEvent is received, but this might be over-simplifying it.
EDIT:
I've tested this on stock Android 2.3.3 and this is precisely the result.
EDIT:
Based on your clarification in the comments, this does sound like a pretty bad bug. Looks like what you need to do is have the Handler removes the callbacks to that runnable in ACTION_UP. Can you link me to the project so I can try to submit a patch?
EDIT
The NumberPicker you provided wasn't using Handlers correctly, IMO. Instead of keeping a reference to the same Handler so that callbacks could later be removed, it was created a new one everytime it posted. I've made some changes and fixed the issues here: https://gist.github.com/3657989

Related

Android avoid calling single tap twice for double tap, without using onSingleTapConfirmed

My app has a View which is clickable, and then when this View is clicked, a handleClickEvent() should be called. I tried setting an OnClickListener to the view, but then when user is double tapping the view, the handleClickEvent() gets called twice. I don't want it to be called twice during double tap. I.e. I want handleClickEvent() be called ONCE for both single click and double tap.
I did some research and found out this question: Android detecting double tap without single tap first I tried what's suggested in the answer, i.e. implementing a GestureDetector with onSingleTapConfirmed() method. That worked, however I notice that there is a noticeable delay on the response time, comparing to using OnClickListener. I assume it is because onSingleTapConfirmed() is called only after the system confirms that this is a single tap by waiting for some certain time period.
Is there anyway to achieve this without having a noticeable delay? Another option I can think of is to have a lastClickedTimeStamp member variable, compare the current time when clicked with lastClickedTimeStamp, and if it is within some threshold don't do anything.
I am wondering if there are any other options?
Thank you!
The way I would do it is set a state variable as a long and call it timeOfLastTouch and set it to 0. Then in each OnClickListener function put this code in their
long currentTime = System.currentTimeMillis();
if (timeOfLastTouch + 100 < currentTime) {
// code
timeOfLastTouch = System.currentTimeMillis();
}
You can try using GestureDetector.SimpleOnGestureListener and combine it with onTouchEvent.
private GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Log.d("SingleTap", " is detected");
return true;
}
#Override
public boolean onDoubleTap(MotionEvent e) {
Log.d("DoubleTap", " is detected");
return true;
}
});
then, remove onClickListener and set touch event on that view
yourView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
});
You can refer to Detecting Common Gestures

Hide view every 5 seconds with postDelayed

PROBLEM
I have a horizontalListView that is showing user all the images loaded to ViewPager. Its situated on the bottom of the screen.
What I want to do is for horizontalListView to hide when its not being used for more then 5 sec.
How it should work:
User taps screen horizontalListView appears
User scrolls, selects do stuff on horizontalListView and its blocking threads to be fired
After 5 sec of doing nothing on horizontalListView, it disappears
How its working right now:
User taps screen horizontalListView appears
User scrolls, selects ect. and postDelayed is getting fired making my horizontalListView dissappear.
CODE
HorizontalListView and Runnable
horizontalListView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
hideViewHandler.removeCallbacks(mRunnable);
hideViewHandler.postDelayed(mRunnable,5*1000);
return true;
}
});
mRunnable = new StoppableRunnable() {
#Override
public void stoppableRun() {
hideAnimation();
}
};
TapListener that is set on Image responsible for showing horizontalListView
private class TapGestureListener extends GestureDetector.SimpleOnGestureListener {
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
hListView.clearAnimation();
if (((ViewGroup.MarginLayoutParams) hListView.getLayoutParams()).bottomMargin < 0) {
expandAnimation();
hideViewHandler.postDelayed(mRunnable,5*1000);
} else {
hideAnimation();
}
return true;
}
}
You have to check inside the listener for the MotionEvent.ACTION_UP or MotionEvent.ACTION_CANCEL to only trigger the runnable after them.
Your current code is posting the runnable on any event, but you want to do it only when the user has stopped using the view.
BTW: If you return true from the TouchListener it means that you have consumed the event and the event chain will stop. Most likely the ScrollView will not even scroll, since the event will not be propagated to it.
You can do something like this..
yourScreenLayout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (horizontalListView.getVisibility() == View.INVISIBLE) {
horizontalListView.setVisibility(View.VISIBLE);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
if (horizontalListView.getVisibility() == View.VISIBLE)
horizontalListView.setVisibility(View.INVISIBLE);
}
}, 5000);
} else {
horizontalListView.setVisibility(View.INVISIBLE);
}
}
});
I think the source of you problem might be calling removeCallbacks before postDelayed.
The thread itself is fired after u clear the queue.
I have managed to fix the problem myself.
FIX
The main reason why my onTouchListener did not work as intended was that it had not been triggered at all. OnClickListener inside horizontalListView adapter was consuming the trigger.
Now I am passing the info that the click occurred inside onClickListener and do all the things in different method.

Android Spinner OnLongClickListener or OnItemLongClickListener

Anyone have a work around to get this set up? I have a spinner in my action bar where I'd like to capture a long click event, either OnLongClickListener, or for the individual item OnItemLongClickListener - so the user can edit the value.
I've read that spinner doesn't support long clicks, but I was hoping that something might have changed/someone might have a workaround.
It's the ideal solution for editing the string - there's not enough space in the action bar to put a dedicated button for editing, and my users intuitively try to long press to edit.
Here are some related questions (although quite old now):
Android spinner item long click/touch,
How can I use spinner setOnItemLongClickListener
You should be able to "simulate" a long click with the following code:
final Handler actionHandler = new Handler();
final Runnable runnable = new Runnable() {
#Override
public void run() {
Toast.makeText(Activity.this, "Long click performed", Toast.LENGTH_SHORT).show();
}
};
spinner.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
actionHandler.postDelayed(runnable, 1000);
} else if(event.getAction() == MotionEvent.ACTION_UP){
actionHandler.removeCallbacks(runnable);
}
return false;
}
});
Yo can play around with the time in order to get a more "real" feeling, but that's pretty it i believe.
I hope it helps

onTouchEvent() will not be triggered if setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) is invoked

I call
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
when my app starts to make my app able to display the full screen.
I want my app's UI to pop up when screen is touched, but Activity.onTouchEvent() is not triggered until the screen is touched a second time. At first touch, only the Virtual Keys are shown.
So, I have to trigger my app's UI to pop up on
public void onSystemUiVisibilityChange(int visibility) {
if (visibility == View.SYSTEM_UI_FLAG_VISIBLE) {
// show my APP UI
}
}
but onSystemUiVisibilityChange with View.SYSTEM_UI_FLAG_VISIBLE will be invoked NOT once per touch (3 times on my Galaxy Nexus) by system, especially if the user touches the screen very fast/often.
project lib 4.0 or 4.03.
Samsung galaxy(9250) with 4.03.
Android 4.4 (API Level 19) introduces a new SYSTEM_UI_FLAG_IMMERSIVE flag for setSystemUiVisibility() that lets your app go truly "full screen." This flag, when combined with the SYSTEM_UI_FLAG_HIDE_NAVIGATION and SYSTEM_UI_FLAG_FULLSCREEN flags, hides the navigation and status bars and lets your app capture all touch events on the screen.
This did work for me:
setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() {
#Override
public void onSystemUiVisibilityChange(int visibility) {
if ((visibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
// show my app UI
}
}
});
What I've done is first imported android.view.GestureDetector so I can use it to detect gestures. Android has a number of default gestures that are automatically detected in the GestureDector class. Most of this info is found here, but below is code in a form that I've used in an actual project that works.
First I've made an anonymous class in my Activity (this can be nested wherever, but I tend to make my anonymous classes at the bottom, right before the closing bracket). NOTE: You can also implement OnGestureListener as part of your class, also.
The code below is for using gesture detection to give a simple hide/show.
I've declared and defined my action bar (my UI, which is initially hidden) as an instance variable, so I can access it here, and wherever else, but you can substitute it for a getActionBar().show() and getActionBar().hide() in the case you don't want to declare it as an instance variable. Substitute your UI in the place of the actionBar here:
public class Example extends ActionBarActivity {
// declared in onCreate() method
private android.support.v7.app.ActionBar actionBar;
private GestureDetectorCompat mDetector;
private YourView view1;
private YourView view2;
private YourView view3;
private YourView view4;
// some other code
class GestureListener extends GestureDetector.SimpleOnGestureListener {
private static final String DEBUG_TAG = "Gestures in Example Class";
#Override
public boolean onDoubleTap(MotionEvent event) {
Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString());
// if there is a double tap, show the action bar
actionBar.show();
return true;
}
#Override
public boolean onSingleTapConfirmed(MotionEvent event) {
Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString());
// if the tap is below the action bar, hide the action bar
if (event.getRawY() > getResources().getDimension(R.dimen.abc_action_bar_default_height)) {
actionBar.hide();
return true;
}
return false;
}
#Override
public boolean onDown(MotionEvent event) {
return true;
}
} // end-of-Example Class
Then in my onCreate() I've declared my GestureDetector and also (optionally) set my GestureListeners:
private GestureDetectorCompat mDetector;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// some code here
mDetector = new GestureDetectorCompat(this, new GestureListener());
// this code is for more advanced view logic not needed for a basic set-up
//setGestureListeners();
} // end-of-method onCreate()
Then in order to actually send gestures to be processed we provide the instructions for doing that, there are two ways I know about, first the simplest:
/**
* This method recognizes a touchEvent and passes it to your custom GestureListener
* class.
*/
#Override
public boolean onTouchEvent(MotionEvent event){
this.mDetector.onTouchEvent(event);
return super.onTouchEvent(event);
}
The second way is more complex, but if you want to only recognize touch events on certain Views in your layout as in the case where you have overlapping views and can only access the top View, you can create a custom class to pass the event around or up:
class MyOnTouchListener implements View.OnTouchListener {
public boolean onTouch(View v, MotionEvent event) {
if (v.equals(view4)) {
return mDetector.onTouchEvent(event);
} else return false;
}
} // end-of-class MyOnTouchListener
and then use it here:
public void setGestureListeners() {
/* when we return false for any of these onTouch methods
* it means that the the touchEvent is passed onto the next View.
* The order in which touchEvents are sent to are in the order they
* are declared.
*/
view1.setOnTouchListener(new MyOnTouchListener());
view2.setOnTouchListener(new MyOnTouchListener());
view3.setOnTouchListener(new MyOnTouchListener());
view4.setOnTouchListener(new MyOnTouchListener());
} // end-of-method setGestureListeners()
In my setGestureListeners method, I gave them all the same set of commands, that essentially only recognizes touchEvents on view4. Otherwise, it just passes the touchEvent to the next view.
This is code using AppCompat, but if you are not building for older versions, you can use the regular GestureDetector and ActionBar.
Have you tried adding code to only show your UI when the state has changed? You have to maintain the last known visibility and only show your UI when you first come into being visible:
int mLastSystemUiVis;
#Override
public void onSystemUiVisibilityChange(int visibility) {
int diff = mLastSystemUiVis ^ visibility;
mLastSystemUiVis = visibility;
if ((diff&SYSTEM_UI_FLAG_VISIBLE) != 0
&& (visibility&SYSTEM_UI_FLAG_VISIBLE) == 0) {
// DISPLAY YOUR UI
}
}
Code sample adopted from the Android docs
The method Activity.onTouchEvent() gets called at the end of the responder chain (meaning after all other views have had a chance to consume the event). If you tap on a view that is interested in touch (i.e. a Button or EditText) there's a good chance your Activity will never see that event.
If you want to have access to touches before they every get dispatched to your view(s), override Activity.dispatchTouchEvent() instead, which is the method called at the beginning of the event chain:
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
//Check the event and do magic here, such as...
if (event.getAction() == MotionEvent.ACTION_DOWN) {
}
//Be careful not to override the return unless necessary
return super.dispatchTouchEvent(event);
}
Beware not to override the return value of this method unless you purposefully want to steal touches from the rest of the views, an unnecessary return true; in this spot will break other touch handling.
I got this problem too, and I found this http://developer.android.com/reference/android/view/View.html#SYSTEM_UI_FLAG_HIDE_NAVIGATION
So, no way to help. Even the android system packaged Gallery app used SYSTEM_UI_FLAG_LOW_PROFILE instead of SYSTEM_UI_FLAG_HIDE_NAVIGATION in photo page view. This is at least what we can do.
I had a very similar issue with trying to update the UI from an onTouchEvent() requiring two touches to work, and I tried a bunch of elaborate stuff before finally getting it to work on the first click.
In my case, I was showing a previously hidden item, then getting its height, then moving a button down by that height. The problem I ran into is that the height was showing as 0 until after the first touch event finished. I was able to solve this by calling show() during ACTION_UP for the onTouchEvent() instead of its ACTION_DOWN. Maybe it'd work if you did something similar?
Try to use:
getWindow().getDecorView().setSystemUiVisibility(View.GONE);
instead:
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
After that you can use normal activity in fullscreen and if you want nav keys you need to swipe from bottom to up. Working for me at Galaxy Tab 2 with android 4.1.2

Android :: OnTouchListener && OnClickListener combination issue

Problem description:
I have a TextView on a RelativeLayout and I want to color it red when the user touches it, and go on another page when he clicks on it.
So I tried to set an OnClickListener to do the click, and an OnTouchListener to implement the touch function (MotionEvent.ACTION_DOWN) but this combination doesn't work, because OnTouchListener makes OnClickListener non-functional (don't know why).
On forums people say that we can implement the OnClick by the OnTouch MotionEvent.ACTION_UP, but this one can be triggered out of my TextView layout (the TextView gonna be clicked if you press it and drag your finger out of him to release) and this is not the desired behavior because I want:
click = press + release on the TextView.
Can someone give me a solution for this please?
you may call View.performClick() when action_up. Hope it helps.
your_txtView.setOnClickListener(new TextView.OnClickListener(){
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
your_txtView.setOnTouchListener(new TextView.OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
if (MotionEvent.ACTION_DOWN == event.getAction()) {
} else if (MotionEvent.ACTION_UP == event.getAction()) {
v.performClick();
}
return true;
}
});
Adel, is the problem with the first click, or you don't get any click at all?
There is this issue if you have multiple clickable layout you don't get any click events for the first. That's because it makes it first selected and then you get the click event, try the below code.
private class CustomTouchListener implements OnTouchListener {
#Override
public boolean onTouch(View v, MotionEvent event) {
TextView tv = (TextView) v.findViewById(R.id.single_line_text);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
tv.setTextColor(COLOR_WHEN_PRESSED);
} else if (event.getAction() == MotionEvent.ACTION_UP) {
tv.setTextColor(COLOR_WHEN_RELEASED);
// Action of click goes here
} else if (event.getAction() == MotionEvent.ACTION_CANCEL) {
tv.setTextColor(COLOR_WHEN_RELEASED);
// To handle release outside the layout region
}
return false;
}
}
This is working in my current implementation if you set the touch listener for your layout.
You also need to set below on your layout
android:focusable="true"
android:focusableInTouchMode="true"
android:clickable="true"
Hope it helps!!!
EDIT: Additionally, there should be a flag in both DOWN and UP. Set it in DOWN and check if its set in UP. This will avoid a bug where user might tap anywhere in the screen and then hover on your textview and release it.
Had the same problem. Solved it by returning false from ACTION_MOVE. I've been fighting with it for few hours, trying various things, but seems like i've kept overlooking this little issue... And now it makes sense. When you return true from onTouch, futher processing is stopped, so that OnClickListener is not aware of any movements and triggers onClick even after pointer have moved outside of view.

Categories

Resources