I use a view switcher to switch between views in a way that is similar to standard activities switching in a task. For example, the current view in the switcher might have a button that, when clicked, initiates the view switch: the current view, which is now obsolete, slides out, and is replaced by the new current view, which slides in.
The animated switch is fine only for one thing: I cannot find a correct way to tell the sliding out view to stop processing user events, like touch events. So what happens is, if done fast enough, and it doesn't have to be that fast, the user can click on the button that initiates the view switch more than once, which is bad. Once a click (or any user action) has initiated a view switch, I would like to invalidate and ignore all other user events on the sliding out view.
Is there a clean, standard way to do so? I've tried setEnabled(false) on the sliding out view, but it seems click listeners in child views are still handled afterward. The thing I want to avoid is looking for all event handlers and adding verification code that ensures that nothing is done if the view is actually sliding out.
I had a similar problem and want to share my solution. I override the Activity.dispatchTouchEvent method as noted here. In doing so I intercept all touch events before they are dispatched to the other views.
public boolean mDisplayBlocked = false;
#Override
public boolean dispatchTouchEvent(MotionEvent pEvent) {
if (!mDisplayBlocked) {
return super.dispatchTouchEvent(pEvent);
}
return mDisplayBlocked;
}
In your click listener, before you initiate a view switch, I believe you should be able to disable the listener.
I imagine other UI interactions will still be possible though. Perhaps you need a masterDisable method, which will disable all UI interactions on that View, which you can call before you make the view switch.
There is no method to tell a parent to disable all of it children's events. And also, calling setEnabled(false) on a parent does not disable it's children.
Probably the easiest way to do this is to call setEnabled(false) on the button.
May be, If you set the visibility to GONE for the child controls of the fading out view, you can check the same in the listener and handle events only for the visible views.
Well, it was painful, but I think I finally found the right way to achieve what I wanted, and you really have to find out how the Android API wants you to do it. So here is how I manage the child view that is sliding out:
childView.startAnimation(animation);
parentView.removeView(childView); // makes childView a "disappearing child" of parentView
That may seems strange, but when you do that, childView is still drawn in its parent, even though it is not "physically" there anymore (it doesn't receive user inputs anymore, which is the behavior I was after in the first place). Hence, childView becomes a "disappearing child" of parentView, a very special case implemented in ViewGroup. And when the animation ends (through AnimationListener), you must do:
parentView.clearDisappearingChildren();
otherwise parentView will still draw the pixels of not-animated-anymore childView.
Note that the first thing I tried was:
childView.startAnimation(animation);
And when the animation ended:
parentView.removeView(childView);
Even though that way of doing seemed natural to me, all hell started to break loose! I started getting NullPointerException deep in ViewGroup.dispatchDraw. All of this because AnimationListener.onAnimationEnd was called during the execution of ViewGroup.dispatchDraw, and changing the list of child views is a very bad idea at that point...
Related
I've design the user interface of my app as a collection of views, not activities or fragments. I manage the showing and hiding of views on this collection of views which are right on top of each other.
I believe I chose the wrong design principle to use for my app. The problem I am having is that when I hide a view in order to show another one; the button that resides on the hidden view fires its on-Click listener.
After reading some of the Android Reference documentation, I see that the view tree will navigate down the hierarchy of views in order to consume the touch event. It is then that the on-Click listener of this button is called.
What I need is something to prevent the button from the hidden view to stop calling itself when the user touches the shown view. I know that calling setVisibility(View.INVISIBLE) stops the on-Click listener from being called.
I was thinking of the ViewTreeObserver class that will allow me to create a listener of view hierarchy that somehow allows me to get a handle of all the hidden views in order to call setVisibility(View.INVISIBLE) on the hidden views.
Any ideas or advice
When you do setVisibility(View.INVISIBLE) for a view, it will still present in the layout. So instead of setVisibility(View.INVISIBLE), try setVisibility(View.GONE).
This will remove the view completely from the layout including the onClickListner attached to it.
When you want that view back, you can use setVisibility(View.VISIBLE) to get it back.
I have a bottom drawer - a view that is bottom aligned with the bottom of the screen and when dismissed animates itself down until it becomes invisible (in such state its top border is aligned with the bottom of the screen). I am trying to implement dismissing this view by vertical swipe down on it. To do it I attached a gesture detector in this method of the view:
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
I did it this way since this view contains a horizontal recycler view, so I do not want to intercept the child touches too early to make sure my view responds only to swipe down and does not steal horizontal touches from the recycler view (as described here: https://developer.android.com/training/gestures/viewgroup.html)
Now, my problem is that when I swipe on one area of this view, the fling recognition works, while when I swipe on another area ofthis view it does not work. In the first case I see in the debugger that onInterceptTouchEvent is called three times, the third call is recognized by the gesture recognizer as fling. In the second case I see that onInterceptTouchEvent is called only once.
It looks that some other view is stealing my touches, but I have no idea which one is that since my app is pretty complex and I am not the only author :). What is an easy way of debugging that in a general case? I would like to just know what view in the entire hierarchy of my activity consumed my event making onInterceptTouchEvent called only once - that would be a good start for the further investigation.
Thanks!
The way I do it is using emulator with android source code downloaded.
I put a breakpoint in View.dispatchTouchEvent (all places that returns true). This will show me which view is handling the event.
If this does not help, you can also place various breakpoint log messages.
I hope this will help you solve the problem :D
I have extended a FrameLayout and overriden the onInterceptTouchEvent() and onTouchEvent(). This custom Framelayout will be the root layout of my ListView rows.
My requirement is that, if I tap on a row in the ListView, the onItemClickListener should be called, if I scroll (up/down) the list scrolls as usual. However if I scroll (left/right) or swipe (left/right), the custom FrameLayout should take some action based upon which row I perform the action on.
My idea was that, I would override onInterceptTouchEvent, monitor the events and if I see that ACTION_MOVE has been called enough to make it a scroll/swipe then I take control and do the task. If it's a tap or some other gesture then let the default flow occur.
The problem I face now is that if my framelayout does not contain any touchable child, then I receive no calls post ACTION_DOWN. And if I receive the ACTION_DOWN in my onTouchEvent() so as to analyze the kind of gesture, then I have no control left to let the default flow occur if I calculate and find that the action was actually just a tap/vertical scroll.
Is there some way in which a child can get hold of the events (ACTION_DOWN and ACTION_MOVE) and if a condition satisfies then perform an action or else let the parent handle these same events?
Is there some other approach that I can take to satisfy this requirement?
The problem is the the scrolling will intercept with touch event has set to the parent layout.
Can I keep the onTouch event with the scroll in ScrollView ?
This is a very tricky part. There is an overriden method from Activity which is: public boolean onTouchEvent(MotionEvent event)
This is the general method that interprets all the touch events from the whole screen. And you could say, "ok, I can implement this and I am good to go..". And here comes the difficult part on how android works.
As you know every View has its own onTouchEvent() method that you could implement in order to add some custom implementation. So which method will listen? The ScrollView or the Activity? It appears that these touch events go from the "inside" elements to the "outside" elements. I mean parent-child relations.
Another thing to take into account is that the onTouchEvent method returns a boolean. This boolean parameter determines whether the touch event should go one level up or it is handled by the current View. Meaning that if you have a CustomViewA that implements the onTouchEvent() and CustomViewB implementing its own touch event, and the A is a child in B then the touch event would go through A first and if it is not handled it would go to B.
So basically yes it could be done. It depends on what touch event you wanted to do.
So in our case, the ScrollView returns true when the touch events are a horizontal. The activity's touch event will be handled only if the ScrollView touch event is not handled by itself then you are fine. Otherwise you have to override and implement the on touch event of scroll view and in some cases you have to return false so as for the whole layout to implement it. Good luck with the last part. I started to implement a fling effect but came up with some difficulties so I have implemented a 2 finger move with scroll view in it and it works like a charm.
This is about a week of research and experimenting and it is an overview of what I came up with. if you find anything else please let me know. Hope it helped.
The views are not cached in a ViewFlipper. Is there a way wherein we can get an image of the view and show it to user so that he sees the Ui as we see on Home scrren(when we swipe the previous view also moves along and when we lift our finger, only then the next view is shown completely.)
What I want to do is that when the user starts moving his finegr on screen, the view should also move along(create an image of view).
I am not getting to do this, as when we swipe the present view goes and next view comes, we do not get both visible when we r moving our finger on screen.
Please if anyone gets what I am trying to do, do help me.
Thanks,
Farha
It's tricky to get scroll and swipe tracking working on Android, while using ViewAnimator or its subclasses.
They allow you to set in and out animations and start them at a given moment, but they work with discrete, either-this-or-the-other-view animations. They are actually using FrameLayout and after in or out animation is executed, other views' visibility is set to View.GONE to hide them from showing up under/over your current View.
The Launcher and the Gallery application are actually doing the functionality you want, by using a different approach.
They track the user touch input (onTouchEvent()), on MotionEvent.ACTION_MOVE they perform animations manually and on MotionEvent.ACTION_UP snap to the appropriate view, just like in the iPhone.
Unfortunately, this approach is actually more complicated than it looks like.
With the manual handling, you have to ensure that you are taking care of everything related to the touch input. This includes a lot of flag-raising, value-checking, event-delegating, etc.
If you want to get better acquainted with this, take a look at these classes from Gallery3D or Launcher's source code.
One other way to get nice horizontal scrolling is to use HorizontalScrollView.
You have to figure out a way to recycle your views, like you would with a ListView and you have to add the snap-to-view logic, but if you have to take care of a small number of views it could be the easiest approach.
Hope that helps.