Подтвердить что ты не робот

Веб-просмотр в Scrollview

Мне нужно разместить WebView в ScrollView. Но я должен поместить некоторые взгляды в тот же список прокрутки перед веб-просмотром. Итак, это выглядит так:

<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 от бэкэнд. Он не имеет никаких тегов тела или головы, просто данные, окруженные <p> или <h4> или некоторые другие теги. Также там есть теги <img>. Иногда картинки слишком широки для текущей ширины экрана. Поэтому я добавил некоторые CSS в начале HTML. Поэтому я загружаю данные в веб-просмотр следующим образом:

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 прокручивается до места, где начинается веб-просмотр. И я не знаю, как это исправить.

Кроме того, иногда после содержимого веб-страницы появляется огромное белое пустое пространство. Я также не знаю, что с этим делать.

Иногда scrollbars scrollview начинает дрожать случайным образом, пока я прокручиваю...

Я знаю, что не правильно размещать webview в scrollview, но похоже, что у меня нет другого выбора. Может ли кто-нибудь предложить простой способ поместить все представления и весь контент HTML в веб-просмотр?

4b9b3361

Ответ 1

Обновление 2014-11-13: Поскольку Android KitKat не работает ни с одним из описанных ниже решений, вам нужно будет искать различные подходы, например, Manuel Peinado FadingActionBar, который предоставляет прокручивающийся заголовок для WebViews.

Обновление 2012-07-08: "Нобу-игры" любезно создали класс TitleBarWebView, приносящий ожидаемый поведение обратно в Android Jelly Bean. При использовании на старых платформах он будет использовать скрытый метод setEmbeddedTitleBar(), а при использовании на желе Bean или выше он будет имитировать такое же поведение. Исходный код доступен под лицензией Apache 2 в коде google

Обновление 2012-06-30: Кажется, что метод setEmbeddedTitleBar() был удален в Android 4.1, а также Jelly Bean: - (

Оригинальный ответ:

Можно поместить WebView в ScrollView, и он работает. Я использую это в GoodNews на устройствах Android 1.6. Главный недостаток заключается в том, что пользователь не может прокручивать "диагональное" значение: если веб-контент превышает ширину экрана, ScrollView отвечает за вертикальную прокрутку в WebView для горизонтальной прокрутки. Поскольку только один из них обрабатывает события касания, вы можете либо прокручивать по горизонтали, либо по вертикали, но не по диагонали.

Далее возникают некоторые неприятные проблемы, описанные вами (например, пустое вертикальное пространство при загрузке контента, меньшего, чем предыдущее). Я нашел обходные пути для всех из них в GoodNews, но теперь их не помню, потому что нашел гораздо лучшее решение:

Если вы поместите WebView в ScrollView, чтобы разместить элементы управления над веб-контентом, и вы в порядке, чтобы поддерживать только Android 2 и выше, вы можете использовать скрытый внутренний метод setEmbeddedTitleBar() для WebView. Он был введен в 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() с идентификатором макета, который будет встроен в WebView.

Ответ 2

использование:

android:descendantFocusability="blocksDescendants"

на макете упаковки это предотвратит переход от WebView к началу.

использовать:

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

каждый раз, когда вы изменяете размер WebView, чтобы сделать недействительным макет, это приведет к удалению пустого интервала.

Ответ 3

Вот реализация WebView, содержащая еще один заголовок "Строка заголовка" поверх нее.

Как выглядит:

Красная панель + 3 кнопки - это "Строка заголовка", ниже - веб-представление, все прокручивается и скрепляется в одном прямоугольнике.

Он чистый, короткий, работает от API 8 до 16 и выше (с небольшими усилиями он может работать и на API < 8). Он не использует никаких скрытых функций, таких как WebView.setEmbeddedTitleBar.

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();
   }
}

Использование: поместите свою иерархию представления заголовка внутри <WebView> элемент в макете 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 = "программное обеспечение", WebView в аппаратном обеспечении на API 11+ неправильно анимируется и рисуется, когда у него есть hw-слой.

Прокрутка работает отлично, а также клики по строке заголовка, клики по сети, выбор текста в Интернете и т.д.

Ответ 4

Спасибо за ваш вопрос @Dmitriy! И также большое спасибо @sven за ответ.

Но я надеюсь, что есть обходной путь для случаев, когда нам нужно поместить WebView внутри ScrollView. Как правильно заметил sven, основная проблема заключается в том, что просмотр прокрутки перехватывает все события касания, которые можно использовать для вертикальной прокрутки. Поэтому обходной путь очевиден - расширение прокрутки и переопределение метода ViewGroup.onInterceptTouchEvent(MotionEvent e) таким образом, просмотр прокрутки становится толерантным к нему дочерним представлениям:

  • обрабатывать все события касания, которые можно использовать для вертикальной прокрутки.
  • отправить все из них в дочерние представления.
  • перехватывать события только с вертикальной прокруткой (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>

Таким образом, основная идея заключается в том, чтобы избежать конфликтов вертикальной прокрутки, помещая все представления (заголовок, веб-просмотр, нижний колонтитул) в вертикальную линейную компоновку и правильно отправлять события касания, чтобы разрешить горизонтальную и диагональную прокрутку. Я не уверен, может ли это быть полезным, но я создал образец проекта с помощью TolerantScrollView и образца макета, вы можете скачать его здесь.

Что еще - я просмотрел решение с доступом к закрытому API через рефлексы, и я думаю, что это довольно хреново. Это также не работает для меня из-за серых прямоугольных артефактов над веб-браузером на некоторых устройствах HTC с Android ICS. Может быть, кто-то знает, в чем проблема и как мы можем ее решить?

Ответ 5

Ни один из этих ответов не работает для меня, так что вот мое решение. Прежде всего нам нужно переопределить WebView, чтобы иметь возможность обрабатывать ее прокрутку. Вы можете найти, как это сделать здесь: fooobar.com/questions/117043/...

Затем создайте свой 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);
        }
});

Ответ 6

Я знаю, что не правильно размещать webview в scrollview, но похоже, что у меня нет другого выбора.

Да, вы, потому что то, что вы хотите, не будет работать надежно.

Но я должен поместить некоторые представления в то же самое прокрутку перед веб-просмотром.

Удалите ScrollView. Измените android:layout_height вашего WebView на fill_parent. WebView заполнит все оставшееся пространство в LinearLayout, которое не будет потреблено другими виджетами.