ListView of 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;
}
4 ответа
Я думаю, что проблема в том, что с помощью написанного вами кода система сначала измеряет высоту контейнера (строка 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
Я пробовал решение sirFunkenstine, которое прекрасно работает. Только клиенту, для которого я создаю это приложение, не понравилось исполнение. И я должен был признать, что это было не так гладко.
Казалось, что добавление и удаление предустановленных WebViews, где проблема. Пользовательский интерфейс зависал на долю секунды каждый раз, когда приложение добавляло / удаляло новый WebView для следующей строки.
Решение
Поэтому я подумал, что было бы лучше настроить только содержимое и высоту WebView, а не добавлять / удалять. Таким образом, у нас есть только количество веб-представлений, которые в настоящее время видны в памяти. И мы можем использовать функцию повторного использования ListView / Adapter.
So I've added a single WebView behind the ListView which constantly calculates the WebView height of the next items. You can get the height with the WebView method getContentHeight. You'll have to do this in the onPageFinished method so you have the final height. This raised another issue, cause the method returns zero when the content is loaded with loadDataWithBaseURL or loadData methods. It seems that the value is set eventually, but not yet at the time that the onPageFinished is called. To overcome this you can add a thread which constantly checks if the getContentHeight is not zero anymore. If it is not zero then 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.
Пример кода:
1: Queue the positions of the rows which 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: Start the Runnable to constantly load new items from the queue and check/add the height to an array:
Handler h;
Runnable rowHeightCalculator = new Runnable() {
h = new Handler();
rowHeightCalculator.run();
}
3: Load new HTML content and check the heights:
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);
}
}
};
У меня была такая же проблема с моим 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
Моим решением (хотя я и положил вознаграждение) для моей проблемы было поместить ImageView в FrameLayout, он разумно вырос.
Отказ от просмотра или принудительное вычисление макета ничего не сделали для меня, но контейнер с разметкой кадра для просмотра изображений, казалось, решил все. Я предполагаю, что это поведение ошибочно, но обходной путь был достаточно простым.
Может быть, то же самое будет применяться к веб-просмотру ОП.