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

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;
}

Related

Long press possible on child item of HorizontalScrollView?

I'm having a problem implementing a long press within my custom view, based on a HorizontalScrollView.
The HorizontalScrollView has a child LinearLayout, which in turn has a child View. The View draws bitmaps to the canvas via OnDraw().
I'd like to allow the HorizontalScrollView to scroll normally, either fast or slow. But, if the user holds their finger (even if scrolling) on one of the images, it would immediately cancel the scrolling and allow the user to perform a function with the selected image. (In this particular case, they'd be moving the image around the screen, but it could really be any number of functions.)
I've tried many combinations of handling the events (true, false, super) within each layer (HorizontalScrollView and View) but none seem to work 100%. Some combinations get there most of the way, some others part of the way, but they always seem to be missing one feature or another (scroll, hit test, etc.).
The closest I've gotten is to return false within the HorizontalScrollView's onInterceptTouchEvent() and true within the View's onTouch() event. This allows the scroll and also registers the hit test on the image. But, it immediately passes control back to the onTouch() event of the HorizontalScrollView. That makes it impossible to check if the image has been pressed for a number of seconds (long press).
If I return true within the View's onTouch() event, the hit test registers, and I'm able to check if the user has long pressed the image within ACTION_MOVE. But, then the HorizontalScrollView doesn't scroll.
Am I missing something completely obvious, or have I simply chosen two views that don't play well together? Any insight is appreciated.
right,
dont know if you have sorted this or not, I have mashed some bits together that I think do what you ask, if not then hey ho.
I have an activity that loads in the horizontal scroller, this might not be the best way but it works for me:
HolderActivity class (the one that loads in the HorizontalScrollView class) I have:
int selectedItem;
public boolean onLongClick(View v, int position) {
selectedItem = position;
openContextMenu(v);
return true;
}
public boolean onItemClick(int position) {)//do what you want here on click (press)
#Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
String[] menuItems = {"Menu item 1 text", "Cancel"};
for (int i = 0; i<menuItems.length; i++) {
menu.add(Menu.NONE, i, i, menuItemsRemove[i]);
}
menu.setHeaderTitle("My menu title");
}
in your HorizontalScrollView class's constructor pass I pass through a context in there like so:
public MyScroller(Context context) {
super(context);
this.context = context;
}
I have a method for creating the items from an ArrayList called setFeatureItems like so:
public void setFeatureItems(ArrayList<MyListEntity> items){}
Within this method I add a GestureDetector passing the context to it to each item like so:
mGestureDetector = new GestureDetector(context, new MyGestureDetector());
And the MyGestureDetector nested class which has the reference to the all important parentActivity is like this:
class MyGestureDetector extends SimpleOnGestureListener {
#Override
public void onLongPress(MotionEvent arg0) {
parentActivity.onLongClick(MyScroller.this, mActiveFeature);
};
#Override
public boolean onSingleTapUp(MotionEvent arg0) {
parentActivity.onItemClick(mActiveFeature);
return true;
};
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
//right to left
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
int featureWidth = getMeasuredWidth();
mActiveFeature = (mActiveFeature < (mItems.size() - 1))? mActiveFeature + 1:mItems.size() -1;
smoothScrollTo(mActiveFeature*featureWidth, 0);
return true;
}
//left to right
else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
int featureWidth = getMeasuredWidth();
mActiveFeature = (mActiveFeature > 0)? mActiveFeature - 1:0;
smoothScrollTo(mActiveFeature*featureWidth, 0);
return true;
}
} catch (Exception e) {
Log.e("Fling", "There was an error processing the Fling event:" + e.getMessage());
}
return false;
}
}
I have cut this from an existing proj so there might be remnants where I have not made it generic enough, I hope this makes sense or helps, let me know If i can add any more detail

Viewflipper with Scrollviews and a nav bar

I have a viewflipper which includes a number of layouts. Each layout has a scrollview as the root.
Below the viewflipper I have a horizontal scrollview which contains textviews which acts as a navigation bar.
My original issue was that the fling stopped working as soon as I added a scrollview but I added the following code:
#Override
public boolean dispatchTouchEvent(MotionEvent ev){
super.dispatchTouchEvent(ev);
return gestureDetector.onTouchEvent(ev);
}
And the scrolling now works along with the fling.
The only issue I have now though is that the HSV nav bar now acts a little strange. Sometimes when I try to slide it across, to select new contents, the HSV springs back to where it was. Other times the HSV thinks I want to fling and moves to the next contents. This is of course dependant of the speed I try to scroll it.
I want the nav bar to work independently of the flipper.
I'm trying to understand the code to fix it myself but not getting anywhere.
I have the following in my onCreate:
gestureDetector = new GestureDetector(new MyGestureDetector());
gestureListener = new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (gestureDetector.onTouchEvent(event)) {
return true;
}
return false;
}};
I have the MyGestureDetector class:
class MyGestureDetector extends SimpleOnGestureListener {
#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;
// right to left swipe
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
flipper.setInAnimation(flip_in_from_right);
flipper.setOutAnimation(flip_out_to_left);
flipper.showNext();
} else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
flipper.setInAnimation(flip_in_from_left);
flipper.setOutAnimation(flip_out_to_right);
flipper.showPrevious();
}
} catch (Exception e) {
// nothing
}
And then the override:
#Override
public boolean dispatchTouchEvent(MotionEvent ev){
super.dispatchTouchEvent(ev);
return gestureDetector.onTouchEvent(ev);
}
Can anyone shed any light on this?
Where exactly is your HSV? Is it pinned to the bottom of the screen? If so, here's what I would do. Within your onFling event, check the Y position of the fling and make sure it's not within your HSV. If it is, then return false and you should be good to go.

Let parent View assume MotionEvents if child returns false

I have a application that need event handling on a unusual way.
For my question, let me first explain a simple case that the current event handling system of Android don't fits for me.
Supposing that I have a FrameLayout (that I'll call ViewSwiper since now) that all Views added on it are MATCH_PARENT X MATCH_PARENT (that I'll call PageView), it's handles events by translating the actual View and replace it based on the direction of moving.
This component I already have done and work properly ( Using Animation to swipe views ).
But the problem is on that PageView I add on top of it, in case of ImageViews that return false on it's onTouchEvent, the ViewSwiper will handle the events and let another PageView enter the screen, but if I add a ScrollView on that, all the events will be consumed by the Scroll and the ViewSwiper will not have chance to replace the PageView.
So I figured out that returning false onTouchEvent of the ScrollView the parent can assume it's events, I wrote this sub-class of ScrollView to test it:
public class ScrollViewVertical extends ScrollView {
public ScrollViewVertical(Context context) {
super(context);
setOverScrollMode(OVER_SCROLL_ALWAYS);
setVerticalScrollBarEnabled(false);
}
public boolean onTouchEvent(MotionEvent evt) {
super.onTouchEvent(evt);
return false;
}
}
But returning false make any further events to get dispatched to the parent, but I need these events for VERTICAL scrolling, so I have the idea to return falses only if the user are moving HORIZONTAL, that's what my code looks like:
public class ScrollViewVertical extends ScrollView {
private MovementTracker moveTracker;
public ScrollViewVertical(Context context) {
super(context);
setOverScrollMode(OVER_SCROLL_ALWAYS);
setVerticalScrollBarEnabled(false);
moveTracker = new MovementTracker();
}
public boolean onTouchEvent(MotionEvent evt) {
if (moveTracker.track(evt))
if (moveTracker.getDirection() == Direction.HORIZONTAL)
return false;
return super.onTouchEvent(evt);
}
}
PS: MovementTracker will returns true on track() after some events and tell on which direction the user is moving.
But in that case, the ScrollView keep receiving events since it's returns true on the first events.
Any ideas on how can I handle the events on ViewSwiper when it's child returns false (even if some trues are returned).
PS: I can give more info about this if needed, and accept different solutions also.
Based on answers I tried the following:
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
onTouchEvent(ev);
return intercept;
}
public boolean onTouchEvent(MotionEvent evt) {
boolean x = super.onTouchEvent(evt);
if (moveTracker.track(evt)) {
intercept = moveTracker.getDirection() != Direction.VERTICAL;
if (!intercept)
getParent().requestDisallowInterceptTouchEvent(false);
}
return x;
}
Still nothing.
try this in onTouchEvent() of the scrollview
//if (evt.getAction() == MotionEvent.ACTION_MOVE) {
if (moveTracker.track(evt)){
if (moveTracker.getDirection() == Direction.VERTICAL){
//Or the direction you want the scrollview keep moving
getParent().requestDisallowInterceptTouchEvent(true);
}
}
return true;
Update
Please try the following to the custom Scrollview
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
super.dispatchTouchEvent(ev);
return false;
}
And nothing else
This way i assume the MotionEvent will perform on both views. And since they don't conflict (One is vertical the other one is Horizontal) this could work
Based on the answer from weakwire, I came to the following solution:
On ViewSwiper
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if(!super.dispatchTouchEvent(ev))
onTouchEvent(ev);
return true;
}
And on ScrollHorizontal I return false on dispatchTouchEvent when I don't need then anymore.

Android ListView tolerance

I have list items that have a HorizontalScrollView in each of them. Hence, I should be able to swipe up and down the list and also swipe through the list item horizontally. (e.g. Pulse News App).
What I'm encountering is that, whenever I scroll the HorizontalScrollView, the ListView also gets scrolled by tiny bit but giving out annoying disturbances in terms of User Experience.
So, I was actually thinking to subclass ListView and implement my own ListView widget. However, I don't know which method to override so that I could increase X tolerance and stop the listview from giving these unwanted flickering.
gestureDetector = new GestureDetector(new YScrollDetector());
public boolean onInterceptTouchEvent(MotionEvent ev) {
//Call super first because it does some hidden motion event handling
boolean result = super.onInterceptTouchEvent(ev);
//Now see if we are scrolling vertically with the custom gesture detector
if (gestureDetector.onTouchEvent(ev)) {
return result;
}
//If not scrolling vertically (more y than x), don't hijack the event.
else {
return false;
}
}
// Return false if we're scrolling in the x direction
class YScrollDetector extends SimpleOnGestureListener {
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
try {
if (Math.abs(distanceY) > Math.abs(distanceX)) {
return true;
} else {
return false;
}
} catch (Exception e) {
// nothing
}
return false;
}
}
This is will help you, you have to override these in the custom list view you create,i used this to eliminate the problem in my application. Hope this helps.

Swipe/Fling tab-changing in conjunction with ScrollView?

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;
}

Categories

Resources