2011-07-28 18 views
24

Android 2.3以降のブラウザは、変更された方向のコンテンツのスクロール位置を維持するのに適しています。方向変更時にWebViewコンテンツのスクロール位置を維持する

私は、アクティビティ内では表示されているが成功していないWebViewについては、同じことを達成しようとしています。

マニフェストの変更(android:configChanges = "orientation | keyboardHidden")をテストして、回転でのアクティビティの再作成を回避しましたが、アスペクト比が異なるためスクロール位置が必要な場所に設定されません。さらに、これはではありません。私は、ポートレートとランドスケープで異なるレイアウトを持つ必要があるため、私の解決策です。

私はWebViewの状態を保存して復元しようとしましたが、もう一度上部に表示されているコンテンツでこれを再開しました。

onPageFinishedでさらにscrollToを使用してスクロールしようとすると、WebViewの高さがこの時点でゼロではなくても機能しません。

アイデア?前もって感謝します。ピーター

部分的な解決策は:

私の同僚は、JavaScriptのソリューションを経由して作業をスクロールし得ることができました。同じ垂直位置への単純なスクロールの場合、WebViewの「Y」スクロール位置はonSaveInstanceState状態に保存されます。以下は、その後onPageFinishedに追加されます。

public void onPageFinished(final WebView view, final String url) { 
    if (mScrollY > 0) { 
     final StringBuilder sb = new StringBuilder("javascript:window.scrollTo(0, "); 
     sb.append(mScrollY); 
     sb.append("/ window.devicePixelRatio);"); 
     view.loadUrl(sb.toString()); 
    } 
    super.onPageFinished(view, url); 
} 

をコンテンツが最初から新しいスクロール位置にジャンプしますが、それはほとんど目立たあるとしてあなたはわずかなちらつきを入手できます。次のステップは、(高さの違いに基づいて)パーセンテージベースの方法を試みることです。また、WebViewにその状態を保存して復元させることを検討します。

答えて

37

ができオリエンテーションの変更私はあなたがそれを手動で行わなければならないのではないかと心配しています。

私は、このメソッドを使用:

  1. は、それがあるため

を再現していますときのWebViewの位置を復元onRetainNonConfigurationInstance

  • で保存
  • WebViewのスクロールの実際のパーセントを計算しますWebViewの幅と高さがポートレートモードとランドスケープモードで同じではないため、ユーザーのスクロール位置を表すためにパーセントを使用します。ステップバイ

    ステップ:

    1)

    // Calculate the % of scroll progress in the actual web page content 
    private float calculateProgression(WebView content) { 
        float positionTopView = content.getTop(); 
        float contentHeight = content.getContentHeight(); 
        float currentScrollPosition = content.getScrollY(); 
        float percentWebview = (currentScrollPosition - positionTopView)/contentHeight; 
        return percentWebview; 
    } 
    

    2 WebViewのスクロールの実際のパーセントを計算する)だけで姿勢変更

    前に、進行状況を保存onRetainNonConfigurationInstance

    でそれを保存します

    @Override 
    public Object onRetainNonConfigurationInstance() { 
        OrientationChangeData objectToSave = new OrientationChangeData(); 
        objectToSave.mProgress = calculateProgression(mWebView); 
        return objectToSave; 
    } 
    
    // Container class used to save data during the orientation change 
    private final static class OrientationChangeData { 
        public float mProgress; 
    } 
    

    3)あなたのページがかかる場合

    は、あなたがこの方法が問題となる可能性がある( を再ロードするページを待たなければならない現在の位置を復元するに姿勢変化データ

    private boolean mHasToRestoreState = false; 
    private float mProgressToRestore; 
    
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
    
        setContentView(R.layout.main); 
    
        mWebView = (WebView) findViewById(R.id.WebView); 
        mWebView.setWebViewClient(new MyWebViewClient()); 
        mWebView.loadUrl("http://stackoverflow.com/"); 
    
        OrientationChangeData data = (OrientationChangeData) getLastNonConfigurationInstance(); 
        if (data != null) { 
         mHasToRestoreState = true; 
         mProgressToRestore = data.mProgress; 
        } 
    } 
    

    から進捗状況を取得して再作成だのWebView私はあなたがonPageFinishedメソッドは、スクロール作業を行うために呼び出された後、少し待つ必要があることに遭遇私のテスト中にロードするのに長い時間)

    private class MyWebViewClient extends WebViewClient { 
        @Override 
        public void onPageFinished(WebView view, String url) { 
         if (mHasToRestoreState) { 
          mHasToRestoreState = false; 
          view.postDelayed(new Runnable() { 
           @Override 
           public void run() { 
            float webviewsize = mWebView.getContentHeight() - mWebView.getTop(); 
            float positionInWV = webviewsize * mProgressToRestore; 
            int positionY = Math.round(mWebView.getTop() + positionInWV); 
            mWebView.scrollTo(0, positionY); 
           } 
          // Delay the scrollTo to make it work 
          }, 300); 
         } 
         super.onPageFinished(view, url); 
        } 
    } 
    

    。 300msは大丈夫です。この遅延により、ディスプレイはフリックします(スクロール0の最初の表示が正しい位置に移動します)。

    多分もっと良い方法がありますが、私は気づいていません。

  • +0

    @ ol_v-er情報をありがとう。 'scrollTo'が動作するのを見るためにあなたが記述しているように遅延を導入しようと考えています(遅れが必要ですが、とにかく...)。ついに、javascriptのソリューション(私はまもなく掲載予定)を介して動作するようにスクロールしていますが、結果を見るためにまだ試みていないパーセンテージスクロールの考えがありました。 – PJL

    +0

    実際にonPageFinishedコールバックが呼び出されると、WebViewは単に描画を開始します。遅延は、WebViewが描画を完了していることを確認することです(正確な高さを持つ)。あなたのjavascriptソリューションを楽しみにしています。 –

    +0

    ありがとう、私のjavascriptソリューションを追加しました。 – PJL

    1

    ページを再読み込みしていないため、ページが読み込まれない可能性があります。方向を変更しているだけで、再読み込みが行われるかどうかはわかりません。

    アクティビティのonConfigurationChangedメソッドでscrollToを使用してみてください。

    +0

    オリエンテーションの変更で 'onPageFinished'を取得していますが、' WebView :: scrollTo'を呼び出すことは効果がありません。 – PJL

    +0

    さらに、この解決策は一部の人にとっては役に立ちますが、私はオリエンテーションの変更でアクティビティを再開したいと考えています。 – PJL

    1

    WebViewの現在の位置が、常に後でスクロールするのに適切な場所にならないように、アスペクトを変更すると、ほとんどの場合、あなたは時のWebViewのの現在の位置を復元するには卑劣なこととWebViewの中のトップ最も目に見える要素を決定し、姿勢変更後に元にその時点でanchorを注入し、それにユーザーをリダイレクト...

    +0

    ありがとう、私は、この問題の単純な/組み込みの解決策があるはずであると感じるのを助けることができません。 – PJL

    0

    WebViewの正しい割合を計算するには、mWebView.getScale()をと数えることが重要です。実際には、戻り値getScrollY()はそれぞれです。mWebView.getContentHeight()* mWebView.getScale()です。

    0

    私たちがこれを達成するために管理した方法は、私たちのフラグメント内のWebViewへのローカル参照を持っています。

    /** 
    * WebView reference 
    */ 
    private WebView webView; 
    

    onCreateViewが呼び出されたときに、その後setRetainInstanceonCreate

    @Override 
    public void onCreate(final Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setRetainInstance(true); 
    
        // Argument loading settings 
    } 
    

    trueには、その後、WebViewの既存のローカルインスタンスを返し、そうでない場合は、コンテンツおよびその他の設定をセットアップする新しいコピーをインスタンス化します。重要なステップは、WebViewを再接続して、以下のelse節で見ることができる親を削除するときです。

    @Override 
    public View onCreateView(final LayoutInflater inflater, final ViewGroup container, 
         final Bundle savedInstanceState) { 
    
        final View view; 
        if (webView == null) { 
    
         view = inflater.inflate(R.layout.webview_dialog, container); 
    
         webView = (WebView) view.findViewById(R.id.dialog_web_view); 
    
         // Setup webview and content etc... 
        } else { 
         ((ViewGroup) webView.getParent()).removeView(webView); 
         view = webView; 
        } 
    
        return view; 
    } 
    
    +0

    私は、webviewを含むフラグメントに2回目に戻るときにnullポインタを取得します。断片1はwebviewと共に読み込まれます。私はある位置にスクロールして、フラグメント2に移動し、フラグメント1に戻って行きます。しかし、その後私はフラグメント2に移動し、フラグメント1に戻り、アプリケーションはヌルポインタでクラッシュします。何か案は? – piratemurray

    +0

    この回避策では、WebViewが新しいアクティビティにアタッチされている間に元のアクティビティのコンテキストを保持するため、メモリリークが発生します。これを避けてください。 – BladeCoder

    0

    I)は、(代わりonPageFinishedの、)(元のポストに記載の部分的な解決策を使用するが、代わりonPageStarted内部のJavaScriptスニペットを置きます。ジャンプすることなく私のために働くと思われる。

    私の使用例は少し異なります。ユーザーがページを更新した後、水平位置を維持しようとしています。

    しかし、私はあなたのソリューションを使用することができましたし、それは私のために完璧に動作:)

    0

    元の記事で説明した部分的な解決策では、次のコード

    private float calculateProgression(WebView content) { 
        float contentHeight = content.getContentHeight(); 
        float currentScrollPosition = content.getScrollY(); 
        float percentWebview = currentScrollPosition/ contentHeight; 
        return percentWebview; 
    } 
    
    を変更する場合は、ソリューションを向上させることができます

    の場合、content.getTop()を使用しているときはコンポーネントのトップ位置にあるため、必要ありません。つまり、スクロールY位置の実際の位置にコンポーネントが配置されている親の位置を追加し、コンテンツを実行します。私の説明がはっきりしていることを望む。

    0

    なぜあなたは受け入れ答えの上にこの答えを考慮する必要があります。

    受け入れ答えはまともな提供し、スクロール位置を保存するシンプル方法は、しかし、それははるかに完璧からです。このアプローチの問題点は、回転中に画面上で見た要素が回転していないことがあります。画面の上部にあった要素は、回転後に下部に表示されるようになりました。スクロールの割合で位置を保存することはあまり正確ではなく、大きな文書ではこの不正確さが加わることがあります。

    これは別の方法です:より複雑な方法ですが、回転前に見た回転の後で同じ要素を正確に見ることがほぼ保証されています。私の意見では、これは特に大きな文書で、はるかに優れたユーザーエクスペリエンスにつながります。すべての

    ======

    まず、我々はjavascriptを介して、現在のスクロール位置を追跡します。これにより、現在どの要素が画面の最上部にあるか、どれだけスクロールされているかを正確に知ることができます。

    まず、あなたのWebViewのために有効になっているのJavaScriptのことを確認します。

    webView.getSettings().setJavaScriptEnabled(true); 
    

    次に、我々はjavascriptの内部からの情報を受け入れるJavaクラスを作成する必要があります。

    public class WebScrollListener { 
    
        private String element; 
        private int margin; 
    
        @JavascriptInterface 
        public void onScrollPositionChange(String topElementCssSelector, int topElementTopMargin) { 
         Log.d("WebScrollListener", "Scroll position changed: " + topElementCssSelector + " " + topElementTopMargin); 
         element = topElementCssSelector; 
         margin = topElementTopMargin; 
        } 
    
    } 
    

    その後、我々は、このクラスを追加to WebView:

    scrollListener = new WebScrollListener(); // save this in an instance variable 
    webView.addJavascriptInterface(scrollListener, "WebScrollListener"); 
    

    これで、htmlページにjavascriptコードを挿入する必要があります。このスクリプトは、(あなたが生成するHTMLであれば、ちょうどこのスクリプトを追加し、そうでない場合、あなたはwebView.loadUrl("javascript:document.write(" + script + ")");経由document.write()を呼び出すに頼る必要がある場合があります)Javaへスクロールデータを送信します:

    <script> 
        // We will find first visible element on the screen 
        // by probing document with the document.elementFromPoint function; 
        // we need to make sure that we dont just return 
        // body element or any element that is very large; 
        // best case scenario is if we get any element that 
        // doesn't contain other elements, but any small element is good enough; 
        var findSmallElementOnScreen = function() { 
         var SIZE_LIMIT = 1024; 
         var elem = undefined; 
         var offsetY = 0; 
         while (!elem) { 
          var e = document.elementFromPoint(100, offsetY); 
          if (e.getBoundingClientRect().height < SIZE_LIMIT) { 
           elem = e; 
          } else { 
           offsetY += 50; 
          } 
         } 
         return elem; 
        }; 
    
        // Convert dom element to css selector for later use 
        var getCssSelector = function(el) { 
         if (!(el instanceof Element)) 
          return; 
         var path = []; 
         while (el.nodeType === Node.ELEMENT_NODE) { 
          var selector = el.nodeName.toLowerCase(); 
          if (el.id) { 
           selector += '#' + el.id; 
           path.unshift(selector); 
           break; 
          } else { 
           var sib = el, nth = 1; 
           while (sib = sib.previousElementSibling) { 
            if (sib.nodeName.toLowerCase() == selector) 
             nth++; 
           } 
           if (nth != 1) 
            selector += ':nth-of-type('+nth+')'; 
          } 
          path.unshift(selector); 
          el = el.parentNode; 
         } 
         return path.join(' > ');  
        }; 
    
        // Send topmost element and its top offset to java 
        var reportScrollPosition = function() { 
         var elem = findSmallElementOnScreen(); 
         if (elem) { 
          var selector = getCssSelector(elem); 
          var offset = elem.getBoundingClientRect().top; 
          WebScrollListener.onScrollPositionChange(selector, offset); 
         } 
        } 
    
        // We will report scroll position every time when scroll position changes, 
        // but timer will ensure that this doesn't happen more often than needed 
        // (scroll event fires way too rapidly) 
        var previousTimeout = undefined; 
        window.addEventListener('scroll', function() { 
         clearTimeout(previousTimeout); 
         previousTimeout = setTimeout(reportScrollPosition, 200); 
        }); 
    </script> 
    

    あなたは、この時点でアプリケーションを実行する場合、新しいスクロール位置が受信されたことを知らせるメッセージがすでにlogcatに表示されているはずです。

    今、私たちは、WebViewの状態を保存する必要があります。我々はまた、追加する必要が

    if (savedInstanceState != null) { 
        webView.restoreState(savedInstanceState); 
        initialScrollElement = savedInstanceState.getString("scrollElement"); 
        initialScrollMargin = savedInstanceState.getInt("scrollMargin"); 
    } 
    

    @Override 
    public void onSaveInstanceState(Bundle outState) { 
        super.onSaveInstanceState(outState); 
        webView.saveState(outState); 
        outState.putString("scrollElement", scrollListener.element); 
        outState.putInt("scrollMargin", scrollListener.margin); 
    } 
    

    はその後、我々はのonCreate(活動)またはonCreateView(フラグメントのための)方法でそれを読みます私たちのWebViewにWebViewClientと上書きonPageFinished方法:

    @Override 
    public void onPageFinished(final WebView view, String url) { 
        if (initialScrollElement != null) { 
         // It's very hard to detect when web page actually finished loading; 
         // At the time onPageFinished is called, page might still not be parsed 
         // Any javascript inside <script>...</script> tags might still not be executed; 
         // Dom tree might still be incomplete; 
         // So we are gonna use a combination of delays and checks to ensure 
         // that scroll position is only restored after page has actually finished loading 
         webView.postDelayed(new Runnable() { 
          @Override 
          public void run() { 
           String javascript = "(function (selectorToRestore, positionToRestore) {\n" + 
             "  var previousTop = 0;\n" + 
             "  var check = function() {\n" + 
             "   var elem = document.querySelector(selectorToRestore);\n" + 
             "   if (!elem) {\n" + 
             "    setTimeout(check, 100);\n" + 
             "    return;\n" + 
             "   }\n" + 
             "   var currentTop = elem.getBoundingClientRect().top;\n" + 
             "   if (currentTop !== previousTop) {\n" + 
             "    previousTop = currentTop;\n" + 
             "    setTimeout(check, 100);\n" + 
             "   } else {\n" + 
             "    window.scrollBy(0, currentTop - positionToRestore);\n" + 
             "   }\n" + 
             "  };\n" + 
             "  check();\n" + 
             "}('" + initialScrollElement + "', " + initialScrollMargin + "));"; 
    
           webView.loadUrl("javascript:" + javascript); 
           initialScrollElement = null; 
          } 
         }, 300); 
        } 
    } 
    

    これはそれです。画面を回転させた後、画面上部にあった要素がそのまま残ります。