【问题标题】:WebView getting rid of double tap zoom.WebView 摆脱双击缩放。
【发布时间】:2012-09-06 13:58:39
【问题描述】:

我阅读了很多关于 Zooming in WebViews 主题的票,但没有为我的案例找到答案。

这是我的设置:

我正在使用通常具有以下设置的自定义 web 视图:

getSettings().setBuiltInZoomControls(false);
getSettings().setSupportZoom(false);
getSettings().setUseWideViewPort(true);
getSettings().setLoadWithOverviewMode(true);

让我在这里指出,我依赖 OverviewMode 以及 WideViewPort 来缩放我的 WebView。

我还重写了我的 OnTouchEvent 并将所有合适的事件委托给手势检测器:

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if (gestureDetector.onTouchEvent(event)) return true;
    return super.onTouchEvent(event);
  }

这是它的监听器实现,它拦截了所有的 doubleTap 事件:

  @Override
  public boolean onDoubleTapEvent(MotionEvent e) {
    // Do nothing! 
    return true;
  }

  @Override
  public boolean onDoubleTap(MotionEvent e) {
    // Do nothing! 
    return true;
  }

  @Override
  public boolean onSingleTapConfirmed(MotionEvent e) {
    // Do nothing! 
    return true;
  }

我还覆盖了这两个与缩放相关的 WebView 方法:

  @Override
  public boolean zoomIn() {
    return true;
  }

  @Override
  public boolean zoomOut() {
    return true;
  }

除了所有这些选项之外,特定的点击频率会导致我的 web 视图放大/缩小。 我还没有找到禁用这种缩放的选项,此缩放的 MotionEvent 似乎不适用于 GestureDetector 并且覆盖 zoomIn() zoomOut() 方法也没有效果。

谁能帮助我避免这种 WebView 的双击缩放行为?

【问题讨论】:

标签: android webview zooming touch-event motionevent


【解决方案1】:

有两种方法可以实现您的目标:

方法一

像这样实现GestureDetector.OnDoubleTapListener

@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
  return false; //Nothing
}

@Override
public boolean onDoubleTap(MotionEvent e) {
  //Indicates that this implementation has handled the double tap.
  return true;
}

@Override
public boolean onDoubleTapEvent(MotionEvent e) {
  //Indicates that this implementation has handled the double tap.
  return true;
}

并将其附加到您的GestureDetector,如下所示:

gestureDetector.setOnDoubleTapListener(this);

方法二

您也可以使用WebSettings.setUseWideViewPort(false); 并手动计算视图的大小。

这些方法应该可以帮助您实现显示所有内容的不可缩放 Web 视图。

public int getWindowWidth(Activity activity) {
  Display display = ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
  Point size = new Point();
  display.getSize(size);
  int width = size.x;
  return width;
}

public int getInitialScale(Activity activity, int websiteWidth) {
  return (getWindowWidth(activity) / websiteWidth) * 100;
}

【讨论】:

  • 不幸的是,结果是一样的,GestureDetector 似乎不适用于导致 web 视图放大/缩小的某种运动。
  • 抱歉,结果不是我的情况的替代品。我认为这是因为概览模式只能与wideviewport 结合使用。
  • 但是这些功能怎么用??
【解决方案2】:

有一个基于 Javascript 的出色解决方案可以解决您的问题(因此,如果是这种情况,您必须能够访问远程端的 HTML/JS 代码)。

使用FastClick library,您只需添加.js 文件,然后调用它:

<script type="application/javascript" src="fastclick.js"></script>

<script language="javascript">

window.addEventListener('load', function() {
    FastClick.attach(document.body);
}, false);

</script>

这将摆脱双击缩放,并且(在我看来)还有一个巨大的好处:所有点击都快 0.3 秒,因为系统不必再等待双击!在 Android 上查看此示例以查看差异:Practical Example

好吧,我不知道这个解决方案是否适合您的情况,但它是我的 Webview 项目的完美解决方案。希望对您有所帮助!

ps1:你必须在所有页面和框架中添加上面的代码

ps2:双指缩放会正常工作

【讨论】:

    【解决方案3】:

    您需要通过

    覆盖您的 WebView 上的 OnTouchListener
        wv.setOnTouchListener(this);
    

    内部方法 onTouch 只需检查它是否检测到双标签然后强制忽略 webview 中的缩放以返回 true

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // TODO Auto-generated method stub
        onTouchEvent(event);
    
        if (doubletab)
            return true;
    
        return false;
    }
    

    你可以看到完整的代码如下:MainActivity.java

    package com.example.testwebview;
    
    import android.os.Bundle;
    import android.app.Activity;
    import android.view.GestureDetector;
    import android.view.GestureDetector.OnGestureListener;
    import android.view.View.OnTouchListener;
    import android.view.MotionEvent;
    import android.view.View;
    import android.webkit.WebSettings;
    import android.webkit.WebView;
    import android.widget.TextView;
    
    public class MainActivity extends Activity implements OnGestureListener, OnTouchListener, GestureDetector.OnDoubleTapListener
    {
    
        TextView tv;
        WebView wv;
        GestureDetector gd;
    
        boolean doubletab = false;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            gd = new GestureDetector(this);
    
            setContentView(R.layout.activity_main);
    
            tv = (TextView)findViewById(R.id.textView1);
            wv = (WebView)findViewById(R.id.webView1);
    
            WebSettings setting = wv.getSettings();
            setting.setBuiltInZoomControls(false);
            setting.setSupportZoom(false);
            setting.setUseWideViewPort(true);
            setting.setLoadWithOverviewMode(true);
    
            wv.setOnTouchListener(this);
            wv.loadUrl("http://www.sanook.com");
        }
        @Override
        public boolean onDoubleTap(MotionEvent arg0) {
            tv.setText("double tap");
            doubletab = true;
            // TODO Auto-generated method stub
            return false;
        }
    
        @Override
        public boolean onDoubleTapEvent(MotionEvent arg0) {
            tv.setText("double tap event");
            doubletab = true;
            // TODO Auto-generated method stub
            return false;
        }
    
        @Override
        public boolean onSingleTapConfirmed(MotionEvent arg0) {
            tv.setText("single tap confirm");
            doubletab = false;
            // TODO Auto-generated method stub
            return false;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent me)
        {
            return gd.onTouchEvent(me);
        }
    
        @Override
        public boolean onDown(MotionEvent arg0) {
            tv.setText("down");
            doubletab = false;
            // TODO Auto-generated method stub
            return false;
        }
    
        @Override
        public boolean onFling(MotionEvent arg0, MotionEvent arg1, float arg2,
                float arg3) {
            tv.setText("fling");
            doubletab = false;
            // TODO Auto-generated method stub
            return false;
        }
    
        @Override
        public void onLongPress(MotionEvent arg0) {
            tv.setText("long press");
            doubletab = false;
            // TODO Auto-generated method stub
    
        }
    
        @Override
        public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2,
                float arg3) {
            tv.setText("scroll");
            doubletab = false;
            // TODO Auto-generated method stub
            return false;
        }
    
        @Override
        public void onShowPress(MotionEvent arg0) {
            // TODO Auto-generated method stub
            tv.setText("show press");
            doubletab = false;
    
        }
    
    
        @Override
        public boolean onSingleTapUp(MotionEvent arg0) {
            // TODO Auto-generated method stub
            tv.setText("single tab up");
            doubletab = false;
            return false;
        }
    
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            // TODO Auto-generated method stub
            onTouchEvent(event);
    
            if (doubletab)
                return true;
    
            return false;
        }
    }
    

    和activity_main.xml

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentRight="true"
            android:layout_alignParentTop="true"
            android:text="TextView" />
    
        <WebView
            android:id="@+id/webView1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentLeft="true"
            android:layout_below="@+id/textView1" />
    
    </RelativeLayout>
    

    别忘了在 manifest.xml 中添加权限

    <uses-permission android:name="android.permission.INTERNET" />
    

    【讨论】:

    • 这捕获了完全相同的双击事件,我已经在处理,不幸的是,双击运动事件未被听众识别,并且在一定频率下双击仍然会导致放大/缩小。跨度>
    • 您能否详细解释一下无法识别的运动事件?如果它不能通过频率或延迟点击识别,它就会变成大约间隔。您有 2 个选择 - 覆盖 GestureDetector 并增加它们的间隔。 - 通过 onSingleTap 进行检测并让一些线程及时检测
    • 就像那个gestureDetector.onTouchEvent(me);实际上在导致缩放的事件上返回 false。覆盖 GestureDetector 听起来像是一种可能性,当我找到时间时,我会仔细研究一下 GestureDetector。
    • 如果它回答了您的问题,您应该将其标记为正确答案:]
    • 我目前在一个不同的项目中,无法找到时间来遵循这种理论上的可能性,证明它以及它是如何工作的,赏金和正确答案是你的! ;-)
    【解决方案4】:

    另一种方法(唯一对我有用的方法)是模拟双击之间的距离较远的中间点击,以便 WebView 不会将它们重新识别为结果。负坐标可用于此目的,尽管小于 -1 的任何值都会减慢甚至破坏该过程。因此,我们至少需要两个点,例如 (0, -1) 和 (2*d+1, -1),其中 d 是轻点之间的最大距离,以便将它们视为双击。

    webView.setOnTouchListener(new View.OnTouchListener() {
            private long lastUp = -1000;
            private float lastDownX = -1000;
            private float lastDownY = -1000;
            private int dtDistance = 0;         
            private int dtDistanceSquared = 0;
            private long dtTime = 0;
    
            private void performTap(View v, int x, int y, long t) {             
                MotionEvent e_down = MotionEvent.obtain(t, t, MotionEvent.ACTION_DOWN, x, y, 0);
                v.dispatchTouchEvent(e_down);
                e_down.recycle();
    
                MotionEvent e_up = MotionEvent.obtain(t, t, MotionEvent.ACTION_UP, x, y, 0);
                v.dispatchTouchEvent(e_up);
                e_up.recycle();
            }
    
            private int getRemoteX(float x) {
                return Math.round(x > dtDistance + 0.5 ? 0 : 2 * dtDistance + 1);
            }
    
            private boolean inRadius(int x0, int y0, float x1, float y1) {
                boolean result = (x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1) <= dtDistanceSquared;
                return result;
            }
    
            @Override
            public boolean onTouch(View v, MotionEvent event) { 
                if (event.getY() >= 0)  // Otherwise it's a fake tap we simulated
                {               
                    if (dtTime == 0)
                    {
                        dtDistance = ViewConfiguration.get(v.getContext()).getScaledDoubleTapSlop();    // Maximum distance between taps for them to be considered double tap
                        dtDistanceSquared = dtDistance * dtDistance;
                        dtTime = ViewConfiguration.getDoubleTapTimeout();   // Maximum time elapsed between taps for them to be considered double tap
                    }
    
                    switch (event.getAction())
                    {
                        case MotionEvent.ACTION_UP:
                            lastUp = event.getEventTime();
                            break;
                        case MotionEvent.ACTION_DOWN:
                            long t = event.getEventTime(); 
                            if (t - lastUp < dtTime * 4/3)  // Very rarely just (t - lastUp <= dtTime) doesn't work
                            {
                                int x = getRemoteX(event.getX());
                                if (inRadius(x, -1, lastDownX, lastDownY))
                                    performTap(v, getRemoteX(lastDownX), -1, t);    // Otherwise our fake tap would constitute a double tap with the previous real tap
                                performTap(v, x, -1, t);                                
                            }
                            lastDownX = event.getX();
                            lastDownY = event.getY();
                            break;                      
                    }
                }
    
                return false;                                   
            }
        });
    

    【讨论】:

      【解决方案5】:

      如果可能,请替换 html 中的静态元标记:

      <script>
       var meta = document.createElement("meta");
       meta.setAttribute('name','viewport');
       meta.setAttribute('content','width=device-width, user-scalable=no');
       document.getElementsByTagName('head')[0].appendChild(meta); 
      </script>
      

      此外,您还可以使用一个不错的脚本:FastClick
      它不会等待点击事件,因此速度更快。

      <script type="application/javascript" src="fastclick.js"></script>
      
          <script language="javascript">
      
              window.addEventListener('load', function() {
                  FastClick.attach(document.body);
              }, false);
      
      </script>
      

      然后为您的手势检测器设置一个双击监听器。 (在自定义 WebView 中)

      gestureDetector.setOnDoubleTapListener(new OnDoubleTapListener() {
      
          @Override
          public boolean onSingleTapConfirmed( MotionEvent e ) {
              return false;
          }
      
          @Override
          public boolean onDoubleTapEvent( MotionEvent e ) {
      
              return true;
          }
      
          @Override
          public boolean onDoubleTap( MotionEvent e ) {
      
              return true;
          }
      });
      

      重写 onTouchEvent 方法(在自定义 WebView 中):

      @Override
      public boolean onTouchEvent( MotionEvent event ) {
      
          boolean gestureHandled = gestureDetector.onTouchEvent(event);
          int actionMask = event.getActionMasked() & MotionEvent.ACTION_MASK;
          int index = ( event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
          int pointId = event.getPointerId(index);
      
          // ignore move events
          if (actionMask == MotionEvent.ACTION_MOVE) {
      
              return super.onTouchEvent(event);
          }
      
          // cancel detected double taps
          if (gestureDetector.onTouchEvent(event)) {
              event.setAction(MotionEvent.ACTION_CANCEL);
              super.onTouchEvent(event);
              return true;
          }
      
          // if you want to ignore multi touch events/taps
          if (pointId != 0) {
      
              System.out.println("KEY multiple points detected");
              return true;
          }
      
          // use single taps
          if (event.getAction() == MotionEvent.ACTION_UP) {
      
              event.setAction(MotionEvent.ACTION_CANCEL);
              super.onTouchEvent(event);
              event.setAction(MotionEvent.ACTION_DOWN);
              super.onTouchEvent(event);
              event.setAction(MotionEvent.ACTION_UP);
              return true;
          }
      
      return super.onTouchEvent(event);
      }
      

      【讨论】:

      • 为什么必须动态更改元标记?
      • 作为最后的手段,“user-scalable”必须设置为“no”:user-scalable=no
      【解决方案6】:

      尝试将 onDoubleTapEvent onDoubleTap 的返回值设为 false....

      @Override
        public boolean onDoubleTapEvent(MotionEvent e) {
          // Do nothing! 
          return false;
        }
      
        @Override
        public boolean onDoubleTap(MotionEvent e) {
          // Do nothing! 
          return false;
        }
      

      【讨论】:

      • true 表明该事件尚未被消费,因此它可能会在其他地方处理。反正试了一下,没有预期的效果。
      • 由于这更多是猜测并且无济于事,因此我建议您将其删除。
      猜你喜欢
      • 1970-01-01
      • 2014-01-23
      • 2012-05-07
      • 2014-04-07
      • 1970-01-01
      • 2017-10-27
      • 2022-08-12
      • 1970-01-01
      • 2012-05-29
      相关资源
      最近更新 更多