So hi there.
I have a simple Layout with 2 Views in it. Both have an onTouchListener attached.
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
System.out.println("Touching");
return false;
}
});
But when I open the application on my phone and touch the first view and do NOT relase my finger and touch the second view with another finger, the second view wont trigger the touch event. why is this so?
I think in this case both touches are passed to the first view as a multi-touch event. So this is one event but contains (I forgot the details) both touch positions.
Related
I wish to create a simple game for Android, where the player will be shown a table of buttons. He should have the ability to swipe/drag his fingers over some buttons, and the result of his move should only be displayed after he stops touching the screen.
I can see when Action_UP and Action_Down is called, but I can't use Action_HOVER_MOVE:
View.OnTouchListener OTL = new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction()==MotionEvent.ACTION_DOWN
|| motionEvent.getAction()==MotionEvent.ACTION_HOVER_MOVE)
{
Button B = (Button)view;
if (B.isActivated())
{
String letter = B.getText()+"";
word+=letter;
}
B.setActivated(false);
Toast t = Toast.makeText(MainActivity.this,word,Toast.LENGTH_SHORT);
t.show();
}
if (motionEvent.getAction()==MotionEvent.ACTION_UP)
{
Toast t = Toast.makeText(MainActivity.this,word,Toast.LENGTH_SHORT);
t.show();
word="";
resetButtons();
}
return true;
}
};
How to detect swipe over other buttons?
Per the docs, you probably want ACTION_MOVE
int ACTION_MOVE
Constant for getActionMasked(): A change has happened during a press gesture (between ACTION_DOWN and ACTION_UP). The motion contains the most recent point, as well as any intermediate points since the last down or move event.
You're having trouble with ACTION_HOVER_MOVE because:
int ACTION_HOVER_MOVE
Constant for getActionMasked(): A change happened but the pointer is not down (unlike ACTION_MOVE). The motion contains the most recent point, as well as any intermediate points since the last hover move event.
This action is always delivered to the window or view under the pointer.
This action is not a touch event so it is delivered to onGenericMotionEvent(MotionEvent) rather than onTouchEvent(MotionEvent).
Source: https://developer.android.com/reference/android/view/MotionEvent.html#ACTION_MOVE
I a using a Fragment which extends a ListFragment, this then uses an ArrayAdapter in order to create a dynamic list of custom rows (this is all working perfectly).
What I am now trying to do is implement an onTouch event where when the user swipes each row left of right then do something. This logic is all working the only issue is I am having to do the onTouch event as the ACTION_UP does not seem to get called first time.
#Override
public void onListItemClick(ListView l, View v, final int position, long id) {
super.onListItemClick(l, v, position, id);
v.setOnTouchListener(new View.OnTouchListener() {
int initialX = 0;
final float slop = ViewConfiguration.get(getActivity()).getScaledTouchSlop() * 1.5f;
#Override
public boolean onTouch(final View v, MotionEvent event) {
switch(event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
initialX = (int) event.getX();
return true;
case MotionEvent.ACTION_UP:
// Do something here
return false;
}
return false;
}
});
}
I am returning true from the ACTION_DOWN so from my understanding this should indicate to the onTouch event to continue processing touch events and in my above code this will be the ACTION_UP when the user lifts his finger from the screen, not sure what I am missing here.
Further to this if at all possible I would also like to bind an onClick event onto the same view (not essential as already have workaround if I can get above working).
You must have used OnTouchListener listener for swiping each rows of listView, make sure that you are returning false on ACTION_UP event.
Parent view receives a call back only when child views have not consumed it. So if each rows of list view is having a onTouchListener, you have to propagate event to parent by returning false.
Hope it will help.
I have an ImageView overlay inside of a RelativeLayout and want to prevent any clicks from going through the ImageView to the Buttons etc that are behind it (thus in effect disabling the entire RelativeLayout).
Is the a simpler way of doing this then iterating the RelativeLayout views and setting them to disabled as I currently am doing using this code:
RelativeLayout rlTest = (RelativeLayout ) findViewById(R.id.rlTest);
for (int i = 0; i < rlTest.getChildCount(); i++) {
View view = rlTest.getChildAt(i);
view.setEnabled(true);
}
you can set the image to be
android:clickable="true"
Simply call rlTest.setClickable(false). This will prevent the click to be propagate to the children
There is a much cleaner way
You can use:
android:onClick="preventClicks"
in XML and in your Activity
public void preventClicks(View view) {}
This works with fragments.
Example inside this Activity has multiple fragments overlapping one another, just by adding the XML attribute in the background of your fragment it will still call the Activity.preventClicks and will prevent touches on fragments behind it
The following solution works in the general case:
_container.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// NOTE: This prevents the touches from propagating through the view and incorrectly invoking the button behind it
return true;
}
});
It basically blocks any touches from propagating through the view by marking the touch event as handled.
This works on both UI controls and layout containers (ie: LinearLayout, FrameLayout etc.).
The solution to set "clickable" as false did not work for me for layout containers either in code or in the view XML.
I assume that you are using onClickListeners.
How about using onTouchListener instead of onClickListeners. By doing this you will have a control over how deep down in your hierarchy the touch even can be visible. For example, if you have toch listeners on a relative-layout(RL) and a image-view(IV)(contained in RL), and you assign touchListeners to both. Now if you return true from IV's touch event, the lower down member RL will not receive the touch event. However if you return false from from IV's touch event, the lower down member RL will receive the touch event.
Hope this helps!
Just add these two listeners:
// Set an OnTouchListener to always return true for onTouch events so that a touch
// sequence cannot pass through the item to the item below.
view.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
v.onTouchEvent(event);
return true;
}
});
// Set an OnHoverListener to always return true for onHover events so that focus cannot
// pass through the item to the item below.
view.setOnHoverListener(new OnHoverListener() {
#Override
public boolean onHover(View v, MotionEvent event) {
v.onHoverEvent(event);
return true;
}
});
You could use databindings and consume the clicks like this:
android:onClick="#{() -> true}"
In C#, I use an empty delegate:
objectName.Click += delegate {};
I haven't encountered any problems with it but it does prevent clicks from filtering through to underlying controls.
you can also se the root click listener to null:
// Do not process clicks on other areas of this fragment
binding.root.setOnClickListener(null)
This works 100%.
It doesnt affect other listeners that are already set on the fragment's views.
I've implemented a grid of buttons using a grid layout. I'm trying to allow for a single swipe to activate multiple buttons. When the user touches any one of the buttons, I call a function with the specific button pressed to take the according action. But, currently, I can only activate one button per touch. Multi-touch works, but not a single swipe. The problem is that while the onTouch function is called continuously the view object that I use to determine the button pressed is only updated on the initial touch. What I need to do is get the id's of all of the buttons that were swiped across.
Thanks.
#Override
public boolean onTouch(View v, MotionEvent event)
{
super.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_MOVE)
{
switch (v.getId())
{
case R.id.padZeroZero:
padTouch(0,0);
break;
case R.id.padZeroOne:
padTouch(0,1);
break;
case R.id.padZeroTwo:
padTouch(0,2);
break;
//There's a lot more cases (it's a 9x8 grid), but they all do the same thing.
}
}
return false;
}
In my application I want to add a simple animation to buttons and other views acting as buttons.
To do this I set a custom onTouchListener to all views and call startAnimation on them.
My onTouch method looks like this:
public boolean onTouch(View v, MotionEvent event) {
// Only show animation when enabled.
if (v.isEnabled()) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
v.startAnimation(shrink);
break;
case MotionEvent.ACTION_UP:
v.startAnimation(grow);
break;
}
}
return v.onTouchEvent(event);
}
This works ok as the views are resized to a smaller size while the user presses the button and returns to the original size when the user releases the finger.
However for some reason other buttons that lie near the touched button also get the UP event so they get a small animation flicker as well.
Why is this, and more importantly, how do I prevent this annoying behaviour?
Edit: Just to be clear. The neighbour views are also registered to the same listener instance, but they are not touched by my finger.
The view to wich you have registered listener then should only get notified then also use following approach.
You can explicitly check what is id of View and then only start animation.
public boolean onTouch(View v, MotionEvent event) {
if (v.getId() == R.id.desired_view_id) {
// Only show animation when enabled.
if (v.isEnabled()) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
v.startAnimation(shrink);
break;
case MotionEvent.ACTION_UP:
v.startAnimation(grow);
break;
}
}
}
return v.onTouchEvent(event);
}
The problem was using the same Animation instance on several views. Creating seperate instances for each view fixed the problem.