I use a 3rd party component (AndroidSideMenu) which allows the whole activity to be dragged to the right to expose a side menu.
In the main (non-menu) section of the layout I have a horizontal SeekBar.
The problem is that when I start dragging the SeekBar button, the whole layout drags along with it because the SlideHolder component is also responding to the drag events.
I'm not sure how touch events are managed in Android, but is there a way to stop the events after they are processed by the SeekBar so that they don't reach the parent container?
So when you drag the Seekbar, the whole screen (parent) must stay put, but when you drag outside the SeekBar it must still work as required for the side menu?
Thanks
In the container of the SeekBar, create and apply (to the seekBar) an OnTouchListener like the following:
private final OnTouchListener onTouchListener = new OnTouchListener()
{
public boolean onTouch(View v, MotionEvent event)
{
// If the View in question isn't a child of the seekBar and
// isn't the seekBar itself, then return and don't consume the event.
if ( (seekBar.findViewById(v.getId()) == null) &&
(v.getId() != seekBar.getId()) )
{
return false;
}
// The View is either a child of the seekBar or is the seekBar itself,
// so we pass the event along to the seekBar and return true, signifying
// that the event has been consumed.
seekBar.onTouchEvent(event);
return true;
}
}
Try this out and see if it works; you may need to create a more robust condition for the if statement.
Related
I am catching the touch events on an ImageView, when I touch it the view gets hidden and all the view is covered by a FrameLayout. I need that the touch events on the image view to stop and to transfer a new touch event on the new layout, which is covering this ImageView.
I have tried something like this:
imageView.setOnTouchEventListener { v, event ->
imageView.clearFocus()
frameLayout.requestFocus()
return false
}
I have tried things as dispatchEvent but it does not work in this case since the imageView is just a little part of the whole screen and the frameLayout covers all the screen.
Anyways I always end up with the same problem, where I have to lift my finger and touch the screen again so the imageView looses the old touch event and finally the framelayout starts catching it. What can I do to solve this?
you can implement touch event listener on both ImageView and FrameLayout then use a variable to know ImageView is touched or not. something like this:
boolean imageViewIsTouched = false;
imageView.setOnTouchEventListener { v, event ->
if(!imageViewIsTouched){
//do something
imageViewIsTouched = true;
}
return false
}
frameLayout.setOnTouchEventListener { v, event ->
if(imageViewIsTouched){
//do something
}
return false
}
So I have the following view structure:
LinearLayout
HorizontalScrollView
Other Child Views
The parent LinearLayout is clickable has a custom selector (changes color when pressed). I want to be able to touch the HorizontalScrollView within the LinearLayout and still handle the touch in the LinearLayout as long as it is not a scroll motion. If I do a scroll motion then the HorizontalScrollView should intercept the gesture and cancel the touch for the LinearLayout. Basically, I want to be able to intercept the gesture from a child view as opposed from the parent which is the standard.
I have tried to handle the MotionEvent manually by creating extension classes that do the following:
LinearLayout
public override bool OnInterceptTouchEvent(MotionEvent ev)
{
// Handle the motion event even if a child returned true for OnTouchEvent
base.OnTouchEvent(ev);
return base.OnInterceptTouchEvent(ev);
}
HorizontalScrollView
public override bool OnTouchEvent(MotionEvent e)
{
if (e.Action == MotionEventActions.Down)
{
_intialXPos = e.GetX();
}
if (e.Action == MotionEventActions.Move)
{
float xDifference = Math.Abs(e.GetX() - _intialXPos);
if (xDifference > _touchSlop)
{
// Prevent the parent OnInterceptTouchEvent from being called, thus it will no longer be able to handle motion events for this gesture
Parent.RequestDisallowInterceptTouchEvent(true);
}
}
return base.OnTouchEvent(e);
}
This almost worked. When I touch the HorizontalScrollView, the LinearLayout shows the pressed state UI and activates when the click is completed. If I touch and scroll the HorizontalScrollView then scrolling works. When I let go of the scroll, the click handler for the LinearLayout does not fire because it was intercepted. But the problem is that before I start scrolling the LinearLayout changes to the pressed state and it does not reset even after the gesture is completed. In my additional attempt to try to manually cancel the gesture for the LinearLayout I kept running into other issues. Additionally, the LinearyLayout has other buttons inside it which when clicked should not allow the parent LinearLayout to display the pressed state. Any suggestions? Is there a set pattern for intercepting touch events from a child? I'm sure it is possible if both classes know about each other, but I am trying to avoid coupling them.
The following work for me for all cases:
InterceptableLinearLayout
public override bool DispatchTouchEvent(MotionEvent e)
{
bool dispatched = base.DispatchTouchEvent(e);
// Handle the motion event even if a child returns true in OnTouchEvent
// The MotionEvent may have been canceled by the child view
base.OnTouchEvent(e);
return dispatched;
}
public override bool OnTouchEvent(MotionEvent e)
{
// We are calling OnTouchEvent manually, if OnTouchEvent propagates back to this layout do nothing as it was already handled.
return true;
}
InterceptCapableChildView
public override bool OnTouchEvent(MotionEvent e)
{
bool handledTouch = base.OnTouchEvent(e);
if ([Meets Condition to Intercept Gesture])
{
// If we are inside an interceptable viewgroup, intercept the motionevent by sending the cancel action to the parent
e.Action = MotionEventActions.Cancel;
}
return handledTouch;
}
I've been learning android for almost a month, and I want to make a simple game with a custom class that extends from View, and it's included on main_activity.xml. In Main Activity.class I create an instance of the View, and control the movement of the sprite on the GameView with buttons, so each button has a method that control sprite's movement like this:
public void move_up(View v){
gameview.sprite.move(Dir.UP);}
The problem is that it only works when the button is released, and it's executed one time. I want the method to be executed while the botton is pressed but I can't figure out how to do this.
I'm assuming by the name of your method that you're adding the onClick property to the xml layout file in order to handle the event. While this may seem convenient at first it will not give you what you need.
Instead, you should implement the OnTouchListener which gives you information about what touch event is currently happening, in your case you'd want to handle the ACTION_DOWN action:
findViewById(R.id.btn_up).setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
gameview.sprite.move(Dir.UP); //Or whatever
return true; //In case you wan to handle ACTION_UP
}
return false;
}
});
Although you could make your Activity implement the listener and handle multiple buttons there. This is achieved simply by telling your activity to implement OnTouchListener, imeplementing the method onTouch as in the code above but instead as a method of your activity.
And the resulting set, would be simplified to findViewById(R.id.btn_up).setOnTouchListener(this); where R.id.btn_up is the id you've defined in the xml file.
This would make it start moving when they press, instead of when they release, if what you want is to make it move until they release (which would make sense), do something like:
if(event.getAction() == MotionEvent.ACTION_DOWN){
gameview.sprite.move(Dir.UP); //Or whatever
return true; //In case you wan to handle ACTION_UP
}else
if(event.getAction() == MotionEvent.ACTION_UP){
gameview.sprite.stop(); //Or whatever you call the stop method
return false;
}
Idea: I want to display an image as background. On this image, somewhere on the screen there is a little clickable View. On top of this all, a black draggabke image with a little transparent whole (displaying the underlying Views).
This is solved with a FrameLayout, whereas the image is the first frame, the View is the second frame and the ''Viewport'' is the third frame.
Now the goal is, that the user can click that second frame, if it is visible in the viewport. The problem is, that this transparency is only ''in the png'', not in the frame itself. So the solution would be to propagate the OnClick coordinates to the underlying frame. How is this possible? Is there a functionality for a FrameLayout in such cases?
Making the upper layer as clickable false, i think the click event should be ignored by it and passed to the second frame
Create a custom class that extends FrameLayout
Add the following overridden methods
#Override public boolean onTouchEvent(MotionEvent event) {
return false;
}
#Override public boolean onInterceptTouchEvent(MotionEvent event) {
return true;
}
Then add touch listener to frame layout like
framelayout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
underLyingView.onTouchEvent(motionEvent);
return false;
}
});
This will pass onclick event to the view underLyingView which lies under FrameLayout
Okey, so I've implemented a button on a Sliding drawer in a android application I'm building. The only problem is that when I press the button the whole sliding drawer is pressed and it slides up.
I know I can disable 'press to slide up' in the XML, but that does not seem to work as the sliding drawer still is pressed just without the slide up.
If I call the slidingDrawer.lock(); function the button actually works but then the sliding drawer can't slide up or even be pressed up.
Any one have a simple solution to this problem?
If I understand well you have added buttons on your SlidingDrawer handle and you want them to work like buttons when the user press them with keeping a standard SlidingDrawer behaviour when the handle is pressed/dragged?
I just solved a similar problem.
My Handle was looking something like that:
It's composed of two buttons and a center TextView which will be the real handle (reacting as a standard SlidingDrawer handle).
To make the buttons work independently of the SlidingDrawer I changed a bit of source code in the onInterceptTouchEvent method of the standard SlidingDrawer.java class (copy paste the source file from the android code source):
public boolean onInterceptTouchEvent(MotionEvent event) {
//...
final Rect frame = mFrame;
final View handle = mHandle;
// original behaviour
//mHandle.getDrawingRect(frame);
// New code
View trackHandle = mTrackHandle;
// set the rect frame to the mTrackHandle view borders instead of the hole handle view
// getParent() => The right and left are valid, but we need to get the parent top and bottom to have absolute values (in screen)
frame.set(trackHandle.getLeft(), ((ViewGroup) trackHandle.getParent()).getTop(), trackHandle.getRight(), ((ViewGroup) trackHandle.getParent()).getBottom());
if (!mTracking && !frame.contains((int) x, (int) y)) {
return false;
}
//...
}
I also added a setter for the mTrackHandle attribute to set, during the activity creation, the real hanlde to use:
protected void onCreate(Bundle savedInstanceState) {
//...
mSlidingDrawer.setTrackHandle((View) findViewById(R.id.menu_handle_TextView_Title));
//...
}
After that you can set standard listener on your two buttons. They will work like a charm.
in response to Joakim Engstrom:
Yes that's possible!
to do that you have to override onInterceptTouchEvent as follow.
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
rect = new Rect(handle.getLeft(), ((View) handle.getParent()).getTop(),
handle.getRight(), ((View) handle.getParent()).getBottom());
if (!rect.contains((int) event.getX(), (int) event.getY())) {
if (event.getAction() == MotionEvent.ACTION_UP)
this.lock();
else
this.unlock();
return false;
} else {
this.unlock();
return super.onInterceptTouchEvent(event);
}
}
you have also to add a setter to set handle to actual handle view during activity creation.
may be this code can help you
https://github.com/xPutnikx/SlidingDrawerWithButtons