ListView from WebViews - reuse of elements of different heights; requests all views before displaying any

I have an activity that displays a ListView . Each item in a ListView is a LinearLayout consisting of one WebView . There are potentially hundreds of elements in the list, each of which has a different height.

The first problem is that when you reuse the redesigned view in getView() new view is always the height of the original view, even if I set layout_height for LinearLayout and WebView to wrap_content .

The second problem is that getView() seems to get a call for each item in the list, although only the first five or six fit on the screen. I did not see this when using other types of list items. For example, in another place, I list custom views that have the same height, and I only see getView() for the number of views that originally fit on the screen.

So ... I need to figure out how to make recycled WebViews display their new content so that their height can be calculated, and not just use the previous height. And I would like to know why the system asks me ALL of my items in this case.

Code fragments are required here:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ListView android:id="@+id/topPane" android:layout_width="fill_parent" android:layout_height="fill_parent" android:dividerHeight="1.0px" android:divider="#FFFFFF" android:smoothScrollbar="false" /> </LinearLayout> 

Rows are built from:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="wrap_content" > <WebView android:id="@+id/rowWebView" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="0.0px" android:scrollbars="none" /> </LinearLayout> 

This is getView () in my adapter. HTML fragments are now from an array of strings.

  @Override public View getView(int position, View convertView, ViewGroup parent) { String item = (String) getItem(position); if (convertView == null) { convertView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.rowview, parent, false); } WebView wv = (WebView) convertView.findViewById(R.id.rowWebView); wv.loadDataWithBaseURL(null, item, "text/html", "utf-8", "about:blank"); return convertView; } 
+8
android android-listview android-webview android-linearlayout
source share
4 answers

I think the problem is that with the code you wrote, the system first measures the height of the container (row linear_layout) and then the content (WebView). But the second measure does not affect the first, until you call invalidate or any method that causes the container to recount its size.

About why your getView method is called multiple times, check your adapterโ€™s getViewTypeCount () method.

 @Override public int getViewTypeCount() { return NumberOfTypeOfViewsInYourList; } 
code>

The documentation states that:

"this method returns the number of view types that getView (int, View, ViewGroup) will create. each type is a set of views that can be converted to getView (int, View, ViewGroup). If the adapter always returns the same view type for of all items, this method should return 1. This method will only be called when the adapter is installed on the AdapterView. "

I had a problem with this method giving a large number (e.g. 5000) which caused the application to crash. It looks like it was used internally for some ListView calculations.

Hope this helps.

By the way, there is a very good talk about the world of ListViews

+4
source share

I had the same issue with my ExpandableListView. Of the other posts, this seems to be a constant problem with multiple webviews on the same list. The work around that I found was to create and array a wewView the first time getView was called, and then return the webView at the correct index. Its a bit more memory, but it does not resize the view.

 SparseArray<WebView> wvGroup = new SparseArray<WebView>(); @Override public View getView(int position, View convertView, ViewGroup parent) { WebView wv = null; if (wvGroup.size() < 1) { for (int i = 0; i < sectionItems.size(); i++) { WebView webViewItem = new WebView(context); String htmlData = "<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />" + sectionItems.get(i).child_content; webViewItem.loadDataWithBaseURL("file:///android_assets/", htmlData, "text/html", "UTF-8", null); wvGroup.append(i, webViewItem); } } wv = wvGroup.get(groupPosition); wv.setPadding(30, 10, 10, 10); return wv 
+3
source share

I tried sirFunkenstine solution that works fine. Only the client for whom I am creating this application did not like the performance. And I had to admit that it was not so smooth.

It seemed that adding and removing preinstalled webviews was where the problem is. The user interface was frozen for a split second each time the application added / removed a new WebView for the next line.

Decision

So I thought it would be better to adjust the content and height of the WebView instead of adding / removing. Thus, we only have the number of WebViews that are currently visible in memory. And we can use ListView / Adapter reuse functions.

So, I added a single WebView behind the ListView, which constantly calculates the height of the WebView for the following items. You can get the height using the WebView method getContentHeight. You will need to do this in the onPageFinished method so that you have the final height. This caused another problem, so the method returns zero when the content is loaded using the loadDataWithBaseURL or loadData methods. It seems that the value is being set in the end, but not at the time that onPageFinished is called. To overcome this, you can add a thread that constantly checks to see if getContentHeight has lost more than zero. If it is not equal to zero, the value is set and you can load the next one.

This whole solution is a bit hacky, but it gave me a nice and smooth ListView, including WebViews with different heights.

Code example:

1: The queue at the position of the lines you want to preload:

 private SparseArray<Integer> mWebViewHeights = new SparseArray<Integer>(); private LinkedBlockingQueue mQueue; { mQueue = new LinkedBlockingQueue(); try { mQueue.put(position); } catch (InterruptedException e) { e.printStackTrace(); } } 

2: Run Runnable to constantly load new items from the queue and check / add height to the array:

 Handler h; Runnable rowHeightCalculator = new Runnable() { h = new Handler(); rowHeightCalculator.run(); } 

3: Download the new HTML content and check the height:

 Handler h; Runnable rowHeightCalculator = new Runnable() { int mCurrentIndex = -1; boolean mLoading = false; // Read the webview height this way, cause oncomplete only returns 0 for local HTML data @Override public void run() { if(Thread.interrupted()) return; if (mCurrentIndex == -1 && mQueue.size() > 0) { try { mCurrentIndex = (Integer)mQueue.take(); String html = mItems.get(mCurrentIndex).getPart().getText(); mDummyWebView.clearView(); mDummyWebView.loadDataWithBaseURL("file:///android_asset/reader/", "THE HTML HERE", "text/html", "UTF-8", null); mLoading = true; } } catch (InterruptedException e) { e.printStackTrace(); } } if(mDummyWebView.getContentHeight() != 0 && mCurrentIndex >= 0) { int contentHeight = mDummyWebView.getContentHeight(); mWebViewHeights.append(mCurrentIndex, contentHeight); mCurrentIndex = -1; mLoading = false; } // Reload view if we loaded 20 items if ((mQueue.size() == 0 && (mItemsCount - mQueue.size()) < 20) || (mItemsCount - mQueue.size()) == 20) { notifyDataSetChanged(); } if (mQueue.size() > 0 || mLoading) { h.postDelayed(this, 1); } } }; 
+3
source share

My solution (even though I set the bounty up), because my problem was to put ImageView in FrameLayout, than intellectually.

An invalid view or forced layout calculation did nothing for me, but the framelayout container for the view image seemed to solve everything. I assume this behavior is erroneous, but the workaround was quite simple.

Perhaps the same thing applies to web browsing OP.

+1
source share

All Articles