Sending a touch event from look to his brother WebView

Resuming a problem: send a touch event to the layout of his brother WebView, getting the same scroll as the default WebView scroll (discard)

I have a frameLayout over a WebView after this xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.app.ObservableWebView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/observableWebView"> </com.app.ObservableWebView> <FrameLayout android:layout_width="match_parent" android:layout_height="200dp" android:id="@+id/frameLayout"> </FrameLayout> </RelativeLayout> 

At the beginning of the application, an empty empty place for html placement is placed somewhere in the WebView Dom, and its position is set by JavascriptInterface. This position is converted with a pixel ratio, and a FrameLayout is placed above the frameLayout. When the contents of the WebView are moved, the placeholder is moved using FrameLayout (an event dispatched from an Observable WebView). So far, everything is working as it should.

layout schema

In frameLayout, I need to listen for a click on it, so I installed TouchListener after this step:

 private boolean mIsNativeClick = false; private float mStartNativeX; private float mStartNativeY; private final float SCROLL_THRESHOLD = 10; private void init(){ mRootFrameLayout.setOnTouchListener(this); } @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: mStartNativeX = event.getX(); mStartNativeY = event.getY(); mIsNativeClick = true; return true; case MotionEvent.ACTION_MOVE: if (mIsNativeClick && (Math.abs(mStartNativeX - event.getX()) > SCROLL_THRESHOLD || Math.abs(mStartNativeY - event.getY()) > SCROLL_THRESHOLD)) { mIsNativeClick = false; } break; case MotionEvent.ACTION_UP: if (mIsNativeClick) { // my action on touch up goes here return true; } } return false; } 

The touch event arrives correctly for the listener, and everything works fine. Except that the WebView does not scroll when I view FrameLayout, because it is a brother of frameLayout: it is not a parent / child.

The obvious solution would be to set the return true / false correlation for View.onTouchEvent or in the listener so that Android sends the event to the next view in the three lightpad trees. But since I need to handle the up / down event in FrameLayout, starting to return true for MotionEvent.ACTION_DOWN to stop sending the next event.

Searching StackOverFlow for the past week, I have achieved a solution that works at 50%. Describe this:


Step 1: creating a WebView scroll in a FrameLayout move event

The solution consists of capturing the event on FrameLayout and setting the scroll position of the WebView according to the movement / scroll event. The problem with this solution is that the scroll fling event is not controlled (fling: when the user swipes and removes his finger from the screen, it produces an inertial effect, the scroll continues to move for several ms). Adding a reset is explained in step 2.

This is a piece of code: (part for the custom FrameLayout view)

 private int movY; private float mStartNativeX; private float mStartNativeY; @Override public boolean onInterceptTouchEvent ( MotionEvent event ) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mStartNativeX = event.getX(); mStartNativeY = event.getY(); break; case MotionEvent.ACTION_SCROLL: Log.d(LOG_TAG, " scroll event"); break; } return super.onInterceptTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.d(LOG_TAG, "down event : " + Float.toString(mStartNativeY)); break; case MotionEvent.ACTION_MOVE: movY = (int) ((int) mStartNativeY - event.getY()); mWebView.scrollBy(0, movY); Log.d(LOG_TAG, "move event : " + Integer.toString(movY)); return true; case MotionEvent.ACTION_UP: Log.d(LOG_TAG, "up event : "); break; default: break; } return true; } 

Step 2: launch

Adding a transition to manual scrolling. It is not so easy. The main thing is to add a GestureDetector and Scroller. So I have a class that implements Runnable with a Scroller that controls the fling effect. This class is called when the GestureDetector listener is turned on.

 private float mStartNativeY; private GestureDetector mGestureDetector; int movY; @Override public boolean onInterceptTouchEvent ( MotionEvent event ) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mStartNativeY = event.getY(); break; case MotionEvent.ACTION_SCROLL: Log.d(LOG_TAG, " scroll event"); break; } return super.onInterceptTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { mGestureDetector.onTouchEvent(event); // 1.) remember DOWN event ALWAYS as this is important start for every gesture switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.d(LOG_TAG, "down event : " + Float.toString(mStartNativeY)); break; case MotionEvent.ACTION_MOVE: movY = (int) ((int) mStartNativeY - event.getY()); mWebView.scrollBy(0, movY); Log.d(LOG_TAG, "move event : " + Integer.toString(movY)); return true; case MotionEvent.ACTION_UP: Log.d(LOG_TAG, "up event : " + Integer.toString(mWebView.getScrollY())); return false; default: break; } return true; } // GestureDetector Listener @Override public void onLongPress(MotionEvent e) { } @Override public boolean onDown(MotionEvent e) { Log.d(LOG_TAG, "onDown"); return true; // else won't work } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float velocitX, float veloctiyY){ return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { new Flinger().start((int)velocityY); invalidate(); return true; } @Override public void onShowPress(MotionEvent e) { } @Override public boolean onSingleTapUp(MotionEvent e) { return false; } private class Flinger implements Runnable { private final Scroller scroller; private int lastY = 0; Flinger() { scroller = new Scroller(getContext()); } void start(int initialVelocity) { int initialY = mWebView.getScrollY(); int maxY = Integer.MAX_VALUE; // or some appropriate max value in your code scroller.fling(0, initialY, 0, initialVelocity, 0, 10, 0, maxY); Log.i(LOG_TAG, "starting fling at " + initialY + ", velocity is " + initialVelocity + ""); lastY = initialY; mWebView.post(this); } public void run() { if (scroller.isFinished()) { Log.i(LOG_TAG, "scroller is finished, done with fling"); return; } boolean more = scroller.computeScrollOffset(); int y = scroller.getCurrY(); int diff = lastY - y; Log.d(LOG_TAG, "finger run : lasty : " + lastY +" y: " + y + " diff: "+Integer.toString(diff)); if (diff != 0) { mWebView.scrollTo(0, scroller.getCurrY()); lastY = y; } if (more) { mWebView.post(this); } } boolean isFlinging() { return !scroller.isFinished(); } void forceFinished() { if (!scroller.isFinished()) { scroller.forceFinished(true); } } } 

Problem: The throw does not work every time it should. therefore, if I start throwing at a speed of 1009, the magazine says:

 starting fling at 3032, velocity is 1009 up event : 3032 finger run : lasty : 3032 y: 3047 diff: -15 finger run : lasty : 3047 y: 3063 diff: -16 finger run : lasty : 3063 y: 3078 diff: -15 finger run : lasty : 3078 y: 3090 diff: -12 finger run : lasty : 3090 y: 3102 diff: -12 finger run : lasty : 3102 y: 3106 diff: -4 finger run : lasty : 3106 y: 3110 diff: -4 finger run : lasty : 3110 y: 3113 diff: -3 finger run : lasty : 3113 y: 3116 diff: -3 finger run : lasty : 3116 y: 3118 diff: -2 finger run : lasty : 3118 y: 3119 diff: -1 finger run : lasty : 3119 y: 3120 diff: -1 

According to the magazine, the throw theory works, but the starting scroll point (-15) is not enough, it should be greater than ~ 100


EDIT: Alternative Solution: Posting an Event in WebView

Another explanation in the comment should be to send a MotionEvent from FrameLayout to your WebView website using

 mFrameLayout.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouchEvent(MotionEvent ev) { return mWebView.onTouchEvent(MotionEvent ev); } }); 

The problem with this solution is that the scroll distance does not match the default WebView onTouchEvent parameter. It is too slow: the distance when it scrolls is less than it should be, and <<strong> flicker appears.


Any advice or other decision is accepted.

+6
source share
1 answer

Each view has its own onTouchEvent method, which can be called independently.

So, in order to pass the touch event to the sibilization, we just need to call sibiling onTouchEvent with the MotionEvent parameter of the first kind.

 yourView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouchEvent(MotionEvent ev) { //a sibiling MUST be final to be used in an OnTouchListener //if it not, create a new final reference to it return sibiling.onTouchEvent(MotionEvent ev); } }); 

UPDATE:

According to the conversation in the comments, you want to synchronize scrolls from two different kinds of different dimensions. Being different sizes, just passing a MotionEvent to a webView is not enough. But you can change the coordinates of the MotionEvent X and Y before sending it for synchronization using this method:

 ev.setLocation(float x, float y); 

You will need to scale the X and Y fragment according to the size of your fragment and your WebView. I don’t know what type of scrolling thus scales you exactly , so the mathematical algorithm may differ from what I wrote below (but this is up to you anyway). If this is the case, consider this as a demonstration of how to scale a MotionEvent .

 //you first need all dimensions //if they're changing during runtime, you can do all this in OnTouchListener final int fragWidth = fragment.getWidth(); final int fragHeight = fragment.getHeight(); final int webWidith = webView.getWidth(); final int webHeight = webView.getHeight(); //then you calculate scale factors final float scaleX = (float)webWidth / (float)fragWidth; final float scaleY = (float)webHeight / (float)fragHeight; //same old OnTouchListener fragment.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouchEvent(MotionEvent ev) { //calculate scaled coordinates float newX = ev.getX() * scaleX; float newY = ev.getY() * scaleY; //MODIFY the MotionEvent by setting the scaled coordinates ev.setLocation(newX, newY); //call WebView onTouchEvent with the modified MotionEvent return webView.onTouchEvent(MotionEvent ev); } }); 
+2
source

Source: https://habr.com/ru/post/980027/


All Articles