android: ZoomPicker breaks onTouchListener

2019-01-22 10:07发布

问题:

I've got a webview which makes use of the built in zoom controls, as follows:

wv = new WebView(this);
wv.getSettings().setBuiltInZoomControls(true);

Note: this activates two main functions - pinch-to-zoom and invokeZoomPicker() (the latter is only called when a swiping action is done on the view, a simple touch doesn't enable it)


and i also want things to happen when touch events occur, using the following

wv.setOnTouchListener(new View.OnTouchListener() {  
    public boolean onTouch(View v, MotionEvent event) {
        Log.i("touch", "touched!");
        return false;
    }
});

When the WebView loads, and I tap the screen, the log states "touched" every time I interact with the screen, as expected. However, if I do something that would set off invokeZoomPicker() (pinch to zoom doesn't seem to cause this problem, as long as the zoom widget doesn't appear), onTouchListener stops responding to my taps (even after a few seconds, when the widget disappears from view).

To make sure it was invokeZoomPicker(), I edited the second segment of my code as follows:

wv.setOnTouchListener(new View.OnTouchListener() {  
    public boolean onTouch(View v, MotionEvent event) {
        wv.invokeZoomPicker();
        Log.i("touch", "touched!");
        return false;
    }
});

This new onTouch method now only triggers once (as a result of which the zoomwidget appears on the view - and disappears a few seconds later), and then the onTouch method doesn't get called again until the view is reloaded - so it is definitely a problem with the invokeZoomPicker()/Zoom widget

Have I missed some vital piece of code that allows them to coexist, or do I just have to choose which one I can live without?

回答1:

So after running into the same problem myself, I tried to make sense of some of these answers to no avail. The problem at hand is that once the user invokes scroll (not pinch-zoom) (at least for me...), the WebView instance loses its OnTouchListener reference. How did I figure this out? Well...

Webview inherits dispatchTouchEvent() from View. dispatchTouchEvent calls onTouch() (which is the function not firing that should)

The reason why onTouch() wasn't getting called was, as I said before, that the WebView instance's OnTouchListener was getting set to null for some reason. This can be seen by putting a breakpoint in View's dispatchTouchEvent() method

So to solve this, we extend WebView like so:

import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebView;

public class TouchWebView extends WebView {

    WebTouchListener wtl;

    public TouchWebView(Context context) {
        super(context);
    }

    public TouchWebView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public TouchWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void resetTouchListener() {

        if(this.wtl == null) {
            this.wtl = new WebTouchListener();
        }
        this.setOnTouchListener(wtl);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        this.resetTouchListener();
        return super.dispatchTouchEvent(event);
    }
}

And then we implement our OnTouchListener:

import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.webkit.WebView;

public class WebTouchListener implements OnTouchListener {

    @Override
    public boolean onTouch(View v, MotionEvent event) {

        WebView.HitTestResult hr = ((TouchWebView)v).getHitTestResult();
        //Log.i(TAG, "getExtra = "+ hr.getExtra() + "\t\t Type=" + hr.getType());

        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            System.out.println(hr.getExtra() + " " + hr.getType());
        }

        // TODO Auto-generated method stub
        return false;
    }


}

OnTouch will now fire even after zooming or scrolling



回答2:

I had the same problem, but managed to solve it by setting the listener again in my extended WebView class:

public void invalidate() {
    super.invalidate();
    setOnTouchListener(this);
}

Unfortunately, now I have the opposite problem in that the zoom controls (the plus/minus widget) does no longer receive touch events. There seems to be some exclusivity between having an OnTouchListener and zoom, at least in Android SDK level 8 (Froyo 2.2).

This sounds like a bug to me, but would love to find a workaround.



回答3:

Try

youWebView.getSettings().setBuiltInZoomControls(false);

It's work for me.



回答4:

A dirty solution is to cover an invisible view on top of your web view and detects touches there, and you have to distinguish if the touch is consumed, or return false to pass touches to underlying web view.



回答5:

The workaround is pretty simple. You have to create a custom class, that extends one of the ViewGroup subclasses like LinearLayout. Then override onInterceptTouchEvent - it will be called prior to ViewGroup child's onTouchEvent. It is not very elegant to put a webview control into your custom ViewGroup subclass, and then insert it into activity's view hierarchy - where it is desired, but - it works.

private class OwnedLayout extends LinearLayout
{
    protected OwnedLayout(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }

    protected OwnedLayout(Context context)
    {
        super(context);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        //Put Your code here
        return super.onInterceptTouchEvent(ev);
    }


}


回答6:

This is the closest I could come to a solution to this problem. I needed to set the input focus to the WebView whenever it was touched. The only way I could do it was by extending WebView and overriding the onTouchEvent function. This function gets called even after Zooming is used in the WebView.

public class NewWebView extends WebView {

public NewWebView(Context context) {
    super(context);
}

public NewWebView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

public NewWebView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    boolean consumed = super.onTouchEvent(ev);

    if (isClickable())
        if (ev.getAction() == MotionEvent.ACTION_DOWN)
        {
            if (!hasFocus())
                requestFocus();
        }
    return consumed;
}

Don't forget that if you use XML to create your layout, you'll need to change "WebView" to your fully qualified packagename.NewWebView (otherwise you get a confusing ClassCastException if you try to do something like:

      webView = (NewWebView) findViewById(R.id.new_web_view);


回答7:

I think there is some exclusivity, for example, for a 500x500 web view and you zoom it in to 1000x1000, then you touched on it, now what position should it report?

But I really need to get them working together.



回答8:

I had a similar problem where onTouch wouldn't get called after I used pinch to zoom. Inspired by jpeskin's answer, this code worked for me (add it in the class that extends WebView):

@Override
public boolean onTouchEvent(MotionEvent event) {            
    boolean consumed = super.onTouchEvent(event);               
    onTouch(this, event);           
    return consumed;
}

update:

@Override
public boolean onTouch(View v, MotionEvent event) {
    return touchListener.onTouch(v, event);
}

The touchListener is of the type android.view.View.OnTouchListener.



回答9:

I had the same zoom issue as Stijn.V. I used the code in his answer but still had an issue with the touchListener sometimes being set to null. Here's my full webview class, tested on Android 4.0 - 4.3

public class BookWebView extends WebView implements OnTouchListener {

private OnTouchListener mTouchListener;

public BookWebView(Context context) {
    super(context);

}

public BookWebView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

public BookWebView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

@Override
public boolean onTouchEvent(MotionEvent event) {            
    boolean consumed = super.onTouchEvent(event);               
    onTouch(this, event);           
    return consumed;
}

@Override
public boolean onTouch(View v, MotionEvent event) {
    return mTouchListener.onTouch(v, event);
}

@Override
public void setOnTouchListener(OnTouchListener l) {
    super.setOnTouchListener(l);
    if(l != null){
        mTouchListener = l;
    }
}

}

For the sake of completeness here's how touchEvent is initialised in my calling activity

bookWebview.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            return mGestureDetector.onTouchEvent(event);
        }
    });


回答10:

I have encountered same problem, and i extended WebView and create my own ontouchlistener to avoid this bug. For example: public class BookknowWebView extends WebView {

private GestureDetector gestureDetector;
    private OnTouchListener mListener;

/**
 * @param context
 * @param attrs
 * @param defStyle
 */
public BookknowWebView(Context context) {
    super(context);
}

/**
 * @param context
 * @param attrs
 * @param defStyle
 */
public BookknowWebView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

/**
 * @param context
 * @param attrs
 * @param defStyle
 */
public BookknowWebView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

public void setBookknowOnTouchListener(OnTouchListener l){
    mListener = l;
}

/* 
 * @see android.webkit.WebView#onTouchEvent(android.view.MotionEvent)
 */
@Override
public boolean onTouchEvent(MotionEvent ev) {
     mListener.onTouch(this, ev);       
    return gestureDetector.onTouchEvent(ev) || super.onTouchEvent(ev);
}

public void setGestureDetector(GestureDetector gestureDetector) {
    this.gestureDetector = gestureDetector;
}   

}



回答11:

Works perfectly fine for me. Just use onTouchEvent(MotionEvent event) instead of onTouch(View v, MotionEvent event). Works even if setBuiltInZoomControls is set to true.