I'm having a problem that my method
#Override
public boolean onTouchEvent(MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
is never called. Any ideas why is that so? I'm building a google's API 4.0.3 application, and I'm trying to enable swipes for my ViewFliper. However, it can't work because touch is never called.
Code:
public class MainActivity extends SherlockMapActivity implements ActionBar.TabListener {
Thats the declaration of my activity. and to detect swipes i have implemented that:
SimpleOnGestureListener simpleOnGestureListener = new SimpleOnGestureListener(){
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY) {
float sensitvity = 50;
if((e1.getX() - e2.getX()) > sensitvity){
SwipeLeft();
}else if((e2.getX() - e1.getX()) > sensitvity){
SwipeRight();
}
return true;
}
};
GestureDetector gestureDetector= new GestureDetector(simpleOnGestureListener);
Thank You.
edit:
main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/main_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<ViewFlipper
android:id="#+id/ViewFlipper"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#ffffff" >
<include
android:layout_height="match_parent"
layout="#layout/mymain" />
<include layout="#layout/secondmain" />
<include layout="#layout/thirdmain" />
<include layout="#layout/fourthmain" />
</ViewFlipper>
</LinearLayout>
Edit2: all of my included layouts have scrollview implemented. Is it possible that scroll takes those events and handles them? And how to detect gestures if so?
I found a perfect solution. I implemented new method:
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
View v = getCurrentFocus();
boolean ret = super.dispatchTouchEvent(event);
and now it all works fine!
Edit:
My final code:
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
View v = getCurrentFocus();
if (v instanceof EditText) {
View w = getCurrentFocus();
int scrcoords[] = new int[2];
w.getLocationOnScreen(scrcoords);
float x = event.getRawX() + w.getLeft() - scrcoords[0];
float y = event.getRawY() + w.getTop() - scrcoords[1];
if (event.getAction() == MotionEvent.ACTION_UP
&& (x < w.getLeft() || x >= w.getRight() || y < w.getTop() || y > w
.getBottom())) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getWindow().getCurrentFocus()
.getWindowToken(), 0);
}
}
boolean ret = super.dispatchTouchEvent(event);
return ret;
}
As I wrote in Gabrjan's post's comments that this fires continuously while touching the screen, there's actually an easy way to get only the touch down events:
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
System.out.println("TOUCH DOWN!");
//set immersive mode here, or whatever...
}
return super.dispatchTouchEvent(event);
}
This was very useful to me to put the Android into immersive mode whenever any part of the screen was touched regardless which element. But I didn't wish to set immersive mode repeatedly!
ok, now i'm sure that the problem is that scrollview handle touches, so anyway to ignore that and yet be the scrolling avaiable?
Yes that's the problem, when android handles touch events each event goes from child to parent, so first it's handled by ViewFlipper, but then it goes to ScrollView. So you have to implement getParent().requestDisallowInterceptTouchEvent(true) (see ViewParent class) in order to make all touch events handled by ViewFlipper, and then simply detect the direction of gesture if horizontal then flip view if not then pass touch event to ScrollView or just scroll ScrollView programmatically
EDIT: Also you can implement OnTouchListener in your ViewFlipper and in this listener trigger GestureDetector.onTouchEvent(event), but this also requires requestDisallowInterceptTouchEvent of your parent view set to true
All gestures and touch events goes to the lowest element in view hierarchy who can handle it.
So if you have any listener in included layout you can call ((TypeOfParrent)yourView.getParent()).onTouchEvent(event) to delegate event to the handler you want.
ADD: I recoment you to use ViewPager for flipping views.
In the ViewPager you dont need to implements your own onGestureListener.
http://www.edumobile.org/android/android-beginner-tutorials/view-pager-example-in-android-development/
http://developer.android.com/reference/android/support/v4/view/ViewPager.html
Related
I have a view with a lot of Childs. What I need is to implement reaction on Swipe or on Fling moves. Problem is that it only really works if I remove all Childs, otherwise Child views on the top of the main layout block my attempts to swipe.
I tried both adding onSwipeListener to the main layout and adding GestureListener to the whole activity with the same success.
My current (non-working) solution looks like:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_schedule);
main_layout = findViewById(R.id.schedule_main_view);
Animation fadeInAnimation = AnimationUtils.loadAnimation(this, R.anim.fade);
main_layout.startAnimation(fadeInAnimation);
GestureDetector.SimpleOnGestureListener simpleOnGestureListener =
new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onDown(MotionEvent event) {
return true;
}
#Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
Log.d(null,"Fling");
int dx = (int) (event2.getX() - event1.getX());
// don't accept the fling if it's too short
// as it may conflict with a button push
if (Math.abs(dx) > 20
&& Math.abs(velocityX) > Math.abs(velocityY)) {
if (velocityX > 0) {
Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
Log.d(DEBUG_TAG, "onFling To Right");
} else {
Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
Log.d(DEBUG_TAG, "onFling To Left");
}
return true;
} else {
return false;
}
}
};
shift = getIntent().getIntExtra(WEEK_SHIFT, CURRENT_WEEK);
mDetector = new GestureDetectorCompat(this,simpleOnGestureListener);
unDimScreen();
setupWeek();
}
To repeat: if the activity is in the state when there are no child views on top, it works as intended.
So the question is: what I can do to make activity fetch gestures ignoring the overlying views?
The problem is child views getting touch events and not giving it to the parent.
If you are not using on overlying views clickable events, you can turn off on that views clickable property like view.setClickable(false); ... Then all click events will go it's parent view. If it doesn't works, you can define on touch listener on every overlying views like this:
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
return false;
}
});
UPD:
Here another (right) solution of this problem: https://developer.android.com/training/gestures/viewgroup.html#delegate
Try setting android:clickable="true" and android:descendantFocusability="blocksDescendants" to the view you want to swipe in the xml file. This should block children from receiving click events.
I'm using an on touch listener to display and hide some volume controls, on ACTION_DOWN the controls are displayed and on ACTION_UP they are hidden. I want to be able to touch the controls without lifting my finger, I tried using the ACTION_MOVE motion and was unable to get it to work as the event is never triggered. I thought about drag event but I am unsure if it would be appropriate for this.
public boolean onTouch(View v, MotionEvent e)
{
if(v == audioControls)
{
if(e.getAction() == MotionEvent.ACTION_DOWN)
showVolumeControls();
else if(e.getAction() == MotionEvent.ACTION_UP)
hideVolumeControls();
}
else if(e.getAction() == MotionEvent.ACTION_MOVE)
{
if(v == mute)
//Do stuff with this volume control
}
return true;
}
#Demand answer, read my comment - here is the code:
public boolean onTouch(View v, MotionEvent e)
{
if(v == mute && e.getAction() == MotionEvent.ACTION_MOVE)
{
Toast.makeText(getApplicationContext(), "Muted.", Toast.LENGTH_SHORT).show();
hideVolumeControls();
return true;
}
else
return false;
}
So, you need to uderstand how android touch events work. If you touch down on View1, set onTouchListener for that view and return true for that event - other view will never get motion events from same chain.
For you it's mean that if you touch down on "audioControls" then no other views can catch motion events until you release your finger.
You can return false in your onTouch method. In this case parentView for audioControls will also catch all motionEvents. But views, which is not parent for audioControls in the view hierarchy will not catch motionEvent.
You need to catch all motion events in the container for your views and dispatch them for your self. This is the only way to catch motionEvents from one chain in defferent views.
UPDATE:
I will try to explain a little bit more.
Imagine you have layout like this:
<LinearLayout id="#+id/container">
<View id="#+id/view1"/>
<View id="#+id/view2"/>
</LinearLayout>
And you want to touch down on view1 and move your finger to view2. Android touch event flow can't do this. Only one view can catch whole event chain.
So you need to add onTouchListener to your container and do something like this.
public boolean onTouch(MotionEvent event) {
float x = event.getX();
float y = event.getY();
for (int i = 0; i<container.getChildCount(); i++) {
View child = container.getChildAt(i);
if (x>child.getLeft() && x < child.getRight() && y < child.getBottom() && y > child.getTop()) {
/*do whatever you want with this view. Child will be view1 or view2, depends on coords;*/
break;
}
}
}
Please note, I wrote this code right there and could make some mistake. I've tried to show the idea.
Looks like setting RecyclerView's item layout to clickable="true", consume some touch events completely, particulary MotionEvent.ACTION_DOWN (ACTION_MOVE and ACTION_UP afterwards are working):
item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/demo_item_container"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"> <-- this what breaks touch event ACTION_DOWN
....
</LinearLayout>
Having very basic RecyclerView setup in onCreate():
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
... //Standard recyclerView init stuff
//Please note that this is NOT recyclerView.addOnItemTouchListener()
recyclerView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Log.d("", "TOUCH --- " + motionEvent.getActionMasked());
//Will never get here ACTION_DOWN when item set to android:clickable="true"
return false;
}
});
Is this intended behaviour or bug in RecyclerView cause it is still a preview?
PS. I want this to be clickable as per docs to react on pressed state and have ripple effect on click. When set to false ACTION_DOWN is working fine but pressed state is not triggered and selectableBackground does not have any effect.
This is intended behaviour NOT a bug.
When set item clickable true, ACTION_DOWN will be consumed, recycler view
will NEVER get ACTION_DOWN.
Why are you need ACTION_DOWN in onTouch() of recycler view? Does it necessary?
if you want to set lastY in ACTION_DOWN, why not this
case MotionEvent.ACTION_MOVE:
if (linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
// initial
if (lastY == -1)
lastY = y;
float dy = y - lastY;
// use dy to do your work
lastY = y;
break;
case:MotionEvent.ACTION_UP:
// reset
lastY = -1;
break;
Does it you want to? if you still want the ACTION_DOWN, try to get it in activity, such as:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN)
lastY = ev.getRawY();
return super.dispatchTouchEvent(ev);
I have a ScrollView on top of another view(with Buttons). The ScrollView is taking the whole screen and is obscuring the view that is beneath it.
At some point in my app I need the ScrollView to be disabled (but still visible) and transfer all the touch events to the Buttons that are beneath the ScrollView. How can I do that? Some views like Buttons are automatically doing that when disabled but a ScrollView is not doing that.
Try to implement your own ScrollView which has a flag to indicate the status(disabled/enabled) and also overrides the onTouchEvent and dispatchTouchEvent to let the touch events get pass the ScrollView. Here is an example:
public class DisabledScrollView extends ScrollView {
private boolean mIsDisable = false;
// if status is true, disable the ScrollView
public void setDisableStatus(boolean status) {
mIsDisable = status;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
// no more tocuh events for this ScrollView
if (mIsDisable) {
return false;
}
return super.onTouchEvent(ev);
}
public boolean dispatchTouchEvent(MotionEvent ev) {
// although the ScrollView doesn't get touch events , its children will get them so intercept them.
if (mIsDisable) {
return false;
}
return super.dispatchTouchEvent(ev);
}
}
Then all you have to do is change the value of that flag. See if it works.
In my case, I just needed to handle the touch event in View A, which was overlaping View B and then send the event to View B. Both views were child of the same RelativeLayout, but there was no parent-child relation between views A and B. This worked for me:
viewA.setOnTouchListener( new OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
// do my stuff here
viewB.dispatchTouchEvent( event );
}
}
In this case I have a recyclerview under a scrollview. The top of scrollview is in vertical scroll, and the recyclerview is in horizontal scroll. The scrollview have top padding, making the recyclerview is visible through the transparency in the scrollview padding. I have to make it this way because when the scrollview is scrolled the recyclerview will scroll vertically to like parallax effect (this effect is in another code). This code below is working for my case, might help
scrollView.setOnTouchListener(new View.OnTouchListener() {
float mDownX,mDownY;
boolean mIsSwiping,isDown;
#Override
public boolean onTouch(View v, MotionEvent event) {
if(mIsSwiping){
recyclerView.dispatchTouchEvent(event);
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mIsSwiping = false;
isDown = true;
mDownX = event.getX();
mDownY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
if(isDown){
float deltaX = Math.abs(event.getX() - mDownX);
float deltaY = Math.abs(event.getY() - mDownY);
mDownX = event.getX();
mDownY = event.getY();
if(deltaX!=deltaY){
isDown = false;
if(deltaX>deltaY){
mIsSwiping = true;
}
}
}
}
return mIsSwiping;
}
});
This is the layout
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/gray0"
android:clipToPadding="false"
android:clipChildren="false"
android:paddingBottom="70dp">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="320dp"
android:orientation="horizontal"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"/>
<ScrollView
android:id="#+id/scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="301.75dp"
android:paddingBottom="23.5dp"
android:clipChildren="false"
android:clipToPadding="false">
.
.
.
I am using view pager to swipe between the views in Android.
Now I need to capture tap event for each of the views. when I override the touch listener to capture the tap event, the swipe action doesn't happen and the screen remains in the first page itself. How do I add touch listener to view pager?
Code:
viewPager.setOnTouchListener(new OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event)
{
mDetector.onTouchEvent(event);
return true;
}});
For the above code I am able to capture tap event, but the swipe action becomes Impossible.
Here i leave you a snippet from my code to detect a "click" on the OnTouchListener, i hope it helps
mImagePager.setOnTouchListener(new OnTouchListener() {
private float pointX;
private float pointY;
private int tolerance = 50;
#Override
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_MOVE:
return false; //This is important, if you return TRUE the action of swipe will not take place.
case MotionEvent.ACTION_DOWN:
pointX = event.getX();
pointY = event.getY();
break;
case MotionEvent.ACTION_UP:
boolean sameX = pointX + tolerance > event.getX() && pointX - tolerance < event.getX();
boolean sameY = pointY + tolerance > event.getY() && pointY - tolerance < event.getY();
if(sameX && sameY){
//The user "clicked" certain point in the screen or just returned to the same position an raised the finger
}
}
return false;
}
});
We can use Gestures (Link1, Link2):
public boolean onTouchEvent (MotionEvent ev)
Hope this helps!
Nancy, you don't need to manually override the Page swipes or the touch events. Just add the pages to the ViewPager and the ViewPager will automatically take care of swiping.
You do, however, have to attach touch listeners to the object in each page. So if Page 1 has a Linear Layout with many buttons and you need to find out when those buttons are clicked, you need to attach OnClickListeners for each of those buttons.
Do let me know your use case so we can better understand, why you need to find out when a page has been clicked!
Just to add to Jorge's great answer, you may just use distance instead of sameX and sameY, which is a bit more elegant. Sample:
// Ignore events that are swipes rather then touches
float distX = event.getX() - pointX;
float distY = event.getY() - pointX;
double dist = Math.sqrt(distX * distX + distY * distY);
if (dist > tolerance) {
return false;
}
Put the click event on the item view of the viewpager inside the viewPagerAdapter in the method instantiateItem like -
#Override
public Object instantiateItem(ViewGroup container, final int position) {
// Declare Variables
ImageView jive_image;
inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View itemView = inflater.inflate(R.layout.list_item_viewpager, container,
false);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
onBackPressed();
}
});
// Add viewpager_item.xml to ViewPager
((ViewPager) container).addView(itemView);
return itemView;
}