ListView из WebViews – повторное использование элементов разной высоты; Запрашивает все представления перед отображением любых

У меня есть активность, которая отображает ListView . Каждый элемент в ListView представляет собой LinearLayout состоящий из одного WebView . В списке есть потенциально сотни элементов, каждая из которых имеет разную высоту.

Первая проблема заключается в том, что при повторном использовании переработанного представления в getView() новое представление всегда является высотой исходного представления, хотя я установил layout_height как для LinearLayout и для WebView для wrap_content .

Вторая проблема заключается в том, что getView() кажется, получает вызов для каждого элемента в списке, хотя только первые пять или шесть подходят на экране. Я не видел этого при использовании других типов элементов списка. Например, в другом месте я список пользовательских представлений, которые имеют одинаковую высоту, и я вижу только, что getView() для количества представлений, которые первоначально поместились на экране.

Итак … Мне нужно выяснить, как заставить переработанные WebViews отображать их новое содержимое, чтобы их высоту можно было вычислить, а не просто использовать предыдущую высоту. И я хотел бы знать, почему система запрашивает у меня ВСЕ мои предметы в этом случае.

Вот необходимые фрагменты кода:

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

Строки построены из:

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

Это getView () в моем адаптере. HTML-фрагменты из массива строк теперь доступны.

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

Solutions Collecting From Web of "ListView из WebViews – повторное использование элементов разной высоты; Запрашивает все представления перед отображением любых"

Я думаю, проблема в том, что с кодом, который вы написали, система сначала измеряет высоту контейнера (строка linear_layout), а затем содержимое (WebView). Но второе измерение не влияет на первое, пока вы не вызовете invalidate или какой-либо метод, который заставит контейнер пересчитать его размер.

О том, почему ваш метод getView вызывается многократно, проверьте ваш метод getViewTypeCount () вашего адаптера.

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

Документация гласит, что:

«Этот метод возвращает количество типов представлений, которые будут созданы getView (int, View, ViewGroup). Каждый тип представляет собой набор представлений, которые могут быть преобразованы в getView (int, View, ViewGroup). Если адаптер всегда возвращает Тот же тип представления для всех элементов, этот метод должен возвращать 1. Этот метод будет вызываться только тогда, когда адаптер установлен в AdapterView. "

У меня возникла проблема с этим методом, давая большое количество (например, 5000), из-за которого произошел сбой приложения. Похоже, он используется внутри для некоторых вычислений ListView.

Надеюсь, что это поможет.

Кстати, есть очень хороший разговор о мире ListViews

У меня была такая же проблема с моим ExpandableListView. Из других сообщений это, как представляется, постоянная проблема с несколькими веб-просмотрами в одном списке. Работа вокруг, которую я нашел, заключалась в создании и массиве wewViews при первом вызове getView, а затем возвращении webView с правильным индексом. Его немного больше памяти, но это не приводит к изменению размера представления.

 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 

Я пробовал решение sirFunkenstine, которое отлично работает. Только клиент, для которого я создаю это приложение, не понравился. И я должен был признать, что это было не так гладко.

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

Решение

Поэтому я подумал, что было бы лучше настроить контент и высоту WebView вместо добавления / удаления. Таким образом, у нас есть только количество WebView, которые в настоящее время видны в памяти. И мы можем использовать функциональность повторного использования ListView / Adapter.

Таким образом, я добавил единый WebView за ListView, который постоянно вычисляет высоту WebView для следующих элементов. Вы можете получить высоту с помощью метода WebView getContentHeight. Вам нужно будет сделать это в методе onPageFinished, чтобы у вас была конечная высота. Это вызвало еще одну проблему, потому что метод возвращает ноль, когда содержимое загружается с помощью методов loadDataWithBaseURL или loadData. Похоже, что значение устанавливается в конце концов, но еще не в то время, когда вызывается onPageFinished. Чтобы преодолеть это, вы можете добавить поток, который постоянно проверяет, не потерял ли getContentHeight больше нуля. Если он не равен нулю, значение устанавливается, и вы можете загрузить следующий.

Все это решение немного взломано, но это дало мне приятный и гладкий ListView, включая WebViews с разной высотой.

Пример кода:

1: Очередь в позиции строк, которые вы хотите предварительно загрузить:

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

2: Запустите Runnable для постоянной загрузки новых элементов из очереди и проверки / добавления высоты в массив:

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

3: Загрузите новый HTML-контент и проверьте высоту:

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

Мое решение (даже при том, что я поставил щедрость вверх), потому что проблема заключалась в том, чтобы поместить ImageView в FrameLayout, а не разумно рос.

Недействительность вычисления вида или форсирования раскладки ничего не сделала для меня, но контейнер framelayout для образа просмотра, казалось, решил все. Я предполагаю, что это поведение ошибочно, но обходной путь был достаточно простым.

Возможно, то же самое относится и к веб-просмотру OP.