I am doing a project that requires me to write to the log when different types of touches occur on screen. When I touch outside an open spinner drop down, it closes. I can't figure out how to detect this touch.
This code does not catch it, whereas it seems to catch all the other touches outside widgets:
mFullView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
touchCounter++;
Log.d(TAG, "Touch #" + touchCounter + ", no button touch registered.");
}
return false;
}
});
where mFullView is the parent RelativeLayout I have and is set like this:
mFullView = findViewById(R.id.full_view);
I also tried using onTouchEvent like this:
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
Log.d(TAG, "screen was touched outside of open spinner dropdown");
}
return super.onTouchEvent(event);
}
I have this code outside of onCreate and am not too confident about the placement or implementation.
I can't find anything about how to implement this, thanks for any help!
when clicked, the Spinner
will show either a Dialog
or a PopupWindow
. neither of these will be attached to the same Window
as your Activity
so you won't be able to intercept touch events from there.
maybe one could hack his way subclassing a Spinner
I found a way to do this, it is very much hacky.
1- we need to override public void onWindowFocusChanged(boolean hasFocus)
this method will be called when the Activity
's Window
loses its focus because the PopupWindow
's View had been attached to a new Window
on top of the Activity
's one
2- get a list of all Window
s root View
s, this answer has a very dirty hacky method to do it
3- one of these root View
s will be a PopupDecorView
, which is a private non-static class of PopupWindow
. we need to get the instance of PopupWindow
via reflection
4- once we have the instance of PopupWindow
, we need to get the OnTouchListener
, wrap it around one of our own and set it back to the PopupWindow
the overridden method looks like this:
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
new Handler().post(new Runnable() {
@Override
public void run() {
for(View view : getWindowManagerViews()){
try {
Class clazz = view.getClass();
Field outerField = clazz.getDeclaredField("this$0");
outerField.setAccessible(true);
Object popupWindow = outerField.get(view);
Field field = popupWindow.getClass().getDeclaredField("mTouchInterceptor");
field.setAccessible(true);
final View.OnTouchListener innerOnTouchListener = (View.OnTouchListener) field.get(popupWindow);
View.OnTouchListener outerOnTOuchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(MainActivity.class.getSimpleName(), String.format("popupwindow event %s at %s-%s", event.getAction(), event.getX(), event.getY()));
return innerOnTouchListener.onTouch(v, event);
}
};
field.set(popupWindow, outerOnTOuchListener);
}catch (Exception e){
//e.printStackTrace();
}
}
}
});
}
where getWindowManagerViews() is taken from the aforementioned answer, and it looks like this
public static List<View> getWindowManagerViews() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
// get the list from WindowManagerImpl.mViews
Class wmiClass = Class.forName("android.view.WindowManagerImpl");
Object wmiInstance = wmiClass.getMethod("getDefault").invoke(null);
return viewsFromWM(wmiClass, wmiInstance);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
// get the list from WindowManagerGlobal.mViews
Class wmgClass = Class.forName("android.view.WindowManagerGlobal");
Object wmgInstance = wmgClass.getMethod("getInstance").invoke(null);
return viewsFromWM(wmgClass, wmgInstance);
}
} catch (Exception e) {
e.printStackTrace();
}
return new ArrayList<View>();
}
private static List<View> viewsFromWM(Class wmClass, Object wmInstance) throws Exception {
Field viewsField = wmClass.getDeclaredField("mViews");
viewsField.setAccessible(true);
Object views = viewsField.get(wmInstance);
if (views instanceof List) {
return (List<View>) viewsField.get(wmInstance);
} else if (views instanceof View[]) {
return Arrays.asList((View[])viewsField.get(wmInstance));
}
return new ArrayList<View>();
}