Swipe/Fling tab-changing in conjunction with ScrollView? - android

The best I could find on this particular issue (although I do not use a Gallery): ScrollView and Gallery interfering - doesn't really give a specific answer though. And my implementation does not use a Gallery, obviously.
Jump down to the next bold part for the interesting part
So I got Fling/Swipe/Flick/whatever you want to call it to work a while ago on my application. Inspiration was gathered from a couple of different places, some of them being "basic gesture detection" here on Stack Overflow ( Fling gesture detection on grid layout ), Code Shogun ( http://www.codeshogun.com/blog/2009/04/16/how-to-implement-swipe-action-in-android/ ) and Developing Android ( http://developingandroid.blogspot.com/2009/09/implementing-swipe-gesture.html ), but I do not use a ViewFlipper in my application. When a fling occurs I simply change the tab (wrapping around at the ends).
Now, some of my tabs contain ScrollViews. These ScrollViews obviously respond to up/down scrolls in order to let you view all data inside it, no surprise there.
The issue is that it would appear the 'scroll' function of these ScrollViews overwrite my fling gesture. I cannot fling inside a ScrollView (scroll just fine), but it works flawlessly outside them (on the same tab, on other views such as TableRow or whatever).
I had a quick look at http://blog.velir.com/index.php/2010/11/17/android-snapping-horizontal-scroll/ too, which provides a way to implement HorizontalScrollView. But it still handles gestures through a class that extends SimpleOnGestureListener (and overwrites onFling), which is the same implementation as I have (which leads me to believe it won't really help).
Source code for ScrollView from Google: http://google.com/codesearch/p?hl=en#uX1GffpyOZk/core/java/android/widget/ScrollView.java&d=3
Is there any way to have my implementation of Swipe and ScrollView working together effortlessly?
This is where the problem lies, I guess. ScrollView.java also uses a method called onTouchEvent and the documentation for the onTouchEvent for Activity states:
"Called when a touch screen event was
not handled by any of the views under
it. This is most useful to process
touch events that happen outside of
your window bounds, where there is no
view to receive it."
So the ScrollView does "override" it - what do I do? Is there no way to ensure both are checked? My onTouchEvent which is not hit when the onTouchEvent is handled by the ScrollView:
#Override
/** Used for swipe gestures */
public boolean onTouchEvent(MotionEvent event) {
if (gestureDetector.onTouchEvent(event))
return true;
else
return false;
}
More general source code below, it's probably not very vital. The gestureDetector inside my Tabs class with its associated listener:
// Gestures
gestureDetector = new GestureDetector(new MyGestureDetector());
gestureListener = new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (gestureDetector.onTouchEvent(event)) {
return true;
}
return false;
}
};
My gesture class which is a nested class of my Tabs class (which extends TabActivity) - it's the same as any other code you will find on this subject:
/** GestureDetector used to swipe between classes */
class MyGestureDetector extends SimpleOnGestureListener {
TabHost tabHost = getTabHost();
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH) return false;
if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
// my tab code
return true;
} else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
// my tab code
return true;
}
} catch (Exception e) {
Log.e("MyGestureDetector onFling", e.toString());
}
return false;
}
}

I would suggest you have a look at the Google I/O 2010 app source code, as their FlingableTabHost implementation would appear to have solved this problem:
http://iosched.googlecode.com/svn/trunk/src/com/google/android/apps/iosched/ui/ScheduleActivity.java
I think the key is in extending TabHost and overriding its onInterceptTouchEvent method.

In looking to solve a similar issue I am having, I came across this tid-bit:
eventsInterceptionEnabled: when set to true, this property tells the overlay to steal the events from its children as soon as it knows the user is really drawing a gesture. This is useful when there's a scrollable view under the overlay, to avoid scrolling the underlying child as the user draws his gesture
From the Android Dev site, it talks about using this attribute in the <android.gesture.GestureOverlayView> Root in your layout file.

For what it's worth, I found the onFling method very unreliable. I override the onScroll method in the SimpleGestureDetector, and define my onInterceptTouchEvent as:
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//Call super first because it does some hidden motion event handling
boolean result = super.onInterceptTouchEvent(ev);
if (this.mGestureScanner.onTouchEvent(ev)) return true;
return result;
}

Related

Android Navigation Drawer Doesn't Pass onTouchEvent to Activity

I have an Activity which uses the Android NavigationDrawer.
When using only fragments (as usual), everything works perfect.
But now I want to use this drawer on other activities of my app, and for some of them,
I don't want the main view to be a fragment.
Question
The problem is, the onTouchEvent() of the activity itself (and the onItemClickedListener() of a child ListView for that matter) isn't called, because the drawer consumes it.
Of course, I want it to be called:)
Needless to say, I would hope the answer will be simple (even a XML one), and hopefully not by extending the Drawer class (unless that's what it takes of course).
More Info
The Activity's main layout is very simple, basically a ListView and the DrawerLayout on top of it (below in XML).
The Drawer has one fragment as it's childView (for fragment navigation) and of course, the ListView for the Drawer Items.
I've seen many questions regarding (not exactly) similar issues, and the frequent answer was to use onInterceptTouch(), requestDisallowInterceptTouchEvent() on the DrawerLayout, and on the Parent view (Activity's main content) and even onTouchEvent() (with False returned) on the ListView of the Drawer.
Nothing seems to do the trick.
I read this link
and it does seem like using Intercept methods somewhere could be the answer. But how?
Please let me know if you need any code. But it's a very basic code/layout for this matter.
Thanks!
Apparently the answer is somewhat easy, although it does make you extend the DrawerLayout and do some thinking, and maybe will result in some strange results (using the LAST
example, I haven't seen any, yet).
Anyway, related questions which looking backwards can help understanding the issue (will explain about the first one later on):
1. DrawerLayout prevents call of MainActivity.onTouchEvent()
2. How can I requestDisallowTouchEvents on Android DrawerLayout
3. Set drag margin for Android Navigation Drawer
Answer
First, please note that I put lots of examples here. If you just want the best one (for me), jump to the last one.
Secondly, if someone has enough reputation, please comment on the first link's question and put a link to this answer (it can help that guy).
Example 1
Well, basically, just extend Android's DrawerLayout and replace onTouchEvent() to this:
#Override
public boolean onTouchEvent(MotionEvent arg0) {
super.onTouchEvent(arg0);
return false;
}
This solution will do anything except that it won't open the Drawer on slides, only menu clicks and the like. Besides, it forwards clicks so when the Drawer is open
for instance, touching outside of it will NOT close it, but click on whatever is behind (e.g. a ListView). Le'ts try harder...
Example 2
Now, let's catch the open OR visible cases, to return true (and consume the action at the Drawer).
#Override
public boolean onTouchEvent(MotionEvent arg0) {
super.onTouchEvent(arg0);
if(isDrawerOpen(findViewById(R.id.list_slidermenu)) ||
isDrawerVisible(findViewById(R.id.list_slidermenu))){
return true;
}
return false;
}
This solution is better, as it prevents clicks on behind the Drawer when the drawer is open or even visible (slide starts...). But touch-sliding it still doesn't work.
Example 3
Ok, so let's just split cases. Touches (MotionEvent.ACTION_DOWN) inside the Drawer's margin (area that Google desided to slide Drawer when touched at)
will result in returning True to consume the action, and others will forward the event (return False).
#Override
public boolean onTouchEvent(MotionEvent arg0) {
super.onTouchEvent(arg0);
float edge = 30;//that's for a left drawer obviously. Use <parentWidth - 30> for the right one.
View mDrawerListView = findViewById(R.id.drawer_listview);
if(isDrawerOpen(mDrawerListView) ||
isDrawerVisible(mDrawerListView)){
return true;
} else if(arg0.getAction() == MotionEvent.ACTION_DOWN && arg0.getX() > edge){
return false;
}
return true;
}
Note that I used 30dp. That's what I found to be the margin (although in one of the links it is said to be 20....).
Well, the next example would of course be deciding what is, exactly, that edge (see in code above) value is, according to Android. We don't want to
use a number that could change or whatever.
New Question
So now that first link should come handy. It "hacks" the Drawer code to get that Drawer edge/megin number. BUT, it didn't work for me, as those exact Field names could not be found.
I run mDrawerLayout.getClass().getField() which returns all the fields, but without any luck finding what we want. Anyone?
Last Example - Full Code
Ok, looking on example number 3, after understanding what exactly I did, we can make it faster by extending the onFinishInflate() method and save it as a global variable
for this CustomDrawerLayout for later use. We can also put that first 'if' inside the second one to save some more work. OK here goes:
View mDrawerListView;
...
#Override
protected void onFinishInflate() {
super.onFinishInflate();
mDrawerListView = findViewById(R.id.drawer_listview);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
if(event.getX() > 30 && event.getAction() == MotionEvent.ACTION_DOWN){
if(isDrawerOpen(mDrawerListView) || isDrawerVisible(mDrawerListView)){
return true;
} else{
return false;
}
}
return true;
}
That's it for now! Hope it'll helps someone in the future beside myself, hehe....
While working on the same problem I was inspired by guy_m's answer and boiled down his proposals to the following solution.
Again it amounts to extending DrawerLayout and overriding onInterceptTouchEvent(). The logic is simple:
Whenever the touch event occurs off the drawer view (the slideable part), we return false. Then our DrawerLayout is out of the game when it comes to handling the event -- the event is handled by whatever view we put into the DrawerLayout at the respective position.
On the other hand, when the event occurs inside the drawer view, we delegate to super.onInterceptTouchEvent() to decide what to do with the event. That way the drawer will slide in and out as before on touch gestures happening on itself.
The following code sample is for a DrawerLayout whose drawer view is located on the right (android:gravity="right"). It should be obvious how to modify it to cover also the case of a left-placed drawer.
public class CustomDrawerLayout extends DrawerLayout
{
#Override
public boolean onInterceptTouchEvent( MotionEvent event )
{
final View drawerView = getChildAt( 1 );
final ViewConfiguration config = ViewConfiguration.get( getContext() );
// Calculate the area on the right border of the screen on which
// the DrawerLayout should *always* intercept touch events.
// In case the drawer is closed, we still want the DrawerLayout
// to respond to touch/drag gestures there and reopen the drawer!
final int rightBoundary = getWidth() - 2 * config.getScaledTouchSlop();
// If the drawer is opened and the event happened
// on its surface, or if the event happened on the
// right border of the layout, then we let DrawerLayout
// decide if it wants to intercept (and properly handle)
// the event.
// Otherwise we disallow DrawerLayout to intercept (return false),
// thereby letting its child views handle the event.
return ( isDrawerOpen( drawerView ) && drawerView.getLeft() <= event.getX()
|| rightBoundary <= event.getX() )
&& super.onInterceptTouchEvent( event );
}
}
With these answers, i still had some trouble. I could get the motionEvent back to the activity but I lost the onClick listener answer by fragment or everything on the screen. So I found another way to have everything work ( get answer when override OntouchEvent from activity, and answer to onClick Listener )
Extend DrawerLayout and Override this methode :
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(super.onInterceptTouchEvent(ev)) return true;
else {
Activity activity = AppContext.getCurrentActivity();
return activity.onTouchEvent(ev);
}
}
if the drawer want the motion event, let it handle it. And if not, pass the event to activity yourself. (AppContext.getCurrentActivity is something from you with current activity, you can for instance attach activity as weakreference to the drawerLayout OnCreate)
The good thing with this way, you don't care about the edge and don't care if start or end. And you don't care also if it is open or close. Everything work fine.
I have a solution:
Set OnTouchListener on the screen layout (the first childview of DrawerLayout, normally) and transmit the TouchEvent to a custom GestureDetector.
So, you can do your own things in it. One more important thing: if you want to override onSingleTapUp() or something else, you should return true in onDown() to make sure that you can get the rest MotionEvent to make onSingleTapUp() work.
private class MyGestureListener implements GestureDetector.OnGestureListener{
#Override
public boolean onDown(MotionEvent e) {
return true;
}
#Override
public void onShowPress(MotionEvent e) {
}
#Override
public boolean onSingleTapUp(MotionEvent e) {
// do your own things
return true;
}
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
#Override
public void onLongPress(MotionEvent e) {
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
}
and set it :
mGestureDetector=new GestureDetector(this, new MyGestureListener());
layout_content.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
});
To add on to guy_m 's answer, here is my implementation for a drawer that opens from the right, includes constructors so that it is viewable in the layout editor and also takes into account when a user swipes from past the edge point:
public class CustomDrawerLayout extends DrawerLayout {
View mDrawerListView;
float edge;
int holddown = 0;
static final String TAG = CustomDrawerLayout.class.getSimpleName();
public CustomDrawerLayout(#NonNull Context context) {
super(context);
setscreendimensionvals(context);
}
public CustomDrawerLayout(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
setscreendimensionvals(context);
}
public CustomDrawerLayout(#NonNull Context context, #Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setscreendimensionvals(context);
}
private void setscreendimensionvals(Context context){
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
/*((Activity) context).getWindowManager()
.getDefaultDisplay()
.getMetrics(displayMetrics); */
int width = displayMetrics.widthPixels;
float density = displayMetrics.density;
edge = width - (30 * density); // 30 is the edge of the screen where the navigation drawer comes out
Log.d(TAG,"edge: " + edge);
Log.d(TAG,"width: " + width);
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
mDrawerListView = findViewById(R.id.drawerconstraint_overworld);
}
#Override
public boolean onTouchEvent(MotionEvent event){
super.onTouchEvent(event); // need to add action up and a local variable to detect when lifted finger
//Log.d(TAG,"point: " + event.getX());
if(event.getX() >= edge && (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE)){
holddown = 1;
//Log.d(TAG,"hold down");
}
if(event.getAction() == MotionEvent.ACTION_UP){
holddown = 0;
//Log.d(TAG,"hold up");
}
if(holddown == 1){
return true;
}else{
if(isDrawerOpen(mDrawerListView) || isDrawerVisible(mDrawerListView)){
return true;
} else{
return false;
}
}
}
}
For anyone who might have the unfortunate luck of encountering as persistent an issue as this one, I will add onto the others' answers with my own problem case and solution in the hopes that fewer souls will face this nightmare of a headscratcher.
Due notice is that my explanation will most likely work for any swipeable view whose parent is DrawerLayout (e.g. this solution only works for views that are children of DrawerLayout), but I will regale my experience and my toils for the purpose of clarity.
In my case, I needed to have a MaterialCalendarView (3rd-party CalendarView on steroids) in a DrawerLayout with a NavigationView to the right (i.e. with "android:gravity"="end"). It wasn't long after implementing the view hierarchy that I realised there had existed a conflict between the swipe events of my NavigationView and MaterialCalendarView.
In essence, what occured was that whenever I began swiping the MaterialCalendarView to the right in order to swipe back to the next month, I wound up triggering the DrawerLayout's touch event interceptor and closing said DrawerLayout instead of swiping to the previous month.
So, the solution should be easy, shouldn't it? Set a onTouchListeneron the MaterialCalendarView, call requestDisallowInterceptTouchEvent(), and call it a day—akin to this in the view-hosting Activity:
calendar.setOnTouchListener { _, motionEvent ->
when(motionEvent.action) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
drawerLayout.requestDisallowInterceptTouchEvent(true)
}
}
true
}
...and you should be all set?
Well, the fact that I'm answering here is enough to infer that wasn't the case and that my onTouchListener wasn't, like the rest, being triggered.
After extensively scouring this thread and trying to follow everyone's advice, I came at a point wherein none of the solutions offered were helpful for someone who'd simply wanted to "exclude" a view from being detected by the DrawerLayout's touch event interceptor. Some ideas entirely paralysed my touch event infrastructure, while others simply gave me more of the same behaviour. I had hit a roadblock and I didn't know what to do.
Then, an epiphany.
I realised that due to my inexperience with writing custom views I'd missed the glaringly obvious: what I needed to do was simply find out where the MaterialCalendarView was, get its coordinates, and see if any touch events are inside in order to call the proper implementation (be it the Activity or default DrawerLayout one)! And, of course, since in the former, the onTouchListener disables interception of touch events by the DrawerLayout, that meant only the MaterialCalendarView could handle the swipes! It was so simple!
And fast-forward to learning about MotionEvents, reading up on what the heck a Rect was, and a muddy in-between of crashes, I finally wrote the custom DrawerLayout which responded to my swipes on the MaterialCalendarView only with the Activity implementation and ignored the ones outside, opting for the DrawerLayout touch interceptor:
class EventCalendarDrawerLayout : DrawerLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0)
lateinit var calendar: MaterialCalendarView
lateinit var drawer: View
override fun onFinishInflate() {
super.onFinishInflate()
drawer = getChildAt(1)
calendar = findViewById(R.id.event_calendar)
}
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
val rect = Rect()
calendar.getGlobalVisibleRect(rect) // get the calendar rect positions
// respond to proper motions and forward events contained inside the calendar's rect only
if((event.action == MotionEvent.ACTION_MOVE ||
event.action == MotionEvent.ACTION_DOWN) && rect.contains(event.x.roundToInt(), event.y.roundToInt())) {
return (context as Activity).onTouchEvent(event)
}
// otherwise return the default intercept touch event response
return super.onInterceptTouchEvent(event)
}
}
It's not rocket science, truly, but it is something I deemed worth showing, as it was new and unexpected for me (and undoubtedly many others yet to venture here). Nevertheless, I believe that this implementation may work to ignore as many views embedded inside DrawerLayouts as one could possibly wish for.

Swipe Horizontal

I need to swipe horizontally between list of questions and their respective choices in a radio group. I have read that I have different choices such as:
1- Gesture Detector
2- ViewPage
3- ViewPage with fragments
I have tried ViewPage but I am facing different problems on how to swipe backward and indicating which choice has been checked by the user.
I need to use the easiest way to swipe forward and backward (indicating which choice has been checked)
I want to depend on Andriod methods as much as possible without storing the checked choices for example by myself since I believe that it is more optimized such methods from both run-time or code size.
Please if there are other classes that I can use, guide me on that or which of them is really the best to perform my target
I would use ViewPager with a FragmentPagerAdapter. The items of the adapter would be different instances of the same fragment. Each fragment would contain a static question text and a radio group of possible answers to the question.
ViewPager then will take care of swiping substituting one such fragment with another.
I highly recommend this Library:
ViewPagerIndicator by Jake Warton
Is very customizable.
You can easilly use the gesture detector creating a class that extend SimpleOnGestureListener that possess the onFling methods like that :
protected class ExampleGestureDetector extends SimpleOnGestureListener {
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH) {// User swipe vertically
}
if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_MIN_VELOCITY) {// Right swipe
} else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_MIN_VELOCITY) {// Left swipe
}
return false;
}
}
You just have to define the variable as you want, create your gestureDetector like
gestureDetector = new GestureDetector(new ExampleGestureDetector());
And redefine the touch listener for the view you want
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View wv, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
});
EDIT : After testing it, it appear that it only work with scrollable item.

Android onGestureListener and onTouchListner

I am working in android. I have some functionality which is to be done on these following methods:-
1. MotionEvent.ACTION_DOWN
2. MotionEvent.ACTION_UP
3. MotionEvent.ACTION_MOVE
and i also want to do fling() on that image.
For above requirement i implemented onGestureListener and onTouchListener in my application, but these are not working properly. onTouchListener is working but onGestureListener is not working. When i remove onTouchListner code then onGestureListener is working correctly.
So please suggest me what should i do for this. I want to implement these four methods in my application.
1. MotionEvent.ACTION_DOWN
2. MotionEvent.ACTION_UP
3. MotionEvent.ACTION_MOVE
4. onFling
You can acheive this by implementing the OnGestureListener from your activity overriding the below methods
// For touch
public boolean onSingleTapUp(MotionEvent event) { return false; }
// For Fling
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return false;
}
Hope this helps.
EDIT 1: Detailed explanation:
1> implement the OnGestureListener from your activity
public class MyActivity implements OnGestureListener
2> create an instance of GestureDetector:
private GestureDetector gestureScanner;
And at onCreate:
// Avoid a deprecated constructor
// (http://developer.android.com/reference/android/view/GestureDetector.html)
gestureScanner = new GestureDetector(this, this);
3> Override the below methods:
#Override
public boolean onTouchEvent(MotionEvent event) {
return gestureScanner.onTouchEvent(event);
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
/* on scroll to the next page in story */
if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE
&& Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
// ...
}
/* on scroll to the previous page in story */
else if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE
&& Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
// ...
}
return false;
}
#Override
public boolean onSingleTapUp(MotionEvent event) {
return false;
}
EDIT 2: For handling Move
Override the onScroll method have a look at the details here
Just as a word of advice coming from a fellow Android developer who has chased down the primrose path of the swipe or fling event... When writing a listener or trying to implement the OnGestureListener class be sure to have import android.view.GestureDetector.OnGestureListener in your imports.
Otherwise the OnGesturListener will be using a GestureOverlayView's imports and that's not what it seems you are looking for. Those are helpful especially when trying to implement gesture captures using the Gestures application and importing the saved gestures and creating a Gestures Library for your application during run time to then "predict" what the user is doing by running some fancy algorithm in the background and seeing who deviates the least from the user's input. Which turns out to be an array of locations that came in from the OS as the dragged their little stubby fingers across the flat screen... It's a different approach than just trying to capture a fling, or long press. Which seems as what the users are looking for above and not some fancy multi-press gesture capture, which this approach makes very simple. This technique is also very helpful in video games and giving possibly a "backdoor" to any people out there who like to develop games. Or in the 9-5 world, to put a Support or Admin backdoor into your mobile application to hide some settings or logs from everyday users. In which one can also use the above intended techniques but then you have to get an array of points, or locations across the screen and it gets tedious for any non-math people or self taught developers. Hope I contributed or helped anyone. No one mentioned this to me when I was learning this and I never found it in any of the several books I have read about Android also.

Android. How to get layout values from views in TabHost?

I have a TabHost hosting 3 Activities. In addition to the tabs, I want support for swipe gestures to change the current tab. My issue is that one of the views holds a horizontal scrollview, and I cannot figure out how to prevent touches in the horizontal scrollview from changing the current tab.
plateView is the horizontal scrollview that needs to be handled. Finding it's bottom with plateView.getBottom() and not counting touches above that works without the TabHost, but now it returns null and crashes, regardless of where it's called.
onTouchEvent counts touches everywhere and dispatchTouchEvent doesn't count touches on any widget. It seems some combination of them would be great, but together they yield the same functionality as onTouchEvent alone. Swipes anywhere count to change the Activity. My understanding of these is a little fuzzy though.
Why does getBottom() return null? How can I get this to work?
gestureDetector = new GestureDetector(new CalcGestureDetector());
gestureListener = new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(TAG,"onTOUCH");
if(gestureDetector.onTouchEvent(event)){
return true;
}
return false;
}
};
//Takes the touch and interprets it. Handles it. Changes tabs on fling.
class CalcGestureDetector extends SimpleOnGestureListener{
#Override
public boolean onFling(MotionEvent eOne, MotionEvent eTwo, float velocityX, float velocityY){
Log.d(TAG,"WTF "+plateView.getBottom());
if(false){
}
else{
try{
if (Math.abs(eOne.getY() - eTwo.getY()) > flingMaxOffPath)
return false; //Too much of an arc in the fling.
// right to left swipe
if(eOne.getX() - eTwo.getX() > flingMinDistance && Math.abs(velocityX) > flingMinVelocity) {
tabHost.setAnimation(slideLeftIn);
tabHost.setAnimation(slideLeftOut);
tabHost.setCurrentTab((tabHost.getCurrentTab()+1)%3);
} else if (eTwo.getX() - eOne.getX() > flingMinDistance && Math.abs(velocityX) > flingMinVelocity) {
tabHost.setAnimation(slideRightIn);
tabHost.setAnimation(slideRightOut);
tabHost.setCurrentTab((tabHost.getCurrentTab()+2)%3);
}
}
catch(Exception e){
}
}
return false;
}
}
//This method alone keeps a touch in the weights from changing tabs, but won't register
//touches on ANY widget on any screen (like text views).
public boolean dispatchTouchEvent(MotionEvent event){
super.dispatchTouchEvent(event);
return gestureDetector.onTouchEvent(event);
}
//This method alone counts touches for swipes anywhere and everywhere.
#Override
public boolean onTouchEvent(MotionEvent event) {
if (gestureDetector.onTouchEvent(event))
return true;
else
return false;
}

Android: GestureDetector not working (gestureDetector.onTouchEvent(event) always false) with Tabs (TabActivity, Tabwidget)

I have implemented my TabActivity with different child activities:
intent = new Intent().setClass(this, MyChildTabActiviy.class);
// Initialize a TabSpec for each tab and add it to the TabHost
spec = getTabHost.newTabSpec("tag").setIndicator("indicator", getResources().getDrawable(R.drawable.icon)).setContent(intent);
getTabHost.addTab(spec);
...
So far no problems, everything works perfectly fine. I'm switching programmatically between tabs, replacing activities within tabs with ActivityGroups, etc. just as it's shown in many tutorials.
But my problem is, that when I want to check for a fling gesture my gestureDetector.onTouchEvent(event) is always returning false, thus no gesture is registrated.
This is my implementation of gestureDetector:
public class MyChildTabActiviy extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
// ... building views, controls, etc.
GestureDetector gestureDetector = new GestureDetector(this, new MyGestureDetector());
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
class MyGestureDetector extends SimpleOnGestureListener {
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
return false;
// left to right swipe and right to left swipe
if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE
&& Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
//... fling logic ...
return true;
} else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE
&& Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
//... fling logic ...
return true;
}
return false;
}
}
The thing is, that this code (and also the fling detection) works perfectly fine, when I'm starting these activities (there are four basic activities, that I sometimes switch to other activities) outside of a TabActivity, e.g. as a Launcher Activity. But I can't get it to work within a TabActivity. I already tried to append the GestureDetector to the TabActivity, but it doesn't work. I tried to append the GestureDetector to specific views like some layout views or buttons, ViewFlippers, etc. but it just doesn't work.
When I'm debugging, I can see that the touch event is triggered and a motion is registered, but it just isn't evaluated as a fling or any other gesture.
So my question is, are there are any limitations regarding the usage of GestureDetectors with Tabs in Android? As I said, the gestures are registrated perfectly outside a TabActivity.
I would greatly appreciate the help of someone who knows the answer.
If there is a limitation, how could someone get a workaround for that problem?
Thanks in advance for answers.
Have a look at the answer mentioned here. He's pretty much done the same thing as you, but if you look at the first comment on the highest rated answer, Cdsboy got it working by implementing OnDown and returning true. I'm not sure why that is needed, but it worked for me.
As a complement to #Abhinav 's answer (that btw helped me too), I'd like to say that I think overriding onDown() is needed because its default implementation in SimpleOnGestureListener is to return false. Being ACTION_DOWN the first one to reach the listener, it would make it discard the event, whatever it is.

Categories

Resources