2011-07-28 33 views
24

Các trình duyệt trong Android 2.3+ thực hiện tốt công việc duy trì vị trí cuộn nội dung theo hướng đã thay đổi.Duy trì vị trí cuộn nội dung WebView trên thay đổi hướng

Tôi đang cố gắng đạt được điều tương tự cho một WebView mà tôi hiển thị trong một hoạt động nhưng không thành công.

Tôi đã thử nghiệm thay đổi tệp kê khai (android: configChanges = "orientation | keyboardHidden") để tránh hoạt động giải trí trên xoay do tỷ lệ khung hình khác nhau vị trí cuộn không được đặt ở vị trí tôi muốn. Hơn nữa, đây là không phải một giải pháp cho tôi vì tôi cần phải có các bố cục khác nhau trong chân dung và phong cảnh.

Tôi đã cố gắng lưu trạng thái WebView và khôi phục trạng thái này nhưng điều này sẽ tiếp tục lại trong nội dung được hiển thị ở trên cùng một lần nữa.

Hơn nữa, cố di chuyển trong onPageFinished bằng cách sử dụng scrollTo không hoạt động mặc dù chiều cao của WebView khác 0 tại thời điểm này.

Bất kỳ ý tưởng nào? Cảm ơn trước. Peter.

phần Giải pháp:

Đồng nghiệp của tôi quản lý để có được di chuyển làm việc thông qua một giải pháp javascript. Để cuộn đơn giản đến cùng một vị trí dọc, vị trí cuộn 'Y' của WebView được lưu trong trạng thái onSaveInstanceState. Sau đây là sau đó thêm vào 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); 
} 

Bạn không nhận được sự rung nhẹ như nội dung nhảy từ đầu đến vị trí cuộn mới nhưng nó là hầu như không đáng chú ý. Bước tiếp theo là thử phương pháp dựa trên phần trăm (dựa trên sự khác biệt về chiều cao) và cũng điều tra việc WebView lưu và khôi phục trạng thái của nó.

Trả lời

37

Để khôi phục vị trí hiện tại của WebView trong khi thay đổi định hướng Tôi sợ bạn sẽ phải làm điều đó bằng tay.

tôi đã sử dụng phương pháp này:

  1. Tính phần trăm thực tế của cuộn trong WebView
  2. Save nó trong onRetainNonConfigurationInstance
  3. Khôi phục vị trí của WebView khi nó tái

Vì chiều rộng và chiều cao của WebView không giống nhau ở chế độ dọc và ngang, tôi sử dụng phần trăm để biểu thị vị trí cuộn của người dùng.

Từng bước:

1) Tính phần trăm thực tế của cuộn trong WebView

// 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) Lưu nó trong onRetainNonConfigurationInstance

Lưu tiến độ ngay trước khi thay đổi hướng

@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) Khôi phục vị trí của WebView khi nó tái

Lấy tiến bộ từ các dữ liệu thay đổi hướng

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

Để khôi phục lại vị trí hiện tại, bạn sẽ phải chờ trang được nạp lại ( phương pháp này có thể có vấn đề nếu trang của bạn mất một thời gian dài để tải)

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

Trong khi thử nghiệm tôi gặp phải rằng bạn cần đợi một chút sau khi phương thức onPageFinished được gọi để làm cho cuộn hoạt động. 300ms nên ổn. Sự chậm trễ này làm cho màn hình nhấp nháy (hiển thị đầu tiên ở dạng cuộn 0 rồi đến đúng vị trí).

Có thể có một cách khác tốt hơn để làm điều đó nhưng tôi không biết.

+0

@ ol_v-er Cảm ơn thông tin. Tôi quan tâm để thử giới thiệu sự chậm trễ như bạn mô tả để xem công việc 'scrollTo' (Tôi ngạc nhiên rằng một sự chậm trễ là cần thiết nhưng anyway ...). Cuối cùng đã có cuộn để làm việc thông qua một giải pháp javascript (mà tôi sẽ đăng lên sớm) Tôi đã có ý tưởng về tỷ lệ phần trăm cuộn mà tôi đã không có xung quanh để cố gắng chưa thấy kết quả. – PJL

+0

Trong thực tế khi gọi lại onPageFinished được gọi, WebView chỉ bắt đầu vẽ. Độ trễ là đảm bảo WebView đã vẽ xong (và có chiều cao chính xác). Mong cho giải pháp javascript của bạn. –

+0

Cảm ơn, đã thêm giải pháp javascript của tôi. – PJL

1

onPageFinished có thể không được gọi vì bạn không tải lại trang, bạn chỉ đang thay đổi hướng, không chắc chắn nếu điều này gây ra tải lại hay không.

Thử sử dụng cuộnTrong phương thức onConfigurationThay đổi hoạt động của bạn.

+0

Tôi nhận được 'onPageFinished' trên một thay đổi định hướng nhưng gọi 'WebView :: scrollTo' không có hiệu lực. – PJL

+0

Hơn nữa, có lẽ giải pháp này sẽ làm việc cho một số người tuy nhiên tôi muốn hoạt động khởi động lại trên một sự thay đổi định hướng. – PJL

1

Thay đổi khía cạnh sẽ rất có thể luôn khiến vị trí hiện tại trong WebView của bạn không ở đúng vị trí để cuộn đến sau đó. Bạn có thể lén lút và xác định phần tử dễ nhìn thấy nhất trong WebView và sau khi cấy ghép thay đổi định hướng anchor tại điểm đó trong nguồn và chuyển hướng người dùng đến nó ...

+0

Cảm ơn, tôi không thể không cảm thấy rằng cần có giải pháp đơn giản/tích hợp cho vấn đề này. – PJL

0

để tính tỷ lệ phần trăm phù hợp của WebView, điều quan trọng là phải đếm số mWebView.getScale(). Trên thực tế, giá trị trả lại của getScrollY() tương ứng với mWebView.getContentHeight() * mWebView.getScale().

0

Cách mà chúng tôi đã quản lý để đạt được điều này để có tham chiếu cục bộ đến WebView trong Phân đoạn của chúng tôi.

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

Sau đó setRetainInstance để true trong onCreate

@Override 
public void onCreate(final Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setRetainInstance(true); 

    // Argument loading settings 
} 

Sau đó, khi onCreateView được gọi là, trả lại trường hợp địa phương hiện có của WebView, nếu không nhanh chóng một bản sao mới thành lập nội dung và các thiết lập khác. Bước quan trọng anh ta là khi reattaching WebView để loại bỏ các phụ huynh mà bạn có thể nhìn thấy trong mệnh đề khác dưới đây.

@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

Tôi nhận được một con trỏ null lần thứ hai tôi quay trở lại mảnh có chứa webview. Fragment 1 tải với webview. Tôi di chuyển đến một vị trí, di chuyển đến đoạn 2, di chuyển trở lại mảnh vỡ 1. Mọi thứ đều ổn. Tuy nhiên, sau đó tôi chuyển sang đoạn 2 và sau đó quay lại đoạn 1 và ứng dụng gặp sự cố với con trỏ rỗng. Ý tưởng nào? – piratemurray

+0

Cách giải quyết này gây ra rò rỉ bộ nhớ vì WebView của bạn sẽ giữ cho Ngữ cảnh của Hoạt động ban đầu trong khi được gắn với Hoạt động mới. Tránh điều này. – BladeCoder

0

Tôi đã sử dụng giải pháp từng phần được mô tả trong bài đăng gốc, nhưng thay vào đó hãy đặt đoạn mã javascript bên trong onPageStarted(), thay vì onPageFinished(). Dường như làm việc cho tôi mà không nhảy.

Trường hợp sử dụng của tôi hơi khác: Tôi đang cố gắng giữ vị trí nằm ngang sau khi người dùng làm mới trang.

Nhưng tôi đã có thể sử dụng giải pháp của bạn và nó hoạt động hoàn hảo đối với tôi :)

0

Trong giải pháp phần mô tả trong bài gốc, bạn có thể cải thiện các giải pháp nếu thay đổi đoạn mã sau

private float calculateProgression(WebView content) { 
    float contentHeight = content.getContentHeight(); 
    float currentScrollPosition = content.getScrollY(); 
    float percentWebview = currentScrollPosition/ contentHeight; 
    return percentWebview; 
} 

do vị trí trên cùng của thành phần, khi bạn đang sử dụng content.getTop() là không cần thiết; Vì vậy, những gì nó làm là thêm vào vị trí thực tế của vị trí cuộn Y nơi mà các thành phần được đặt đối với nó cha mẹ và chạy xuống nội dung. Tôi hy vọng lời giải thích của tôi là rõ ràng.

0

Tại sao bạn nên cân nhắc câu trả lời này qua câu trả lời được chấp nhận:

câu trả lời chấp nhận cung cấp đàng hoàng và đơn giản cách để tiết kiệm di chuyển vị trí, tuy nhiên nó còn xa mới hoàn hảo. Vấn đề với cách tiếp cận đó là đôi khi trong quá trình quay, bạn thậm chí sẽ không thấy bất kỳ phần tử nào bạn nhìn thấy trên màn hình trước khi quay. Phần tử ở đầu màn hình giờ có thể ở dưới cùng sau khi xoay. Lưu vị trí thông qua phần trăm cuộn không phải là rất chính xác và trên các tài liệu lớn không chính xác này có thể tăng lên. Vì vậy, đây là một phương pháp khác: nó là cách phức tạp hơn, nhưng nó gần như đảm bảo rằng bạn sẽ thấy chính xác cùng một yếu tố sau khi quay mà bạn đã thấy trước khi quay. Theo tôi, điều này dẫn đến trải nghiệm người dùng tốt hơn nhiều, đặc biệt là trên một tài liệu lớn.

======

Trước hết, chúng tôi sẽ theo dõi vị trí cuộn hiện tại qua javascript. Điều này sẽ cho phép chúng tôi biết chính xác phần tử nào hiện đang ở đầu màn hình và phần tử được cuộn.

Thứ nhất, đảm bảo rằng javascript được kích hoạt cho WebView của bạn:

webView.getSettings().setJavaScriptEnabled(true); 

Tiếp theo, chúng ta cần tạo lớp java mà sẽ chấp nhận thông tin từ bên trong javascript:

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

} 

Sau đó chúng ta thêm lớp này tới WebView:

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

Bây giờ, chúng tôi cần chèn mã javascript vào trang html. Kịch bản này sẽ gửi dữ liệu cuộn để java (nếu bạn là thế hệ html, chỉ cần nối thêm kịch bản này, nếu không, bạn có thể cần phải nghỉ mát để gọi document.write() qua webView.loadUrl("javascript:document.write(" + script + ")");):

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

Nếu bạn chạy ứng dụng của bạn vào thời điểm này, bạn sẽ thấy các thông báo trong logcat cho bạn biết rằng vị trí cuộn mới được nhận.

Bây giờ chúng ta cần phải lưu trạng thái WebView:

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

Sau đó, chúng ta đọc nó trong onCreate (đối với hoạt động) hoặc onCreateView (đối với đoạn) phương pháp:

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

Chúng tôi cũng cần thêm WebViewClient vào webView của chúng tôi và ghi đè phương thức 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); 
    } 
} 

Đây là nó. Sau khi xoay màn hình, phần tử ở đầu màn hình của bạn sẽ vẫn ở đó.

Các vấn đề liên quan