I have a WebView in my RecyclerView, and the WebView loaded a html page. The part of the page can be horizontal scrolled, but when I horizontal scroll, the RecyclerView take the touch event easily and start to scroll vertically.
How can I solve this ?
Finally, I have figured out the solution about my question. The part of the WebView is in the top of the view. so I use this to intercept the touch event:
float touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop()
webView.setOnTouchListener((View v, MotionEvent event) -> {
if (!scrollFlag && event.getY() < getHeight() / 2) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
if (Math.abs(event.getY() - downY) < touchSlop && Math.abs(event.getX() - downX) > touchSlop) {
getParent().requestDisallowInterceptTouchEvent(true);
scrollFlag = true;
}
break;
}
}
if (event.getAction() == MotionEvent.ACTION_UP)
scrollFlag = false;
return false;
});
Related
I have WebView(s) inside the RecyclerView. In order to get smooth scrolling experience such that when user scrolls, the RecyclerView will be responsible for scrolling (WebView should not scroll) I called getParent().requestDisallowInterceptTouchEvent(false); inside webview#onTouchEvent(event) when there is only one touch point and is moving vertcially (scrolling up and down).
private void handleSingleFingerTouch(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
x1 = ev.getX();
y1 = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
x2 = ev.getX();
y2 = ev.getY();
// -ve param for canScrollHorizontally is to check scroll left. +ve otherwise
if (Math.abs(x1 - x2) >= Math.abs(y1 - y2)
&& canScrollHorizontally((int) (x1 - x2))) {
// scrolling horizontally. Retain the touch inside the webView.
getParent().requestDisallowInterceptTouchEvent(true);
} else {
// scrolling vertically. Share the touch event with the parent.
getParent().requestDisallowInterceptTouchEvent(false);
}
x1 = x2;
y1 = y2;
}
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
boolean multiTouch = ev.getPointerCount() > 1;
if (multiTouch) {
getParent().requestDisallowInterceptTouchEvent(true);
} else {
handleSingleFingerTouch(ev);
}
return super.onTouchEvent(ev);
}
It works as expected with just one bug, I found that while RecyclerView(and webview) scrolling and I touch inside the WebView, then RecyclerView stops scrolling as expected, then if I don't lift up my finger but keep finger on the screen and try to zoom, the webview would not zoom and actually it wouldn't receive touch event at all. I have to lift my fingers and touch again to zoom. I know this is because getParent().requestDisallowInterceptTouchEvent(false); won't cancel unless UI receive CANCEL or UP event. I tried to implement an interface that call getParent().requestDisallowInterceptTouchEvent(true); when multi-touch happen. Though it did get called, but seems it doesn't work. Zoom still not happen and onTouchEvent inside the WebView still not get triggered. Any idea to solve this?
So basically the solution is to override the onInterceptTouchEvent of the recyclerView
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
// When recyclerview is scrolling this will stop scrolling and allow touch event passed to child views.
if (e.action == MotionEvent.ACTION_DOWN && this.scrollState == RecyclerView.SCROLL_STATE_SETTLING) {
this.stopScroll()
}
return super.onInterceptTouchEvent(e)
}
I want to catch ONLY two fingers touch (one finger touch must be NOT consumed).
Unfortunately, I need to consume the "one finger touch" in order to get the "two finger touch" events.
I hope I'm a clear.
Here my code :
#Override
public boolean onTouch(View v, MotionEvent m)
{
boolean consumed = false;
int pointerCount = m.getPointerCount();
int action = m.getActionMasked();
if(pointerCount == 2)
{
switch (action)
{
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
consumed = true;
break;
case MotionEvent.ACTION_UP:
// Work
break;
case MotionEvent.ACTION_MOVE:
// Some stuff
break;
default:
break;
}
}
else if (pointerCount == 1 && action == MotionEvent.ACTION_DOWN)
{
// This is needed to get the 2 fingers touch events...
consumed = true;
}
return consumed;
}
I've ran into a similar problem and solved my problem by handling all touches at my top view, and propagating the appropriate actions to the views below. In my example, I had an overlay layer that needed to intercept scale gestures (a recognizer is attached in my constructor), while still propagating single touches to my map view below. I haven't sent the actual touch event to my map view, but I've handled it in my top view and sent the appropriate "panning" action to my view below.
#Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getPointerCount() == 2) {
if(event.getActionMasked() == MotionEvent.ACTION_DOWN){
CameraUpdate update = CameraUpdateFactory.newLatLng(initialCoords);
//attachedMapFragment.getMap().moveCamera(update);
}
scaleGestureDetector.onTouchEvent(event);
}else{
if(attachedMapFragment != null) {
if(event.getActionMasked() == MotionEvent.ACTION_DOWN){
initialCoords = attachedMapFragment.getMap().getCameraPosition().target;
lastTouchX = event.getX();
lastTouchY = event.getY();
}else if(event.getActionMasked() == MotionEvent.ACTION_MOVE){
float deltaX = event.getX() - lastTouchX;
float deltaY = event.getY() - lastTouchY;
lastTouchX = event.getX();
lastTouchY = event.getY();
Projection projection = attachedMapFragment.getMap().getProjection();
Point center = projection.toScreenLocation(attachedMapFragment.getMap().getCameraPosition().target);
Point newCenter = new Point(center.x - (int)deltaX, center.y - (int)deltaY);
LatLng newCoords = projection.fromScreenLocation(newCenter);
CameraUpdate update = CameraUpdateFactory.newLatLng(newCoords);
attachedMapFragment.getMap().moveCamera(update);
}
}
}
return true;
}
This way, I had a scale recognizer on my custom view, while the map was still panning appropriately to single touch. Actually, map view wasn't getting any touches at all, my custom view was "routing" the action as seen above. While not always ideal, this would solve the problem in many cases.
I am trying to use ViewPager for showing different views.
Below is code I am using to check whether swiping is left or right
myPager.setOnTouchListener(new ViewPager.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
float x = event.getX();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
mStartDragX = x;
break;
case MotionEvent.ACTION_MOVE:
if (event.getX() > mStartDragX ) {
Log.i(TAG,"SWIPING RIGHT");
} else if (event.getX() < mStartDragX) {
Log.i(TAG,"SWIPING LEFT");
}
break;
}
return false;
}
});
So when I start to swipe from right to left or right to left its showing me correct log message.
But if I started to swipe from right to left and at the same time again if start to swipe from left to right, how can I identify whether currently swiping in which direction?
Can anyone please help ?
Thanks
Then do somethink like this..
case MotionEvent.ACTION_MOVE:
if (event.getX() > mStartDragX) {
Log.i(TAG, "SWIPING RIGHT");
mStartDragX = event.getX();
} else if (event.getX() < mStartDragX) {
Log.i(TAG, "SWIPING LEFT");
mStartDragX = event.getX();
}
Here i am saving the current position again to old position so whenever the swiped back it will give the correct result..
I would like to set up following layout with working scroll and click events:
My CustomSlidingDrawer.xml:
<com.project.util.CustomSliderDrawer
android:id="#+id/slidingDrawerHotelList"
android:layout_width="0dp"
android:layout_height="match_parent"
android:allowSingleTap="false"
android:content="#+id/content"
android:handle="#+id/handle"
android:orientation="horizontal" >
<RelativeLayout
android:id="#+id/handle"
android:layout_width="0dp"
android:layout_height="match_parent" >
<RelativeLayout
android:id="#+id/content"
android:layout_width="0dp"
android:layout_height="0dp" >
</RelativeLayout>
</RelativeLayout>
</com.project.util.CustomSliderDrawer>
The width of the elements are set during runtime.
Here I have to mention, that my real content is in the handle of the Drawer, and the content is only a dummy. I'm doing that in this way, because I was not able to hide the handle. Or make it invisible. So, if I want to swipe in I touch the handle (my content) and pull. In the SlidingDrawer, the handle is a placeholder for a fragment which contains a CustomListView.
What now works: I can scroll the SlidingDrawer.
What behavior I would like to gain:
On the SlidingDrawer the MotionEvents are detected in X-Axis direction (Vertical swipe). If a MotionEvent in Y-Axis direction is detected, then the Events shoud go to my CustomListView. Even the onClicks should pass through to the ListView.
What I've tried: I read a lot: here, this page and this one
Then I tried to put all the navigation control in the main activity, what makes really sense in my eyes:
MainActivity:
slidingDrawer.setOnTouchListener(new OnTouchListener() {
int downX, downY;
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downX = (int) event.getX();
downY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int deltaX = downX - (int) event.getX();
int deltaY = downY - (int) event.getY();
if (Math.abs(deltaX) > Math.abs(deltaY)) {
Log.i(TAG, "scrollX direction");
isVerticalSwipe = true;
} else {
Log.i(TAG, "scrollY direction");
isVerticalSwipe = false;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return false;
}
});
But when I try here to return "isVerticalSwipe" as local, the SlidingDrawer works not properly anymore. :-(
The 2nd try was to intercept the touch events in the custom sliding drawer:
CustomSlidingDrawer:
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "onInterceptTouchEvent ACTION_DOWN");
xDistance = yDistance = 0f;
downX = (int) ev.getX();
downY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "onInterceptTouchEvent ACTION_MOVE");
final int curX = (int) ev.getX();
final int curY = (int) ev.getY();
xDistance += Math.abs(curX - downX);
yDistance += Math.abs(curY - downY);
downX = curX;
downY = curY;
if (xDistance > yDistance)
return false;
}
return super.onInterceptTouchEvent(ev);
}
Here happens only the DOWN event and the MOVE event is never reached.
I also made some trys with "dispatchTouchEvent" but I got no success.
I know, that the events will pass through the "layer" of my views - from top to bottom.
So it would be perfect to know, how I can implement the construct from the first link above in my CustomSlidingDrawer (= top view) and when a vertical swipe is detected keep and handle it and if it's a horizontal swipe, then please send it to the CustomListView and perform there the events.
Any help, thoughts, ideas or code snippets are appreciated. :)
Thank you!
[EDIT]
I've made some progress. I get now my events from the SlidingDrawer down to my ListView. I can scroll and click in the list - that works fine. But unfortunately, my SlidingDrawer shows no reaction anymore. These lines are in my custom SlidingDrawer:
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
gestureDetector = new GestureDetector(new MyGestureDetector());
if (gestureDetector.onTouchEvent(event)) {
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP
|| event.getAction() == MotionEvent.ACTION_CANCEL) {
return false;
} else {
Log.i(TAG, "gestureDetector returned false");
return false;
}
}
class MyGestureDetector extends SimpleOnGestureListener {
#Override
public boolean onDown(MotionEvent e) {
Log.i(TAG, "onDown");
downX = (int) e.getX();
downY = (int) e.getY();
return false;
}
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
float deltaX = downX - e2.getX();
float deltaY = downY - e2.getY();
if (Math.abs(deltaX) > Math.abs(deltaY)) {
Log.i(TAG, "scrollX direction");
return true;
} else {
Log.i(TAG, "scrollY direction");
return false;
}
}
}
I don't really understand why it's not working, if there is a swipe in x-direction, then my Drawer should handle the gesture, bc I return true. Instead every event is forwarded to the ListView.
I tried to run "dispatchTouchEvent" before the "onInterceptTouchEvent" but I had no success. Maybe there is a better solution instead of using a SlidingDrawer for moving views by finger?
Okay, after a lot of tears, sweat and blood I finally found a solution which works for me: I don't take a SlidingDrawer anymore and move my layout via changing the LayoutParams.
Over the "dispatchTouchEvent" I get access to my list.
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mGestureDetector.onTouchEvent(ev)) {
return true;
}
if (ev.getAction() == MotionEvent.ACTION_UP) {
Log.i(TAG, "ACTION_UP");
Log.i(TAG, "getLeft: " + getLeft());
if (getLeft() < 280) {
// run ani to left
} else {
// run ani to right
}
}
return super.dispatchTouchEvent(ev);
}
And in my GestureDetector I handle in the overridden onScroll-function the layout translation. If someone has need for the detector don't hesitate to ask, I'll share.
I have a ViewPager which uses GridViews for pages. I would like the ViewPager to switch pages when I swipe across the screen.
The problem is that swipes are not detected when they are made across the GridView. Outside of the GridView, the swipes work correctly; it seems that the GridView is trapping all touch events without passing it to ViewPager first.
While fiddling with the source code, I did this to a custom class extended from GridView:
#Override
public boolean onTouchEvent(MotionEvent event) {
return pager.onInterceptTouchEvent(event);
}
-- where pager refers to the ViewPager class. With this, ViewPager will correctly detect swipes and move pages accordingly, but it doesn't allow GridView to accept any events, so I can't click on the items.
What I would like to be able to do is correctly detect swipes in ViewPager and item clicks on GridView.
I had trouble with colig's implementation, but I was able to get it to work by subclassing ViewPager and overriding the onInterceptTouchEvent() method. I only checked for swipes in the X direction to allow for vertical scrolling if necessary.
private static final int minSwipeDistance = 30;
private float mTouchX;
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean response = super.onInterceptTouchEvent(event);
float x = event.getX();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mTouchX = x;
break;
case MotionEvent.ACTION_MOVE:
float dX = Math.abs(x - mTouchX);
if (dX > minSwipeDistance)
return true;
break;
}
return response;
}
Alix is on the right track. I managed to come up with this simple-looking fix. I'm not entirely sure of how it works, but it does! And for future reference, it works for other kinds of views too -- TableLayout, for example -- not just GridView.
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
x = event.getX();
y = event.getY();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
downX = x;
downY = y;
return super.onInterceptTouchEvent(event);
}
case MotionEvent.ACTION_MOVE: {
deltaX = Math.abs(downX - x);
deltaY = Math.abs(downY - y);
return super.onTouchEvent(event);
}
case MotionEvent.ACTION_UP: {
if (deltaX > 4 && deltaY > 4) {
super.onTouchEvent(event);
}
}
}
return super.onInterceptTouchEvent(event);
}
You can override onInterceptTouchEvent for dispatch evenement where you want