【问题标题】:Webview in Scrollview滚动视图中的网页视图
【发布时间】:2012-03-15 10:54:33
【问题描述】:

我必须将 WebView 放入 ScrollView。但是我必须在 webview 之前将一些视图放入同一个滚动视图中。所以它看起来像这样:

<ScrollView
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  >
<LinearLayout
    android:id="@+id/articleDetailPageContentLayout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">
  <LinearLayout
      android:id="@+id/articleDetailRubricLine"
      android:layout_width="fill_parent"
      android:layout_height="3dip"
      android:background="@color/fashion"/>

  <ImageView
      android:id="@+id/articleDetailImageView"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:adjustViewBounds="true"
      android:scaleType="fitStart"
      android:src="@drawable/article_detail_image_test"/>

  <TextView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:padding="5dip"
      android:text="PUBLISH DATE"/>

  <WebView
      android:id="@+id/articleDetailContentView"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:background="@color/fashion"
      android:isScrollContainer="false"/>
</LinearLayout>

我正在从后端获取一些 HTML 信息。它没有任何 body 或 head 标签,只有 &lt;p&gt;&lt;h4&gt; 或其他一些标签包围的数据。它还有&lt;img&gt; 标签。有时图片对于当前屏幕宽度来说太宽了。所以我在 HTML 的开头添加了一些 css。所以我像这样将数据加载到 webview:

private final static String WEBVIEW_MIME_TYPE = "text/html";
    private final static String WEBVIEW_ENCODING = "utf-8";
String viewport = "<head><meta name=\"viewport\" content=\"target-densitydpi=device-dpi\" /></head>";
        String css = "<style type=\"text/css\">" +
            "img {width: 100%;}" +
            "</style>";
        articleContent.loadDataWithBaseURL("http://", viewport + css + articleDetail.getContent(), WEBVIEW_MIME_TYPE,
            WEBVIEW_ENCODING, "about:blank");

有时当页面加载时,scrollview 会滚动到 webview 开始的地方。我不知道如何解决这个问题。

另外,有时 webview 内容后会出现巨大的空白区域。我也不知道该怎么办。

有时滚动视图的滚动条在我滚动时开始随机抽动...

我知道将 webview 放入 scrollview 是不对的,但似乎我别无选择。谁能建议将所有视图和所有 HTML 内容放置到 webview 的正确方法?

【问题讨论】:

    标签: android webview scrollview


    【解决方案1】:

    使用:

    android:descendantFocusability="blocksDescendants"
    

    在环绕布局上,这将防止 WebView 跳转到它的开头。

    使用:

    webView.clearView();
    mNewsContent.requestLayout();
    

    每次更改 WebView 大小以使布局无效时,都会删除空白间距。

    【讨论】:

    • +1 表示答案的第一部分。不幸的是,第二部分不起作用(可能是因为您使用 mNewsContent 名称所指的内容并不完全清楚)。
    • android:descendantFocusability="blocksDescendants" 这对我有用!
    • 如果你禁用焦点,你就不能在 webview 中使用输入字段。
    【解决方案2】:

    2014 年 11 月 13 日更新: 由于 Android KitKat 下文描述的解决方案都不起作用 - 您需要寻找不同的方法,例如Manuel Peinado's FadingActionBar 为 WebViews 提供滚动标题。

    2012-07-08 更新:“Nobu 游戏”创建了一个 TitleBarWebView 类,将预期的行为带回 Android Jelly Bean。在旧平台上使用时,它将使用隐藏的setEmbeddedTitleBar() 方法,而在 Jelly Bean 或更高版本上使用时,它将模仿相同的行为。源代码在 Apache 2 许可下可用at google code

    2012-06-30 更新: 似乎 setEmbeddedTitleBar() 方法已在 Android 4.1 aka Jelly Bean 中被删除:-(

    原答案:

    可以将WebView 放入ScrollView 中,并且确实有效。我在 Android 1.6 设备上的 GoodNews 中使用它。主要缺点是用户无法滚动“对角线”含义:如果网页内容超过屏幕宽度,ScrollView 负责垂直滚动,WebView 负责水平滚动。由于其中只有一个处理触摸事件,因此您可以水平滚动,但不能对角滚动。

    此外,您还描述了一些令人讨厌的问题(例如,加载小于前一个内容的内容时垂直空间为空)。我在 GoodNews 中找到了所有解决方法,但现在不记得了,因为我找到了更好的解决方案:

    如果您只将WebView 放入ScrollView 以将控件放置在网页内容的上方 并且您可以仅支持Android 2 及更高版本,那么您可以使用隐藏的内部@ WebView 的 987654335@ 方法。它已在 API 级别 5 中引入,并且(意外地?)仅在一个版本中公开(我认为是 3.0)。

    此方法允许您将布局嵌入到WebView 中,该布局将放置在网络内容上方。此布局在垂直滚动时会滚出屏幕,但在网页内容水平滚动时会保持在相同的水平位置。

    由于 API 不导出此方法,因此您需要使用 Java 反射来调用它。我建议派生一个新类如下:

    public final class WebViewWithTitle extends ExtendedWebView {
        private static final String LOG_TAG = "WebViewWithTitle";
        private Method setEmbeddedTitleBarMethod = null;
    
        public WebViewWithTitle(Context context) {
            super(context);
            init();
        }
    
        public WebViewWithTitle(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        private void init() {
            try {
                setEmbeddedTitleBarMethod = WebView.class.getMethod("setEmbeddedTitleBar", View.class);
            } catch (Exception ex) {
                Log.e(LOG_TAG, "could not find setEmbeddedTitleBar", ex);
            }
        }
    
        public void setTitle(View view) {
            if (setEmbeddedTitleBarMethod != null) {
                try {
                    setEmbeddedTitleBarMethod.invoke(this, view);
                } catch (Exception ex) {
                    Log.e(LOG_TAG, "failed to call setEmbeddedTitleBar", ex);
                }
            }
        }
    
        public void setTitle(int resId) {
            setTitle(inflate(getContext(), resId, null));
        }
    }
    

    然后在你的布局文件中你可以使用

    <com.mycompany.widget.WebViewWithTitle
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/content"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            />
    

    您可以在代码中的其他位置调用 setTitle() 方法,并将布局的 ID 嵌入到 WebView 中。

    【讨论】:

    • “我在 GoodNews 中找到了所有解决方法。”你是如何解决对角滚动问题的?
    • @Mark,此答案中描述的解决方案实现了一个基于旧 WebView 中隐藏功能的标题,以便对角滚动没有问题(请参阅上面的详细信息)。不幸的是,自 JellyBean 以来,此解决方案不再有效。在答案开头查看我的更新。
    • 您在 2014 年 11 月 13 日的提议看起来很有希望。不幸的是,它不适用于 Android 5.0 和某些 4.0.X 设备。 imgur.com/GEXzRWT 我已将问题定位到 onDraw() 方法中的 ObservableWebViewWithHeader 类。似乎 translate 方法在某些 Android 版本上的行为不同。有人知道如何解决这个问题吗?
    • @Johan 我跟你有同样的问题!真正的问题不在于翻译。如果您更正翻译,请使用 visibleHeaderHeight。可以看到从webview滚动出来的内容不会回来了!任何人都可以解决这个问题?
    【解决方案3】:

    这是在其顶部包含另一个“标题栏”视图的 WebView 的实现。

    外观:

    红条+3个按钮是一个“标题栏”,下面是网页视图,全部滚动并剪辑在一个矩形中。

    它简洁、简短,从 API 8 一直工作到 16 及更高版本(只需稍加努力,它也可以在 API

    public class TitleWebView extends WebView{
    
       public TitleWebView(Context context, AttributeSet attrs){
          super(context, attrs);
       }
    
       private int titleHeight;
    
       @Override
       protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
          super.onMeasure(widthMeasureSpec, heightMeasureSpec);
          // determine height of title bar
          View title = getChildAt(0);
          titleHeight = title==null ? 0 : title.getMeasuredHeight();
       }
    
       @Override
       public boolean onInterceptTouchEvent(MotionEvent ev){
          return true;   // don't pass our touch events to children (title bar), we send these in dispatchTouchEvent
       }
    
       private boolean touchInTitleBar;
       @Override
       public boolean dispatchTouchEvent(MotionEvent me){
    
          boolean wasInTitle = false;
          switch(me.getActionMasked()){
          case MotionEvent.ACTION_DOWN:
             touchInTitleBar = (me.getY() <= visibleTitleHeight());
             break;
    
          case MotionEvent.ACTION_UP:
          case MotionEvent.ACTION_CANCEL:
             wasInTitle = touchInTitleBar;
             touchInTitleBar = false;
             break;
          }
          if(touchInTitleBar || wasInTitle) {
             View title = getChildAt(0);
             if(title!=null) {
                // this touch belongs to title bar, dispatch it here
                me.offsetLocation(0, getScrollY());
                return title.dispatchTouchEvent(me);
             }
          }
          // this is our touch, offset and process
          me.offsetLocation(0, -titleHeight);
          return super.dispatchTouchEvent(me);
       }
    
       /**
        * @return visible height of title (may return negative values)
        */
       private int visibleTitleHeight(){
          return titleHeight-getScrollY();
       }       
    
       @Override
       protected void onScrollChanged(int l, int t, int oldl, int oldt){
          super.onScrollChanged(l, t, oldl, oldt);
          View title = getChildAt(0);
          if(title!=null)   // undo horizontal scroll, so that title scrolls only vertically
             title.offsetLeftAndRight(l - title.getLeft());
       }
    
       @Override
       protected void onDraw(Canvas c){
    
          c.save();
          int tH = visibleTitleHeight();
          if(tH>0) {
             // clip so that it doesn't clear background under title bar
             int sx = getScrollX(), sy = getScrollY();
             c.clipRect(sx, sy+tH, sx+getWidth(), sy+getHeight());
          }
          c.translate(0, titleHeight);
          super.onDraw(c);
          c.restore();
       }
    }
    

    用法:将标题栏视图层次结构放在布局 xml 中的 元素内。 WebView 继承 ViewGroup,因此它可以包含子项,尽管 ADT 插件抱怨它不能。 示例:

    <com.test.TitleWebView
       android:id="@+id/webView"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:layerType="software" >
    
       <LinearLayout
          android:id="@+id/title_bar"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:background="#400" >
    
          <Button
             android:id="@+id/button2"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="Button" />
    
          <Button
             android:id="@+id/button3"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="Button" />
    
          <Button
             android:id="@+id/button5"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="Button" />
       </LinearLayout>
    </com.test.TitleWebView>
    

    注意 layerType="software" 的用法,API 11+ 硬件中的 WebView 在具有硬件层时无法正确动画和绘制。

    滚动效果很好,以及点击标题栏、点击网页、选择网页中的文本等。

    【讨论】:

    • -Pointer Null 这非常有效,但它只适用于垂直视图,当方向变为水平时,滚动非常困难。
    • 在大多数情况下都很好,但是,这不适用于文本输入和其他输入元素。
    【解决方案4】:

    感谢您的提问,@Dmitriy! 也非常感谢@sven 的回答。

    但我希望对于需要将 WebView 放入 ScrollView 的情况有一种解决方法。正如 sven 正确注意到的那样,主要问题是滚动视图拦截了所有可用于垂直滚动的触摸事件。所以解决方法很明显 - 扩展滚动视图并覆盖 ViewGroup.onInterceptTouchEvent(MotionEvent e) 方法,这样滚动视图就可以容忍它的子视图:

    1. 处理所有可用于垂直滚动的触摸事件。
    2. 将它们全部发送到子视图。
    3. 仅通过垂直滚动拦截事件 (dx = 0, dy > 0)

    当然,此解决方案只能与适当的布局结合使用。 我已经制作了可以说明主要思想的示例布局。

        <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" >
    
        <com.rus1f1kat0r.view.TolerantScrollView
            android:id="@+id/scrollView1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_centerHorizontal="true"
            android:layout_centerVertical="true" >
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content" 
                android:orientation="vertical">
    
                <TextView
                    android:id="@+id/textView1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Large Text"
                    android:textAppearance="?android:attr/textAppearanceLarge" />
    
                <TextView
                    android:id="@+id/textView2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Large Text"
                    android:textAppearance="?android:attr/textAppearanceLarge" />
    
                <WebView
                    android:id="@+id/webView1"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content" />
    
                <TextView
                    android:id="@+id/textView3"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Medium Text"
                    android:textAppearance="?android:attr/textAppearanceMedium" />
    
               <TextView
                   android:id="@+id/textView4"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:text="Medium Text"
                   android:textAppearance="?android:attr/textAppearanceMedium" />
    
            </LinearLayout>
        </com.rus1f1kat0r.view.TolerantScrollView>
    </RelativeLayout>
    

    因此,主要思想本身就是通过将所有视图(页眉、webview、页脚)放在垂直线性布局中来避免垂直滚动冲突,并正确调度触摸事件以允许水平和对角滚动。 我不确定它是否有用,但是我已经创建了带有 TolerantScrollView 和布局示例的示例项目,您可以下载它here

    更重要的是 - 我已经查看了通过反射访问封闭 API 的解决方案,我想这很糟糕。它也不适用于我,因为在某些带有 Android ICS 的 HTC 设备上的 webview 上有灰色矩形伪影。也许有人知道问题出在哪里以及我们如何解决它?

    【讨论】:

    • 你使用透明背景吗?
    • 您的解决方案非常好,但它不允许正确选择文本。当用户拖动选择图标以垂直移动它时,整个 ScrollView 会移动。你会如何修改你的代码来解决这个问题?
    • 目前我们正在研究另一种解决方案,它更适合开发指南,并且错误和不一致之处更少。准备好后,我会为您提供代码 sn-ps。
    【解决方案5】:

    这些答案都不适合我,所以这是我的解决方案。首先我们需要覆盖WebView 来处理它的滚动。你可以在这里找到如何做:https://stackoverflow.com/a/14753235/1285670

    然后使用两个视图创建您的 xml,其中一个是 WebView,另一个是您的标题布局。

    这是我的 xml 代码:

    <FrameLayout 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" >
    
      <com.project.ObservableWebView
          android:id="@+id/post_web_view"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:background="#fff" />
    
      <LinearLayout
          android:id="@+id/post_header"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:background="#fff"
          android:orientation="vertical"
          android:paddingLeft="@dimen/common_ui_space"
          android:paddingRight="@dimen/common_ui_space"
          android:paddingTop="@dimen/common_ui_space" >
    
          ////some stuff
    
        </LinearLayout>
    
    </FrameLayout>
    

    接下来是处理WebView 滚动并移动适合滚动值的标题。您必须在此处使用NineOldAndroids

    @Override
    public void onScroll(int l, int t, int oldl, int oldt) {
        if (t < mTitleLayout.getHeight()) {
            ViewHelper.setTranslationY(mTitleLayout, -t);
        } else if (oldt < mTitleLayout.getHeight()) {
            ViewHelper.setTranslationY(mTitleLayout, -mTitleLayout.getHeight());
        }
    }
    

    并且您必须在WebView 内容中添加填充,这样它就不会覆盖标题:

    v.getViewTreeObserver().addOnGlobalLayoutListener(
        new OnGlobalLayoutListener() {
            @SuppressWarnings("deprecation")
            @SuppressLint("NewApi")
            @Override
            public void onGlobalLayout() {
                if (mTitleLayout.getHeight() != 0) {
                    if (Build.VERSION.SDK_INT >= 16)
                        v.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    else
                        v.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    }
    
                    mWebView.loadDataWithBaseURL(null, "<div style=\"padding-top: " 
                      + mTitleLayout.getHeight() / dp + "px\">" + mPost.getContent() 
                      + "</div>", "text/html", "UTF8", null);
            }
    });
    

    【讨论】:

    • 这里dp的值是多少
    • @YogeshSeralia 只是屏幕的密度。你可以从context.getResources().getDisplayMetrics().density获得它
    • @YogeshSeralia 但没有更好的方法来转换 px 和 dp,请使用 TypedValue.applyDimension()
    【解决方案6】:

    我知道将 webview 放到 scrollview 中是不对的,但我似乎没有其他选择。

    是的,你这样做,因为你想要的不会可靠地工作。

    但我必须在 webview 之前将一些视图放入同一个滚动视图中。

    删除ScrollView。将WebViewandroid:layout_height 更改为fill_parentWebView 将填满 LinearLayout 中未被其他小部件占用的所有剩余空间。

    【讨论】:

    • 如果我真的像你说的那样,只有 webview 区域可以滚动。但我需要 ImageView 和 TextView 的整个区域能够滚动。
    • @Dmitriy:然后把文字和图片放到HTML里,去掉小部件,你就只有一个WebView,整个东西就可以同步滚动了。
    • @CommonsWare “因为你想要的东西不会可靠地工作。”你能详细说明一下吗?我有完全相同的情况,ScrollView 中的 WebView 加载了不同的内容并且没有正确调整大小,或者 ScrollView 的滚动条不断出现故障/抽搐。将所有内容都放入 WebView 对我来说不是一个选择。
    • @JarrodSmith:“你能详细说明一下吗?” ——没有什么可详细说明的。正如您所发现的,它不能可靠地工作。做点别的。 “将所有内容放入 WebView 对我来说不是一个选项”——然后重新设计应用程序,这样要么是你的选项,要么删除 ScrollView,或者删除 WebView
    • @CommonsWare 好的,谢谢 - 我想我只是想知道如果我禁用 WebView 上的滚动并将其设置为 wrap_content 是否会起作用。我不需要 WebView 本身来滚动其内容。有没有其他方法可以在 Android 中显示正确样式的 CSS/HTML?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-02
    • 1970-01-01
    • 2020-12-05
    相关资源
    最近更新 更多