Webview in Scrollview

后端 未结 6 863
南旧
南旧 2020-11-29 02:40

I have to place WebView into ScrollView. But I have to put some views into the same scrollview before webview. So it looks like this:



        
相关标签:
6条回答
  • 2020-11-29 03:24

    use:

    android:descendantFocusability="blocksDescendants"
    

    on the wrapping layout this will prevent the WebView from jumping to its start.

    use:

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

    every time you change the WebView size to invalidate the layout, this will remove the empty spacing.

    0 讨论(0)
  • 2020-11-29 03:30

    None of this answers work for me, so here is my solution. First of all we need to override WebView to have ability to handle its scrolling. You can find how to do it here: https://stackoverflow.com/a/14753235/1285670

    Then create your xml with two view, where one is WebView and another is your title layout.

    This is my xml code:

    <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>
    

    Next is handle WebView scroll and move title appropriate to scroll value. You must use NineOldAndroids here.

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

    And you must add padding to your WebView content, so it will not overlay title:

    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);
            }
    });
    
    0 讨论(0)
  • 2020-11-29 03:31

    Thanks for your question, @Dmitriy! And also thanks a lot to @sven for the answer.

    But I hope there is a workaround for the cases where we need to put WebView inside the ScrollView. As sven correctly noticed the main issue is that scroll view intercepts all the touch events that can be used for the vertical scrolling. So workaround is obvious - extend scroll view and override ViewGroup.onInterceptTouchEvent(MotionEvent e) method in such a way the scroll view become tolerant to it's child views:

    1. process all touch events that can be used for vertical scrolling.
    2. dispatch all of them to the child views.
    3. intercept events with vertical scrolling only (dx = 0, dy > 0)

    Ofcourse this solution can only be used in couple with appropriate layout. I have made sample layout that can illustrate the main idea.

        <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>
    

    So that the main idea itself is to avoid vertical scrolling conflicts by putting all the views (header, webview, footer) inside vertical linear layout, and correctly dispatch touch events in order to allow horizontal and diagonal scrolling. I'm not sure whether it could be useful, but I have created the sample project with TolerantScrollView and layout sample, you can download it here.

    What's more - I have viewed the solution with accessing to the closed API via reflections and I guess it is rather hucky. It is also don't work for me because of gray rectangle artifacts over the webview on some HTC devices with Android ICS. Maybe someone knows what is the problem and how we can solve it?

    0 讨论(0)
  • 2020-11-29 03:33

    Update 2014-11-13: Since Android KitKat neither of the solutions described below are working -- you will need to look for different approaches like e.g. Manuel Peinado's FadingActionBar which provides a scrolling header for WebViews.

    Update 2012-07-08: "Nobu games" kindly created a TitleBarWebView class bringing the expected behavior back to Android Jelly Bean. When used on older platforms it will use the hidden setEmbeddedTitleBar() method and when used on Jelly Bean or above it will mimic the same behavior. The source code is available under the Apache 2 license at google code

    Update 2012-06-30: It seems as if the setEmbeddedTitleBar() method has been removed in Android 4.1 aka Jelly Bean :-(

    Original answer:

    It is possible to place a WebView into a ScrollView and it does work. I am using this in GoodNews on Android 1.6 devices. The main drawback is that the user cannot scroll "diagonal" meaning: If the web content exceeds the width of the screen the ScrollView is responsible for vertical scrolling at the WebView for horizontal scrolling. As only one of them handles the touch events you can either scroll horizontally or vertically but not diagonal.

    Further on there are some annoying problems as described by you (e.g. empty vertical space when loading a content smaller than the previous one). I've found workarounds for all of them in GoodNews, but cannot remember them now, because I've found a much better solution:

    If you only put the WebView into the ScrollView to place Controls above the web content and you are OK to support only Android 2 and above, then you can use the hidden internal setEmbeddedTitleBar() method of the WebView. It has been introduced in API level 5 and (accidentally?) became public for exactly one release (I think it was 3.0).

    This method allows you to embed a layout into the WebView which will be placed above the web content. This layout will scroll out the screen when scrolling vertically but will be kept at the same horizontal position when the web content is scrolled horizontally.

    As this method isn't exported by the API you need to use Java reflections to call it. I suggest to derive a new class as followed:

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

    Then in your layout file you can include this using

    <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"
            />
    

    and somewhere else in your code you can call the setTitle() method with the ID of the layout to be embedded into the WebView.

    0 讨论(0)
  • 2020-11-29 03:33

    Here is implementation of WebView containing another "Title bar" view at top of it.

    How it looks:

    Red bar + 3 buttons is a "Title bar", below is web view, all is scrolled and clipped together in one rectangle.

    It's clean, short, works all way from API 8 to 16 and up (with small effort it can work also on API<8). It doesn't use any hidden functions such as 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();
       }
    }
    

    Usage: put your title bar view hierarchy inside of <WebView> element in layout xml. WebView inherits ViewGroup, so it can contain children, despite of ADT plugin complaining that it can't. Example:

    <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>
    

    Note usage of layerType="software", WebView in hardware on API 11+ isn't properly animated and drawn when it has hw layer.

    Scrolling works perfectly, as well as clicks on title bar, clicks on web, selecting text in web, etc.

    0 讨论(0)
  • 2020-11-29 03:36

    I know that it's not right to place webview into scrollview, but it seems like I have no other choise.

    Yes you do, because what you want will not work reliably.

    But I have to put some views into the same scrollview before webview.

    Delete the ScrollView. Change the android:layout_height of your WebView to fill_parent. The WebView will fill up all remaining space in the LinearLayout not consumed by the other widgets.

    0 讨论(0)
提交回复
热议问题