8 Stimmen

ListView von WebViews - Wiederverwendung von Elementen unterschiedlicher Höhe schlägt fehl; fragt nach allen Ansichten, bevor eine angezeigt wird

Ich habe eine Aktivität, die eine ListView . Jedes Element in der ListView es un LinearLayout bestehend aus einer WebView . Die Liste kann Hunderte von Einträgen enthalten, die alle eine unterschiedliche Höhe haben.

Das erste Problem ist, dass bei der Wiederverwendung einer wiederverwendeten Ansicht in getView() ist die neue Ansicht immer so hoch wie die ursprüngliche Ansicht, auch wenn ich die Einstellung layout_height sowohl für die LinearLayout und die WebView まで wrap_content .

Das zweite Problem ist, dass getView() scheint für jedes Element in der Liste aufgerufen zu werden, obwohl nur die ersten fünf oder sechs auf den Bildschirm passen. Bei anderen Listenelementtypen habe ich dieses Problem nicht gesehen. An anderer Stelle habe ich beispielsweise eine Liste mit benutzerdefinierten Ansichten, die alle die gleiche Höhe haben, und ich sehe nur getView() für die Anzahl der Ansichten, die ursprünglich auf den Bildschirm passten, aufgerufen wird.

Ich muss also herausfinden, wie ich die Wiederverwertung von Daten erzwingen kann. WebViews um ihre neuen Inhalte zu rendern, so dass ihre Höhe berechnet werden kann, anstatt einfach die vorherige Höhe zu verwenden. Und ich würde gerne wissen, warum das System mich in diesem Fall nach ALLEN meinen Artikeln fragt.

Hier sind die erforderlichen Codeschnipsel:

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

Reihen werden aus gebildet:

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

Dies ist getView() in meinem Adapter. HTML-Schnipsel kommen aus einem Array von Strings für jetzt.

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

5voto

Axel M. Garcia Punkte 5078

Ich denke, das Problem ist, dass das System mit dem von Ihnen geschriebenen Code zuerst die Höhe des Containers (Zeile linear_layout) und später den Inhalt (WebView) misst. Aber die zweite Messung wirkt sich nicht auf die erste aus, bis Sie invalidate oder eine Methode aufrufen, die den Container dazu bringt, seine Größe neu zu berechnen.

Warum Ihre getView Methode so oft aufgerufen wird, überprüfen Sie Ihre getViewTypeCount() Methode Ihres Adapters.

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

Dokumentation heißt es:

"Diese Methode gibt die Anzahl der Typen von Ansichten, die getView(int, View, ViewGroup) erstellt werden. Jeder Typ repräsentiert einen Satz von Views die in getView(int, View, ViewGroup) konvertiert werden können, ViewGroup) konvertiert werden können. Wenn der Adapter immer immer den gleichen Typ von View für alle Elemente zurückgibt, sollte diese Methode 1 zurückgeben. Diese Methode wird nur aufgerufen wenn wenn der Adapter auf den AdapterView gesetzt wird. AdapterView."

Ich hatte ein Problem mit dieser Methode, geben eine große Zahl (wie 5000) machte meine App abstürzen, es sieht aus wie es intern für einige ListView Berechnungen verwendet wird.

Ich hoffe, es hilft irgendwie.

Übrigens, es gibt einen sehr guten Vortrag über die Welt der ListViews

3voto

sirFunkenstine Punkte 7585

Ich hatte das gleiche Problem mit meinem ExpandableListView. Von anderen Beiträgen scheint es ein laufendes Problem mit mehreren Webviews in einer Liste zu sein. Der Workaround, den ich gefunden habe, war die Erstellung eines Arrays von WebViews beim ersten Aufruf von getView und die anschließende Rückgabe des WebViews mit dem richtigen Index. Seine ein bisschen mehr Speicher intensiv, aber es does'nt Ursache der Ansicht, Größe zu ändern.

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

3voto

Ben Groot Punkte 4960

Ich habe die Lösung von sirFunkenstine ausprobiert, die gut funktioniert. Nur der Kunde, für den ich diese App baue, mochte die Leistung nicht. Und ich musste zugeben, dass es nicht so glatt war.

Es schien, dass das Hinzufügen und Entfernen der vorgeladenen WebViews das Problem war. Die Benutzeroberfläche wurde jedes Mal für einen Sekundenbruchteil eingefroren, wenn die App die neue WebView für die nächste Zeile hinzufügte/entfernte.

Solución

Daher dachte ich, es wäre besser, nur den Inhalt und die Höhe der WebView anzupassen, anstatt sie hinzuzufügen oder zu entfernen. Auf diese Weise haben wir nur die Anzahl der WebViews im Speicher, die gerade sichtbar sind. Und wir können die Wiederverwendungsfunktionalität der ListView/Adapter nutzen.

Also habe ich eine einzelne WebView hinter der ListView hinzugefügt, die ständig die WebView-Höhe der nächsten Elemente berechnet. Man kann die Höhe mit der WebView Methode getContentHeight ermitteln. Sie müssen dies in der onPageFinished-Methode tun, damit Sie die endgültige Höhe haben. Dies hat ein weiteres Problem aufgeworfen, denn die Methode gibt Null zurück, wenn der Inhalt mit loadDataWithBaseURL oder loadData Methoden geladen wird. Es scheint, dass der Wert irgendwann gesetzt wird, aber noch nicht zu dem Zeitpunkt, an dem die onPageFinished-Methode aufgerufen wird. Um dies zu umgehen, kann man einen Thread hinzufügen, der ständig überprüft, ob getContentHeight nicht mehr Null ist. Ist dies nicht der Fall, wird der Wert gesetzt und Sie können die nächste Seite laden.

Die ganze Lösung ist ein bisschen hakelig, aber ich habe damit eine schöne und glatte ListView mit WebViews in verschiedenen Höhen.

Einige Beispielcodes:

1: Stellen Sie die Positionen der Zeilen, die Sie vorladen möchten, in eine Warteschlange:

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

2: Starten Sie die Runnable, um ständig neue Elemente aus der Warteschlange zu laden und die Höhe zu überprüfen/einem Array hinzuzufügen:

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

3: Laden Sie neue HTML-Inhalte und überprüfen Sie die Höhen:

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

1voto

HaMMeReD Punkte 2430

Meine Lösung (auch wenn ich das Kopfgeld ausgesetzt habe) für mein Problem war es, die ImageView in ein FrameLayout zu setzen, es dann intelligent wächst.

Invalidieren der Ansicht oder erzwingen Layout-Berechnungen tat nichts für mich, aber die framelayout Container für die imageview schien alles zu lösen. Ich nehme an, dieses Verhalten ist in err, aber die Umgehung war einfach genug.

Vielleicht gilt das auch für die Webansicht des Auftraggebers.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X