Android: Ich kann ViewPager WRAP_CONTENT nicht haben

258

Ich habe einen einfachen ViewPager eingerichtet, der eine ImageView mit einer Höhe von 200 dp auf jeder Seite hat.

Hier ist mein Pager:

pager = new ViewPager(this);
pager.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
pager.setBackgroundColor(Color.WHITE);
pager.setOnPageChangeListener(listener);
layout.addView(pager);

Trotz der als wrap_content festgelegten Höhe füllt der Pager immer den Bildschirm aus, obwohl die Bildansicht nur 200 dp beträgt. Ich habe versucht, die Höhe des Pagers durch "200" zu ersetzen, aber das führt zu unterschiedlichen Ergebnissen bei mehreren Auflösungen. Ich kann diesem Wert kein "dp" hinzufügen. Wie füge ich dem Layout des Pagers 200 dp hinzu?

Adam
quelle

Antworten:

408

Wenn Sie ViewPagerFolgendes wie folgt überschreiben, wird die Größe des größten Kindes erreicht, das es derzeit hat.

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int height = 0;
    for(int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
        int h = child.getMeasuredHeight();
        if(h > height) height = h;
    }

    if (height != 0) {
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
    }

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
Daniel López Lacalle
quelle
24
Dies kommt dem, was ich brauche, am nächsten, aber es gibt zwei Dinge, die hinzugefügt werden müssen: 1. Der ViewPager ändert die Größe nur auf das größte seiner tatsächlichen untergeordneten Elemente, dh nur auf das aktuell sichtbare Element und die direkt benachbarten. Das Aufrufen von setOffscreenPageLimit (Gesamtzahl der untergeordneten Elemente) im ViewPager löst dieses Problem und führt zu einem ViewPager, dessen Größe auf das größte aller Elemente festgelegt ist und dessen Größe niemals geändert wird. 2. WebViews haben einige seltsame Probleme beim Versuch, sie zu messen. Das Aufrufen von requestLayout () in einer WebView nach dem Laden löst dies.
0101100101
3
Es gibt nur ein kleines Problem, das ich beheben werde: Wenn der viewPager für GONE sichtbar ist und Sie ihn sichtbar machen, wird onMeasure aufgerufen, bevor sein Fragment erstellt wird. Es wird also eine Höhe von 0 haben. Wenn jemand eine Idee hat, ist er willkommen. Ich denke, ich werde mit einem Rückruf gehen, wenn das Fragment erstellt wird
edoardotognoni
4
Dies funktioniert nicht, wenn Sie untergeordnete Dekoransichten haben. Dies liegt daran, dass ViewPager.onMeasure () die Dekoransichten misst und ihnen zuerst Speicherplatz zuweist und dann den Rest des Speicherplatzes den nicht dekorierten untergeordneten Ansichten zuweist. Trotzdem ist dies bei weitem die am wenigsten falsche Lösung hier, also habe ich gestimmt;)
Benjamin Dobell
3
Ich komme immer jedes Mal diese zurück ich einen ViewPager verwenden
Ono
7
getChildCount () gibt möglicherweise 0 zurück, während Sie setAdapter () bereits in ViewPager ausgeführt haben! Der eigentliche Aufruf von populate () (der die Ansichten erstellt) erfolgt innerhalb von super.onMeasure (widthMeasureSpec, heightMeasureSpec). Anruf. Das Setzen des zusätzlichen Aufrufs super.onMeasure () am Anfang dieser Funktion hat den Trick getan. Überprüfen Sie auch stackoverflow.com/questions/38492210/…
Southerton
106

Eine andere allgemeinere Lösung besteht darin, wrap_contenteinfach an die Arbeit zu gehen.

Ich habe erweitert ViewPager, um zu überschreiben onMeasure(). Die Höhe wird um die erste untergeordnete Ansicht gewickelt. Dies kann zu unerwarteten Ergebnissen führen, wenn die untergeordneten Ansichten nicht genau dieselbe Höhe haben. Dafür kann die Klasse leicht erweitert werden, um beispielsweise die Größe der aktuellen Ansicht / Seite zu animieren. Aber das habe ich nicht gebraucht.

Sie können diesen ViewPager in Ihren XML-Layouts genau wie den ursprünglichen ViewPager verwenden:

<view
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    class="de.cybergen.ui.layout.WrapContentHeightViewPager"
    android:id="@+id/wrapContentHeightViewPager"
    android:layout_alignParentBottom="true"
    android:layout_alignParentLeft="true"/>

Vorteil: Mit diesem Ansatz können Sie den ViewPager in jedem Layout einschließlich RelativeLayout verwenden, um andere UI-Elemente zu überlagern.

Ein Nachteil bleibt: Wenn Sie Ränder verwenden möchten, müssen Sie zwei verschachtelte Layouts erstellen und dem inneren die gewünschten Ränder geben.

Hier ist der Code:

public class WrapContentHeightViewPager extends ViewPager {

    /**
     * Constructor
     *
     * @param context the context
     */
    public WrapContentHeightViewPager(Context context) {
        super(context);
    }

    /**
     * Constructor
     *
     * @param context the context
     * @param attrs the attribute set
     */
    public WrapContentHeightViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // find the first child view
        View view = getChildAt(0);
        if (view != null) {
            // measure the first child view with the specified measure spec
            view.measure(widthMeasureSpec, heightMeasureSpec);
        }

        setMeasuredDimension(getMeasuredWidth(), measureHeight(heightMeasureSpec, view));
    }

    /**
     * Determines the height of this view
     *
     * @param measureSpec A measureSpec packed into an int
     * @param view the base view with already measured height
     *
     * @return The height of the view, honoring constraints from measureSpec
     */
    private int measureHeight(int measureSpec, View view) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            // set the height from the base view if available
            if (view != null) {
                result = view.getMeasuredHeight();
            }
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

}
Cybergen
quelle
34
Hat noch jemand eine leere Seite neben dem aktuellen Element, als der Viewpager zerstört und wieder geöffnet wurde?
Zyoo
1
Ich habe auch leere Seiten.
Aeren
10
Sie müssen nur zwei Top-Antworten auf diese Frage zusammenführen, wie in meinem Blog beschrieben: pristalovpavel.wordpress.com/2014/12/26/…
anil
4
Ersetzen Sie einfach den Code der 'onMeasure'-Methode durch die Antwort von' Daniel López Lacalle '.
Yog Guru
1
Toll..! Arbeitete für mich .. @cybergen Vielen Dank, du hast meinen Tag gerettet ..!
Dnyanesh M
59

Ich habe meine Antwort auf Daniel López Lacalle und diesen Beitrag http://www.henning.ms/2013/09/09/viewpager-that-simply-dont-measure-up/ gestützt . Das Problem mit Daniels Antwort ist, dass meine Kinder in einigen Fällen eine Größe von Null hatten. Die Lösung bestand leider darin, zweimal zu messen.

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int mode = MeasureSpec.getMode(heightMeasureSpec);
    // Unspecified means that the ViewPager is in a ScrollView WRAP_CONTENT.
    // At Most means that the ViewPager is not in a ScrollView WRAP_CONTENT.
    if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) {
        // super has to be called in the beginning so the child views can be initialized.
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int height = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
            int h = child.getMeasuredHeight();
            if (h > height) height = h;
        }
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
    }
    // super has to be called again so the new specs are treated as exact measurements
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

Auf diese Weise können Sie auch eine Höhe auf dem ViewPager festlegen, wenn Sie dies möchten oder nur wrap_content.

MinceMan
quelle
Ich hatte das gleiche Problem und löste es mit Ihrer Antwort, danke. Aber eine Erklärung, warum?
Bart Burg
Ich denke, sie hatten nicht die Absicht, Wrap-Inhalte zu unterstützen, da ich nicht glaube, dass dies ein normaler Anwendungsfall war. Um dies zu unterstützen, müssen wir uns selbst neu messen, nachdem unsere Kinder gemessen wurden, damit wir Inhalte einpacken können.
MinceMan
Warum sind Bilder in diesem ViewPager actualy kürzer als die in einem Image, die denselben verwendet scaleTypeund in ähnlicher Weise, layout_width=match_parentwie auch layout_height=wrap_content? dort fehlen etwa 20dp.
Hai
Hai, ich bin mir wirklich nicht sicher. Das könnte etwas damit zu tun haben, was Ihr Skalentyp tatsächlich tut. Vielleicht möchten Sie versuchen, eine Höhe einzustellen.
MinceMan
1
Ich kann es nicht glauben! Ich habe 2 Tage damit verbracht, meinen benutzerdefinierten Viewpager zusammenzukleben, und bin auf ein Problem gestoßen, als meine anfängliche Ansicht nicht angezeigt wurde und ich einfach nicht herausfinden konnte, warum! // super has to be called in the beginning so the child views can be initialized.<----- DAS war der Grund, musste es am Anfang und am Ende der onMeasure-Funktion aufrufen. Yippiii, heute virtuelle High Fives auf mich!
Starwave
37

Ich habe gerade eine sehr ähnliche Frage dazu beantwortet und habe diese zufällig gefunden, als ich nach einem Link gesucht habe, um meine Behauptungen zu stützen, also viel Glück :)

Meine andere Antwort:
Der ViewPager unterstützt nicht, wrap_contentda (normalerweise) nie alle untergeordneten Elemente gleichzeitig geladen werden und daher keine geeignete Größe erhalten werden kann (die Option wäre, einen Pager zu haben, dessen Größe sich bei jedem Wechsel ändert Seite).

Sie können jedoch eine genaue Abmessung (z. B. 150 dp) festlegen und match_parentfunktionieren auch.
Sie können die Dimensionen auch dynamisch aus Ihrem Code heraus ändern, indem Sie das heightAttribut -attribute in seinem Code ändern LayoutParams.

Für Ihre Anforderungen können Sie den ViewPager in einer eigenen XML-Datei erstellen, wobei die layout_height auf 200 dp festgelegt ist, und dann in Ihrem Code, anstatt einen neuen ViewPager von Grund auf neu zu erstellen, diese XML-Datei aufblasen:

LayoutInflater inflater = context.getLayoutInflater();
inflater.inflate(R.layout.viewpagerxml, layout, true);
Jave
quelle
3
Gute Antwort, irgendwie ärgerlich, dass das Standardverhalten "etwas Unverständliches tun" ist. Danke für die Erklärung.
Chris Vandevelde
8
@ ChrisVandevelde Dies scheint ein häufiger Mandant einiger Android-Bibliotheken zu sein. Sobald Sie die Grundlagen lernen, stellen Sie fest, dass ihnen nichts folgt
CQM
1
Aber @Jave, warum kann der Viewpager seine Höhe nicht jedes Mal anpassen, wenn seine Kinder geladen werden?
Diffy
@CQM in der Tat! Die ViewPagerIndicator-Bibliothek hat das gleiche Problem mit layout_heightset to wrap_content, ist jedoch noch schlimmer, da die einfache Problemumgehung zum Einrichten auf einen festen Betrag nicht funktioniert.
Giulio Piancastelli
20

Mit der Antwort von Daniel López Localle habe ich diese Klasse in Kotlin erstellt. Hoffe es spart dir mehr Zeit

class DynamicHeightViewPager @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : ViewPager(context, attrs) {

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    var heightMeasureSpec = heightMeasureSpec

    var height = 0
    for (i in 0 until childCount) {
        val child = getChildAt(i)
        child.measure(widthMeasureSpec, View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
        val h = child.measuredHeight
        if (h > height) height = h
    }

    if (height != 0) {
        heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
    }

    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}}
Felipe Castilhos
quelle
16

Ich habe dieses Problem bereits in mehreren Projekten gesehen und hatte nie eine vollständige Lösung. Deshalb habe ich ein WrapContentViewPager-Github-Projekt als direkten Ersatz für ViewPager erstellt.

https://github.com/rnevet/WCViewPager

Die Lösung wurde von einigen der Antworten hier inspiriert, verbessert sich jedoch:

  • Ändert dynamisch die ViewPager-Höhe entsprechend der aktuellen Ansicht, auch während des Bildlaufs.
  • Berücksichtigt die Höhe von "Dekor" -Ansichten wie PagerTabStrip.
  • Berücksichtigt alle Polsterungen.

Aktualisiert für die Support-Bibliothek Version 24, die die vorherige Implementierung beschädigt hat.

Raanan
quelle
@mvai Kannst du ein Problem öffnen oder es teilen und die Beispiel-App ändern?
Raanan
1
Ich habe herausgefunden, dass RecyclerView auch einige Probleme mit wrap_content hat. Dies funktioniert, wenn Sie einen benutzerdefinierten LinearLayoutManager wie diesen verwenden . Also nichts falsch mit Ihrer Bibliothek.
Natario
1
Was noch behoben werden muss, ist die Verwendung mit FragmentStatePagerAdapter. Es sieht so aus, als würde es Kinder messen, bevor die Fragmente ausgelegt werden, wodurch eine geringere Höhe erreicht wird. Was für mich funktioniert hat, war die Antwort von @logan, obwohl ich noch daran arbeite. Möglicherweise möchten Sie versuchen, diesen Ansatz in Ihrer Bibliothek zusammenzuführen. Ich bin nicht mit Github vertraut, sorry.
Natario
Danke, ich werde mir das ansehen.
Raanan
1
Wenn Sie sich fragen, wie dies mit einem FragmentPagerAdapter funktioniert, lassen Sie Ihren Adapter das ObjectAtPositionInterface implementieren, indem Sie eine Liste der Fragmente intern führen, damit er das entsprechende Fragment von der getObjectAtPosition-Methode zurückgeben kann.
Pablo
15

Ich bin gerade auf das gleiche Problem gestoßen. Ich hatte einen ViewPager und wollte eine Anzeige auf der Schaltfläche anzeigen. Die Lösung, die ich gefunden habe, bestand darin, den Pager in eine RelativeView zu integrieren und sein layout_above auf die Ansichts-ID zu setzen, die ich darunter sehen möchte. das hat bei mir funktioniert.

Hier ist mein Layout XML:

  <RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:id="@+id/AdLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="vertical" >
    </LinearLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/mainpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/AdLayout" >
    </android.support.v4.view.ViewPager>
</RelativeLayout>
Idan
quelle
4
Nur als Referenz benötigen Sie keine xmlns: android = " schemas.android.com/apk/res/android " in beiden, nur in der ersten.
Martin Marconcini
2
Ihr Problem war überhaupt nicht dasselbe. Ihr Layout funktioniert einwandfrei, wenn ViewPager auf match_parent eingestellt ist. Das OP hatte eine Situation, in der der ViewPager in seinen Inhalt eingeschlossen werden sollte.
k2col
9

Ich bin auch auf dieses Problem gestoßen, aber in meinem Fall hatte ich ein Problem, FragmentPagerAdapterdas die ViewPagerSeiten lieferte . Das Problem, das ich hatte, war das onMeasure()des ViewPagerwurde vor einem der angerufenFragments erstellt wurde (und sich daher nicht richtig dimensionieren konnte).

Nach einigem Ausprobieren stellte ich fest, dass die finishUpdate()Methode des FragmentPagerAdapter aufgerufen wird, nachdem die Fragments(von instantiateItem()in FragmentPagerAdapter) und auch nach / während des Bildlaufs initialisiert wurden . Ich habe eine kleine Schnittstelle gemacht:

public interface AdapterFinishUpdateCallbacks
{
    void onFinishUpdate();
}

was ich in meine übergebe FragmentPagerAdapterund rufe:

@Override
public void finishUpdate(ViewGroup container)
{
    super.finishUpdate(container);

    if (this.listener != null)
    {
        this.listener.onFinishUpdate();
    }
}

was mir wiederum erlaubt, setVariableHeight()auf meine CustomViewPagerImplementierung zurückzugreifen:

public void setVariableHeight()
{
    // super.measure() calls finishUpdate() in adapter, so need this to stop infinite loop
    if (!this.isSettingHeight)
    {
        this.isSettingHeight = true;

        int maxChildHeight = 0;
        int widthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
        for (int i = 0; i < getChildCount(); i++)
        {
            View child = getChildAt(i);
            child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.UNSPECIFIED));
            maxChildHeight = child.getMeasuredHeight() > maxChildHeight ? child.getMeasuredHeight() : maxChildHeight;
        }

        int height = maxChildHeight + getPaddingTop() + getPaddingBottom();
        int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);

        super.measure(widthMeasureSpec, heightMeasureSpec);
        requestLayout();

        this.isSettingHeight = false;
    }
}

Ich bin mir nicht sicher, ob es der beste Ansatz ist. Ich würde mich über Kommentare freuen, wenn Sie denken, dass es gut / schlecht / böse ist, aber es scheint in meiner Implementierung ziemlich gut zu funktionieren :)

Hoffe das hilft jemandem da draußen!

BEARBEITEN: Ich habe vergessen, requestLayout()nach dem Aufruf ein hinzuzufügen super.measure()(andernfalls wird die Ansicht nicht neu gezeichnet).

Ich habe auch vergessen, die Polsterung der Eltern zur endgültigen Höhe hinzuzufügen.

Ich habe auch die ursprüngliche Breite / Höhe MeasureSpecs beibehalten, um bei Bedarf eine neue zu erstellen. Habe den Code entsprechend aktualisiert.

Ein weiteres Problem, das ich hatte, war, dass es sich in einem nicht richtig dimensionierte ScrollViewund feststellte, dass der Täter das Kind mit MeasureSpec.EXACTLYstatt maß MeasureSpec.UNSPECIFIED. Aktualisiert, um dies widerzuspiegeln.

Diese Änderungen wurden alle dem Code hinzugefügt. Sie können den Verlauf überprüfen, um die alten (falschen) Versionen anzuzeigen, wenn Sie möchten.

Logan
quelle
Warum fügen Sie die, die Sie vergessen haben, nicht zum Code hinzu?
Hasan
@hasan habe ich schon, sorry für jede Verwirrung! Wird die Antwort aktualisieren, um das auch zu sagen
Logan
Genial! Ich
bin
8

Eine andere Lösung besteht darin, die ViewPagerHöhe entsprechend der aktuellen Seitenhöhe zu aktualisieren PagerAdapter. Angenommen, Sie erstellen Ihre ViewPagerSeiten folgendermaßen:

@Override
public Object instantiateItem(ViewGroup container, int position) {
  PageInfo item = mPages.get(position);
  item.mImageView = new CustomImageView(container.getContext());
  item.mImageView.setImageDrawable(item.mDrawable);
  container.addView(item.mImageView, 0);
  return item;
}

Wo mPageswird eine interne Liste von PageInfoStrukturen dynamisch zum hinzugefügt PagerAdapterund CustomImageViewist nur regelmäßig ImageViewmit ÜberschreibungonMeasure() ist, bei der die Höhe entsprechend der angegebenen Breite festgelegt wird und das Bildseitenverhältnis beibehalten wird.

Sie können die ViewPagerHöhe in der setPrimaryItem()Methode erzwingen :

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
  super.setPrimaryItem(container, position, object);

  PageInfo item = (PageInfo) object;
  ViewPager pager = (ViewPager) container;
  int width = item.mImageView.getMeasuredWidth();
  int height = item.mImageView.getMeasuredHeight();
  pager.setLayoutParams(new FrameLayout.LayoutParams(width, Math.max(height, 1)));
}

Beachten Sie die Math.max(height, 1). Dies behebt einen lästigen Fehler, der ViewPagerdie angezeigte Seite nicht aktualisiert (sie wird leer angezeigt), wenn die vorherige Seite eine Höhe von Null hat (dh in der Null nicht zeichnbar ist CustomImageView), wobei jede ungerade zwischen zwei Seiten hin und her wischt .

Blackhex
quelle
Es scheint mir der richtige Weg zu sein, aber ich musste einen hinzufügen item.mImageView.measure(..), um die richtigen Dimensionen in den getMeasuredXXX()Methoden zu erhalten.
Gianluca P.
6

Wenn Sie statischen Inhalt im Viewpager verwenden und keine ausgefallene Animation wünschen, können Sie den folgenden View-Pager verwenden

public class HeightWrappingViewPager extends ViewPager {

  public HeightWrappingViewPager(Context context) {
    super(context);
  }

  public HeightWrappingViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)   {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      View firstChild = getChildAt(0);
      firstChild.measure(widthMeasureSpec, heightMeasureSpec);
      super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(firstChild.getMeasuredHeight(), MeasureSpec.EXACTLY));
  }
}
Kirill Kulakov
quelle
Das funktioniert gut. Ich erweiterte es, indem ich mich durch die Kinder schlang und das mit maximaler Größe nahm.
Javier Mendonça
Funktioniert auch unter Sicht des Recyclers
Kanudo
Ich erhalte diese Ausnahme - java.lang.NullPointerException: Versuch, die virtuelle Methode 'void android.view.View.measure (int, int)' für eine Nullobjektreferenz
aufzurufen
Aber das erste Element zu nehmen könnte das falsche sein.
Tobias Reich
4
public CustomPager (Context context) {
    super(context);
}

public CustomPager (Context context, AttributeSet attrs) {
    super(context, attrs);
}

int getMeasureExactly(View child, int widthMeasureSpec) {
    child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
    int height = child.getMeasuredHeight();
    return MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;

    final View tab = getChildAt(0);
    if (tab == null) {
        return;
    }

    int width = getMeasuredWidth();
    if (wrapHeight) {
        // Keep the current measured width.
        widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
    }
    Fragment fragment = ((Fragment) getAdapter().instantiateItem(this, getCurrentItem()));
    heightMeasureSpec = getMeasureExactly(fragment.getView(), widthMeasureSpec);

    //Log.i(Constants.TAG, "item :" + getCurrentItem() + "|height" + heightMeasureSpec);
    // super has to be called again so the new specs are treated as
    // exact measurements.
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
hasanul hakim
quelle
4

Aus dem Quellcode der Popcorn-Zeit-Android-App habe ich diese Lösung gefunden, die die Größe des Viewpagers mit einer schönen Animation dynamisch an die Größe des aktuellen Kindes anpasst.

https://git.popcorntime.io/popcorntime/android/blob/5934f8d0c8fed39af213af4512272d12d2efb6a6/mobile/src/main/java/pct/droid/widget/WrappingViewPager.java

public class WrappingViewPager extends ViewPager {

    private Boolean mAnimStarted = false;

    public WrappingViewPager(Context context) {
        super(context);
    }

    public WrappingViewPager(Context context, AttributeSet attrs){
        super(context, attrs);
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if(!mAnimStarted && null != getAdapter()) {
            int height = 0;
            View child = ((FragmentPagerAdapter) getAdapter()).getItem(getCurrentItem()).getView();
            if (child != null) {
                child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
                height = child.getMeasuredHeight();
                if (VersionUtils.isJellyBean() && height < getMinimumHeight()) {
                    height = getMinimumHeight();
                }
            }

            // Not the best place to put this animation, but it works pretty good.
            int newHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
            if (getLayoutParams().height != 0 && heightMeasureSpec != newHeight) {
                    final int targetHeight = height;
                    final int currentHeight = getLayoutParams().height;
                    final int heightChange = targetHeight - currentHeight;

                    Animation a = new Animation() {
                        @Override
                        protected void applyTransformation(float interpolatedTime, Transformation t) {
                            if (interpolatedTime >= 1) {
                                getLayoutParams().height = targetHeight;
                            } else {
                                int stepHeight = (int) (heightChange * interpolatedTime);
                                getLayoutParams().height = currentHeight + stepHeight;
                            }
                            requestLayout();
                        }

                        @Override
                        public boolean willChangeBounds() {
                            return true;
                        }
                    };

                    a.setAnimationListener(new Animation.AnimationListener() {
                        @Override
                        public void onAnimationStart(Animation animation) {
                            mAnimStarted = true;
                        }

                        @Override
                        public void onAnimationEnd(Animation animation) {
                            mAnimStarted = false;
                        }

                        @Override
                        public void onAnimationRepeat(Animation animation) {
                        }
                    });

                    a.setDuration(1000);
                    startAnimation(a);
                    mAnimStarted = true;
            } else {
                heightMeasureSpec = newHeight;
            }
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}
Vihaan Verma
quelle
4

Für den Fall, dass Sie ViewPager benötigen, der seine Größe an jedes Kind anpasst , nicht nur an das größte, habe ich einen Code geschrieben, der dies tut. Beachten Sie, dass bei dieser Änderung keine Animation vorhanden ist (in meinem Fall nicht erforderlich).

android: minHeight Flag wird ebenfalls unterstützt.

public class ChildWrappingAdjustableViewPager extends ViewPager {
    List<Integer> childHeights = new ArrayList<>(getChildCount());
    int minHeight = 0;
    int currentPos = 0;

    public ChildWrappingAdjustableViewPager(@NonNull Context context) {
        super(context);
        setOnPageChangeListener();
    }

    public ChildWrappingAdjustableViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        obtainMinHeightAttribute(context, attrs);
        setOnPageChangeListener();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {            
        childHeights.clear();

        //calculate child views
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
            int h = child.getMeasuredHeight();
            if (h < minHeight) {
                h = minHeight;
            }
            childHeights.add(i, h);
        }

        if (childHeights.size() - 1 >= currentPos) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeights.get(currentPos), MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private void obtainMinHeightAttribute(@NonNull Context context, @Nullable AttributeSet attrs) {
        int[] heightAttr = new int[]{android.R.attr.minHeight};
        TypedArray typedArray = context.obtainStyledAttributes(attrs, heightAttr);
        minHeight = typedArray.getDimensionPixelOffset(0, -666);
        typedArray.recycle();
    }

    private void setOnPageChangeListener() {
        this.addOnPageChangeListener(new SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                currentPos = position;

                ViewGroup.LayoutParams layoutParams = ChildWrappingAdjustableViewPager.this.getLayoutParams();
                layoutParams.height = childHeights.get(position);
                ChildWrappingAdjustableViewPager.this.setLayoutParams(layoutParams);
                ChildWrappingAdjustableViewPager.this.invalidate();
            }
        });
    }
}
Phatee P.
quelle
Dieser Adapter hat also ein großes Problem, wenn sich die Anzahl der Elemente im Adapter ändert
Jobbert
Können Sie Ihre Aussage klarstellen?
Phatee P
Dieser Code kann Nullzeiger verursachen, da nicht jedes Kind beim Start berechnet wird. Probieren Sie ein Registerkartenlayout aus und scrollen Sie von 1 bis 5 oder Code, und Sie werden es sehen.
Jobbert
4

Verbesserte Antwort von Daniel López Lacalle , umgeschrieben in Kotlin :

class MyViewPager(context: Context, attrs: AttributeSet): ViewPager(context, attrs) {
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val zeroHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)

        val maxHeight = children
            .map { it.measure(widthMeasureSpec, zeroHeight); it.measuredHeight }
            .max() ?: 0

        if (maxHeight > 0) {
            val maxHeightSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY)
            super.onMeasure(widthMeasureSpec, maxHeightSpec)
            return
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }
}
Wojciech Kulik
quelle
3

Ich bin auf dasselbe Problem gestoßen, und ich musste den ViewPager auch um seinen Inhalt wickeln lassen, wenn der Benutzer zwischen den Seiten blätterte. Unter Verwendung der obigen Antwort von cybergen habe ich die onMeasure-Methode wie folgt definiert:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    if (getCurrentItem() < getChildCount()) {
        View child = getChildAt(getCurrentItem());
        if (child.getVisibility() != GONE) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec),
                    MeasureSpec.UNSPECIFIED);
            child.measure(widthMeasureSpec, heightMeasureSpec);
        }

        setMeasuredDimension(getMeasuredWidth(), measureHeight(heightMeasureSpec, getChildAt(getCurrentItem())));            
    }
}

Auf diese Weise legt die onMeasure-Methode die Höhe der aktuellen Seite fest, die vom ViewPager angezeigt wird.

avlacatus
quelle
Mit Ihrer Antwort wird nur der Inhalt mit der höchsten Höhe angezeigt, der andere Inhalt ist verschwunden ...
Blaze Tama
2

Nichts von dem oben vorgeschlagenen hat für mich funktioniert. Mein Anwendungsfall besteht darin, 4 benutzerdefinierte ViewPager zu haben ScrollView. Die Oberseite von ihnen wird basierend auf dem Seitenverhältnis gemessen und der Rest hat gerade layout_height=wrap_content. Ich habe Cybergen , Daniel López Lacalle Lösungen ausprobiert . Keiner von ihnen arbeitet voll für mich.

Ich vermute, warum Cybergen auf Seite> 1 nicht funktioniert, weil es die Höhe des Pagers basierend auf Seite 1 berechnet, die ausgeblendet ist, wenn Sie weiter scrollen.

Sowohl Cybergen als auch Daniel López Lacalle haben in meinem Fall ein seltsames Verhalten: 2 von 3 sind in Ordnung geladen und 1 zufällige Größe ist 0. Erscheint, onMeasureder aufgerufen wurde, bevor Kinder bevölkert wurden. Also habe ich mir eine Mischung aus diesen 2 Antworten und meinen eigenen Korrekturen ausgedacht:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
        // find the first child view
        View view = getChildAt(0);
        if (view != null) {
            // measure the first child view with the specified measure spec
            view.measure(widthMeasureSpec, heightMeasureSpec);
            int h = view.getMeasuredHeight();
            setMeasuredDimension(getMeasuredWidth(), h);
            //do not recalculate height anymore
            getLayoutParams().height = h;
        }
    }
}

Die Idee ist, ViewPagerdie Abmessungen der Kinder berechnen zu lassen und die berechnete Höhe der ersten Seite in den Layoutparametern des zu speichern ViewPager. Vergessen Sie nicht, die Layouthöhe des Fragments auf einzustellen, wrap_contentda Sie sonst height = 0 erhalten können. Ich habe dieses verwendet:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="wrap_content">
        <!-- Childs are populated in fragment -->
</LinearLayout>

Bitte beachten Sie, dass diese Lösung hervorragend funktioniert, wenn alle Ihre Seiten dieselbe Höhe haben . Andernfalls müssen Sie die ViewPagerHöhe basierend auf dem aktuell aktiven Kind neu berechnen . Ich brauche es nicht, aber wenn Sie die Lösung vorschlagen, würde ich die Antwort gerne aktualisieren.

mente
quelle
Könnten Sie Ihre Antwort nach all den Jahren noch aktualisieren? Würde mir eine Tonne helfen
Denny
2

Für Benutzer mit diesem Problem und der Codierung für Xamarin Android in C # ist dies möglicherweise auch eine schnelle Lösung:

pager.ChildViewAdded += (sender, e) => {
    e.Child.Measure ((int)MeasureSpecMode.Unspecified, (int)MeasureSpecMode.Unspecified);
    e.Parent.LayoutParameters.Height = e.Child.MeasuredHeight;
};

Dies ist vor allem dann nützlich, wenn die Ansichten Ihres Kindes gleich hoch sind. Andernfalls müssten Sie für alle untergeordneten Elemente, gegen die Sie prüfen, eine Art "MinimumHeight" -Wert speichern, und selbst dann möchten Sie möglicherweise keine leeren Bereiche unter Ihren kleineren untergeordneten Ansichten sichtbar machen.

Die Lösung selbst reicht mir zwar nicht aus, aber das liegt daran, dass meine untergeordneten Elemente listViews sind und ihre MeasuredHeight anscheinend nicht korrekt berechnet wird.

kompatibel
quelle
Das hat bei mir funktioniert. Alle Ansichten meines Kindes im Viewpager sind gleich hoch.
Dmitry
2

Ich habe eine Version von WrapContentHeightViewPager, die vor API 23 ordnungsgemäß funktioniert hat und deren Größe die Höhenbasis der übergeordneten Ansicht auf der aktuell ausgewählten untergeordneten Ansicht ändert.

Nach dem Upgrade auf API 23 funktionierte es nicht mehr. Es stellt sich heraus, dass die alte Lösung verwendet wurde getChildAt(getCurrentItem()), um die aktuelle untergeordnete Ansicht zu messen, was nicht funktioniert. Die Lösung finden Sie hier: https://stackoverflow.com/a/16512217/1265583

Unten funktioniert mit API 23:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int height = 0;
    ViewPagerAdapter adapter = (ViewPagerAdapter)getAdapter();
    View child = adapter.getItem(getCurrentItem()).getView();
    if(child != null) {
        child.measure(widthMeasureSpec,  MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
        height = child.getMeasuredHeight();
    }
    heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
Howard Lin
quelle
Danke dir!! Ich habe stundenlang versucht, Antworten zu finden, und dies ist die einzige, die für mich voll funktioniert. Es muss mit einem benutzerdefinierten Adapter kombiniert werden, bei dem 'setPrimaryItem ()' eine Funktion im Pager aufruft, die aufruft, requestLayout()damit die Höhe angepasst wird, wenn wir von einer Registerkarte zur nächsten wechseln . Erinnerst du dich, warum superzweimal angerufen werden muss? Mir ist aufgefallen, dass es sonst nicht funktioniert.
M3RS
Funktioniert mit API 28.
Khalid Lakhani
2

Der folgende Code ist das einzige, was für mich funktioniert hat

1. Deklarieren Sie mit dieser Klasse einen HeightWrappingViewPager:

 public class HeightWrappingViewPager extends ViewPager {

        public HeightWrappingViewPager(Context context) {
            super(context);
        }

        public HeightWrappingViewPager(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int mode = MeasureSpec.getMode(heightMeasureSpec);
            // Unspecified means that the ViewPager is in a ScrollView WRAP_CONTENT.
            // At Most means that the ViewPager is not in a ScrollView WRAP_CONTENT.
            if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) {
                // super has to be called in the beginning so the child views can be initialized.
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                int height = 0;
                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
                    int h = child.getMeasuredHeight();
                    if (h > height) height = h;
                }
                heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
            }
            // super has to be called again so the new specs are treated as exact measurements
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

2. Fügen Sie den Pager für die Höhenumbruchansicht in Ihre XML-Datei ein:

<com.project.test.HeightWrappingViewPager
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</com.project.test.HeightWrappingViewPager>

3. Deklarieren Sie Ihren View Pager:

HeightWrappingViewPager mViewPager;
mViewPager = (HeightWrappingViewPager) itemView.findViewById(R.id.pager);
CustomAdapter adapter = new CustomAdapter(context);
mViewPager.setAdapter(adapter);
mViewPager.measure(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
Hossam Hassan
quelle
Vielen Dank. Das hat funktioniert. Aber warum kann Android Team dies nicht in seiner Codebasis haben?
Mohanakrrishna
Dies ist eines der Dinge, die Sie selbst anpassen müssen, abhängig von Ihren Anforderungen. Auch Google hat viewPager2 in diesem Jahr 2019 eingeführt. Google I / O ist ein Ersatz für den alten ViewPager, der 2011 mit der Implementierung 'androidx.viewpager2: viewpager2' erstellt wurde : 1.0.0-alpha04 '
Hossam Hassan
2

Ich bearbeite die Antwort von cybergen, damit der Viewpager die Höhe abhängig vom ausgewählten Element ändert. Die Klasse ist dieselbe wie die von cybergen, aber ich habe einen Vektor von Ganzzahlen hinzugefügt, der die Höhe aller untergeordneten Ansichten des Viewpagers darstellt, und wir können darauf zugreifen, wenn die Seite geändert wird, um die Höhe zu aktualisieren

Das ist die Klasse:

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.ViewPager;

import java.util.Vector;

public class WrapContentHeightViewPager extends ViewPager {
    private Vector<Integer> heights = new Vector<>();

    public WrapContentHeightViewPager(@NonNull Context context) {
        super(context);
    }

    public WrapContentHeightViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        for(int i=0;i<getChildCount();i++) {
            View view = getChildAt(i);
            if (view != null) {
                view.measure(widthMeasureSpec, heightMeasureSpec);
                heights.add(measureHeight(heightMeasureSpec, view));
            }
        }
        setMeasuredDimension(getMeasuredWidth(), measureHeight(heightMeasureSpec, getChildAt(0)));
    }

    public int getHeightAt(int position){
        return heights.get(position);
    }

    private int measureHeight(int measureSpec, View view) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            if (view != null) {
                result = view.getMeasuredHeight();
            }
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }
}

Fügen Sie dann in Ihrer Aktivität einen OnPageChangeListener hinzu

WrapContentHeightViewPager viewPager = findViewById(R.id.my_viewpager);
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
     @Override
     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
     @Override
     public void onPageSelected(int position) {
         LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) viewPager.getLayoutParams();
         params.height = viewPager.getHeightAt(position);
         viewPager.setLayoutParams(params);
     }
     @Override
     public void onPageScrollStateChanged(int state) {}
});

Und hier ist xml:

<com.example.example.WrapContentHeightViewPager
    android:id="@+id/my_viewpager"
    android:fillViewport="true"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

Bitte korrigieren Sie gegebenenfalls mein Englisch

geggiamarti
quelle
Dies hat einige Probleme. Die heightsListe kann die Unendlichkeit erhöhen.
Rosuh
@rosuh Wann sind Sie auf das Problem gestoßen? Ich habe dies nur in TabLayout mit ViewPager verwendet, daher bin ich mir nicht sicher, ob es überall gut funktioniert
geggiamarti
@geggiamarti Das Problem ist, dass einige Seiten recycelt werden. Und neu erstellt, wenn Benutzer zu ihnen wischen, measurewürde der Anruf daher mehrmals aufgerufen werden. Es kann die Höhenliste erhöhen. Eine andere Situation ist, dass der Benutzer diesen viewPager manuell aufruft requestLayout(oder eine setLayoutParamsMethode, genau wie Sie es getan haben), und auch mehrere Male misst.
Rosuh
1

Wenn das, das ViewPagerSie verwenden, ein Kind eines ScrollView UND ist und ein PagerTitleStripKind hat, müssen Sie eine geringfügige Modifikation der bereits bereitgestellten großartigen Antworten verwenden. Als Referenz sieht mein XML folgendermaßen aus:

<ScrollView
    android:id="@+id/match_scroll_view"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/white">

    <LinearLayout
        android:id="@+id/match_and_graphs_wrapper"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <view
            android:id="@+id/pager"
            class="com.printandpixel.lolhistory.util.WrapContentHeightViewPager"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <android.support.v4.view.PagerTitleStrip
                android:id="@+id/pager_title_strip"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="top"
                android:background="#33b5e5"
                android:paddingBottom="4dp"
                android:paddingTop="4dp"
                android:textColor="#fff" />
        </view>
    </LinearLayout>
</ScrollView>

In Ihrem müssen onMeasureSie die gemessene Höhe der hinzufügenPagerTitleStrip , wenn man gefunden wird. Andernfalls wird seine Größe nicht als die größte Größe aller Kinder angesehen, obwohl er zusätzlichen Platz beansprucht.

Hoffe das hilft jemand anderem. Tut mir leid, dass es ein bisschen ein Hack ist ...

public class WrapContentHeightViewPager extends ViewPager {

    public WrapContentHeightViewPager(Context context) {
        super(context);
    }

    public WrapContentHeightViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int pagerTitleStripHeight = 0;
        int height = 0;
        for(int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
            int h = child.getMeasuredHeight();
            if (h > height) {
                // get the measuredHeight of the tallest fragment
                height = h;
            }
            if (child.getClass() == PagerTitleStrip.class) {
                // store the measured height of the pagerTitleStrip if one is found. This will only
                // happen if you have a android.support.v4.view.PagerTitleStrip as a direct child
                // of this class in your XML.
                pagerTitleStripHeight = h;
            }
        }

        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height+pagerTitleStripHeight, MeasureSpec.EXACTLY);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}
alexgophermix
quelle
1

Die meisten Lösungen, die ich hier sehe, scheinen eine Doppelmessung durchzuführen: zuerst die untergeordneten Ansichten messen und dann die aufrufen super.onMeasure()

Ich habe mir einen Benutzer ausgedacht WrapContentViewPager, der effizienter ist und gut mit RecyclerView und Fragment funktioniert

Sie können die Demo hier überprüfen:

github / ssynhtn / WrapContentViewPager

und den Code der Klasse hier: WrapContentViewPager.java

ssynhtn
quelle
0

Ich habe ein ähnliches (aber komplexeres Szenario). Ich habe einen Dialog, der einen ViewPager enthält.
Eine der untergeordneten Seiten ist kurz und hat eine statische Höhe.
Eine andere untergeordnete Seite sollte immer so groß wie möglich sein.
Eine andere untergeordnete Seite enthält eine ScrollView, und die Seite (und damit der gesamte Dialog) sollte WRAP_CONTENT sein, wenn der ScrollView-Inhalt nicht die volle Höhe benötigt, die dem Dialog zur Verfügung steht.

Keine der vorhandenen Antworten funktionierte vollständig für dieses spezielle Szenario. Warte - es ist eine holprige Fahrt.

void setupView() {
    final ViewPager.SimpleOnPageChangeListener pageChangeListener = new ViewPager.SimpleOnPageChangeListener() {
        @Override
        public void onPageSelected(int position) {
            currentPagePosition = position;

            // Update the viewPager height for the current view

            /*
            Borrowed from https://github.com/rnevet/WCViewPager/blob/master/wcviewpager/src/main/java/nevet/me/wcviewpager/WrapContentViewPager.java
            Gather the height of the "decor" views, since this height isn't included
            when measuring each page's view height.
             */
            int decorHeight = 0;
            for (int i = 0; i < viewPager.getChildCount(); i++) {
                View child = viewPager.getChildAt(i);
                ViewPager.LayoutParams lp = (ViewPager.LayoutParams) child.getLayoutParams();
                if (lp != null && lp.isDecor) {
                    int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
                    boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
                    if (consumeVertical) {
                        decorHeight += child.getMeasuredHeight();
                    }
                }
            }

            int newHeight = decorHeight;

            switch (position) {
                case PAGE_WITH_SHORT_AND_STATIC_CONTENT:
                    newHeight += measureViewHeight(thePageView1);
                    break;
                case PAGE_TO_FILL_PARENT:
                    newHeight = ViewGroup.LayoutParams.MATCH_PARENT;
                    break;
                case PAGE_TO_WRAP_CONTENT:
//                  newHeight = ViewGroup.LayoutParams.WRAP_CONTENT; // Works same as MATCH_PARENT because...reasons...
//                  newHeight += measureViewHeight(thePageView2); // Doesn't allow scrolling when sideways and height is clipped

                    /*
                    Only option that allows the ScrollView content to scroll fully.
                    Just doing this might be way too tall, especially on tablets.
                    (Will shrink it down below)
                     */
                    newHeight = ViewGroup.LayoutParams.MATCH_PARENT;
                    break;
            }

            // Update the height
            ViewGroup.LayoutParams layoutParams = viewPager.getLayoutParams();
            layoutParams.height = newHeight;
            viewPager.setLayoutParams(layoutParams);

            if (position == PAGE_TO_WRAP_CONTENT) {
                // This page should wrap content

                // Measure height of the scrollview child
                View scrollViewChild = ...; // (generally this is a LinearLayout)
                int scrollViewChildHeight = scrollViewChild.getHeight(); // full height (even portion which can't be shown)
                // ^ doesn't need measureViewHeight() because... reasons...

                if (viewPager.getHeight() > scrollViewChildHeight) { // View pager too tall?
                    // Wrap view pager height down to child height
                    newHeight = scrollViewChildHeight + decorHeight;

                    ViewGroup.LayoutParams layoutParams2 = viewPager.getLayoutParams();
                    layoutParams2.height = newHeight;
                    viewPager.setLayoutParams(layoutParams2);
                }
            }

            // Bonus goodies :)
            // Show or hide the keyboard as appropriate. (Some pages have EditTexts, some don't)
            switch (position) {
                // This case takes a little bit more aggressive code than usual

                if (position needs keyboard shown){
                    showKeyboardForEditText();
                } else if {
                    hideKeyboard();
                }
            }
        }
    };

    viewPager.addOnPageChangeListener(pageChangeListener);

    viewPager.getViewTreeObserver().addOnGlobalLayoutListener(
            new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    // http://stackoverflow.com/a/4406090/4176104
                    // Do things which require the views to have their height populated here
                    pageChangeListener.onPageSelected(currentPagePosition); // fix the height of the first page

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                        viewPager.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    } else {
                        viewPager.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    }

                }
            }
    );
}


...

private void showKeyboardForEditText() {
    // Make the keyboard appear.
    getDialog().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
    getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);

    inputViewToFocus.requestFocus();

    // http://stackoverflow.com/a/5617130/4176104
    InputMethodManager inputMethodManager =
            (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
    inputMethodManager.toggleSoftInputFromWindow(
            inputViewToFocus.getApplicationWindowToken(),
            InputMethodManager.SHOW_IMPLICIT, 0);
}

...

/**
 * Hide the keyboard - http://stackoverflow.com/a/8785471
 */
private void hideKeyboard() {
    InputMethodManager inputManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);

    inputManager.hideSoftInputFromWindow(inputBibleBookStart.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}

...

//https://github.com/rnevet/WCViewPager/blob/master/wcviewpager/src/main/java/nevet/me/wcviewpager/WrapContentViewPager.java
private int measureViewHeight(View view) {
    view.measure(ViewGroup.getChildMeasureSpec(-1, -1, view.getLayoutParams().width), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
    return view.getMeasuredHeight();
}

Vielen Dank an @Raanan für den Code zum Messen von Ansichten und zum Messen der Dekorhöhe. Ich hatte Probleme mit seiner Bibliothek - die Animation stotterte und ich glaube, meine ScrollView würde nicht scrollen, wenn die Höhe des Dialogfelds kurz genug war, um es zu erfordern.

Patrick
quelle
0

In meinem Fall hat das Hinzufügen clipToPaddingdas Problem gelöst.

<android.support.v4.view.ViewPager
    ...
    android:clipToPadding="false"
    ...
    />

Prost!

Mario
quelle
0

Ich mein Fall Hinzufügen von Android: fillViewport = "true" löste das Problem

hiten pannu
quelle
0

In meinem Fall benötigte ich beim Anwenden der Größe einen Viewpager mit einem wrap_content für das aktuell ausgewählte Element und die Animation. Unten sehen Sie meine Implementierung. Kann jemand nützlich sein.

package one.xcorp.widget

import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import one.xcorp.widget.R
import kotlin.properties.Delegates.observable

class ViewPager : android.support.v4.view.ViewPager {

    var enableAnimation by observable(false) { _, _, enable ->
        if (enable) {
            addOnPageChangeListener(onPageChangeListener)
        } else {
            removeOnPageChangeListener(onPageChangeListener)
        }
    }

    private var animationDuration = 0L
    private var animator: ValueAnimator? = null

    constructor (context: Context) : super(context) {
        init(context, null)
    }

    constructor (context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init(context, attrs)
    }

    private fun init(context: Context, attrs: AttributeSet?) {
        context.theme.obtainStyledAttributes(
            attrs,
            R.styleable.ViewPager,
            0,
            0
        ).apply {
            try {
                enableAnimation = getBoolean(
                    R.styleable.ViewPager_enableAnimation,
                    enableAnimation
                )
                animationDuration = getInteger(
                    R.styleable.ViewPager_animationDuration,
                    resources.getInteger(android.R.integer.config_shortAnimTime)
                ).toLong()
            } finally {
                recycle()
            }
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)

        val measuredHeight = if (heightMode == MeasureSpec.EXACTLY) {
            MeasureSpec.getSize(heightMeasureSpec)
        } else {
            val currentViewHeight = findViewByPosition(currentItem)?.also {
                measureView(it)
            }?.measuredHeight ?: 0

            if (heightMode != MeasureSpec.AT_MOST) {
                currentViewHeight
            } else {
                Math.min(
                    currentViewHeight,
                    MeasureSpec.getSize(heightMeasureSpec)
                )
            }
        }

        super.onMeasure(
            widthMeasureSpec,
            MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)
        )
    }

    private fun measureView(view: View) = with(view) {
        val horizontalMode: Int
        val horizontalSize: Int
        when (layoutParams.width) {
            MATCH_PARENT -> {
                horizontalMode = MeasureSpec.EXACTLY
                horizontalSize = this@ViewPager.measuredWidth
            }
            WRAP_CONTENT -> {
                horizontalMode = MeasureSpec.UNSPECIFIED
                horizontalSize = 0
            }
            else -> {
                horizontalMode = MeasureSpec.EXACTLY
                horizontalSize = layoutParams.width
            }
        }

        val verticalMode: Int
        val verticalSize: Int
        when (layoutParams.height) {
            MATCH_PARENT -> {
                verticalMode = MeasureSpec.EXACTLY
                verticalSize = this@ViewPager.measuredHeight
            }
            WRAP_CONTENT -> {
                verticalMode = MeasureSpec.UNSPECIFIED
                verticalSize = 0
            }
            else -> {
                verticalMode = MeasureSpec.EXACTLY
                verticalSize = layoutParams.height
            }
        }

        val horizontalMeasureSpec = MeasureSpec.makeMeasureSpec(horizontalSize, horizontalMode)
        val verticalMeasureSpec = MeasureSpec.makeMeasureSpec(verticalSize, verticalMode)

        measure(horizontalMeasureSpec, verticalMeasureSpec)
    }

    private fun findViewByPosition(position: Int): View? {
        for (i in 0 until childCount) {
            val childView = getChildAt(i)
            val childLayoutParams = childView.layoutParams as LayoutParams

            val childPosition by lazy {
                val field = childLayoutParams.javaClass.getDeclaredField("position")
                field.isAccessible = true
                field.get(childLayoutParams) as Int
            }

            if (!childLayoutParams.isDecor && position == childPosition) {
                return childView
            }
        }

        return null
    }

    private fun animateContentHeight(childView: View, fromHeight: Int, toHeight: Int) {
        animator?.cancel()

        if (fromHeight == toHeight) {
            return
        }

        animator = ValueAnimator.ofInt(fromHeight, toHeight).apply {
            addUpdateListener {
                measureView(childView)
                if (childView.measuredHeight != toHeight) {
                    animateContentHeight(childView, height, childView.measuredHeight)
                } else {
                    layoutParams.height = animatedValue as Int
                    requestLayout()
                }
            }
            duration = animationDuration
            start()
        }
    }

    private val onPageChangeListener = object : OnPageChangeListener {

        override fun onPageScrollStateChanged(state: Int) {
            /* do nothing */
        }

        override fun onPageScrolled(
            position: Int,
            positionOffset: Float,
            positionOffsetPixels: Int
        ) {
            /* do nothing */
        }

        override fun onPageSelected(position: Int) {
            if (!isAttachedToWindow) {
                return
            }

            findViewByPosition(position)?.let { childView ->
                measureView(childView)
                animateContentHeight(childView, height, childView.measuredHeight)
            }
        }
    }
}

Fügen Sie attrs.xml im Projekt hinzu:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ViewPager">
        <attr name="enableAnimation" format="boolean" />
        <attr name="animationDuration" format="integer" />
    </declare-styleable>
</resources>

Und benutze:

<one.xcorp.widget.ViewPager
    android:id="@+id/wt_content"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:enableAnimation="true" />
maXp
quelle
0

Dieser ViewPager ändert nur die Größe der aktuell sichtbaren Kinder (nicht das größte seiner tatsächlichen Kinder).

Die Idee von https://stackoverflow.com/a/56325869/4718406

public class DynamicHeightViewPager extends ViewPager {

public DynamicHeightViewPager (Context context) {
    super(context);
    initPageChangeListener();
}

public DynamicHeightViewPager (Context context, AttributeSet attrs) {
    super(context, attrs);
    initPageChangeListener();
}



private void initPageChangeListener() {
    addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
        @Override
        public void onPageSelected(int position) {
            requestLayout();
        }
    });
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //View child = getChildAt(getCurrentItem());
    View child = getCurrentView(this);
    if (child != null) {
        child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, 
         MeasureSpec.UNSPECIFIED));
        int h = child.getMeasuredHeight();

        heightMeasureSpec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}


View getCurrentView(ViewPager viewPager) {
    try {
        final int currentItem = viewPager.getCurrentItem();
        for (int i = 0; i < viewPager.getChildCount(); i++) {
            final View child = viewPager.getChildAt(i);
            final ViewPager.LayoutParams layoutParams = (ViewPager.LayoutParams) 
             child.getLayoutParams();

            Field f = layoutParams.getClass().getDeclaredField("position"); 
            //NoSuchFieldException
            f.setAccessible(true);
            int position = (Integer) f.get(layoutParams); //IllegalAccessException

            if (!layoutParams.isDecor && currentItem == position) {
                return child;
            }
        }
    } catch (NoSuchFieldException e) {
        e.fillInStackTrace();
    } catch (IllegalArgumentException e) {
        e.fillInStackTrace();
    } catch (IllegalAccessException e) {
        e.fillInStackTrace();
    }
    return null;
}

}}

Erfan Eghterafi
quelle
0

Messen Sie die Höhe von ViewPager:

public class WrapViewPager extends ViewPager {
    View primaryView;

    public WrapViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (primaryView != null) {
            int height = 0;
            for (int i = 0; i < getChildCount(); i++) {
                if (primaryView == getChildAt(i)) {
                    int childHeightSpec = MeasureSpec.makeMeasureSpec(0x1 << 30 - 1, MeasureSpec.AT_MOST);
                    getChildAt(i).measure(widthMeasureSpec, childHeightSpec);
                    height = getChildAt(i).getMeasuredHeight();
                }

            }

            setMeasuredDimension(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
        }
    }

    public void setPrimaryView(View view) {
        primaryView = view;
    }

}

Rufen Sie setPrimaryView (View) auf:

public class ZGAdapter extends PagerAdapter {

    @Override
    public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        super.setPrimaryItem(container, position, object);
        ((WrapViewPager)container).setPrimaryView((View)object);
    }

}
wslaimin
quelle
0

Geben Sie das übergeordnete Layout von ViewPager als an NestedScrollView

   <androidx.core.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="5dp"
    android:paddingRight="5dp"
    android:fillViewport="true">
        <androidx.viewpager.widget.ViewPager
            android:id="@+id/viewPager"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
        </androidx.viewpager.widget.ViewPager>
    </androidx.core.widget.NestedScrollView>

Vergiss nicht zu setzen android:fillViewport="true"

Dadurch werden die Bildlaufansicht und der Inhalt des untergeordneten Elements erweitert, um das Ansichtsfenster zu füllen.

https://developer.android.com/reference/android/widget/ScrollView.html#attr_android:fillViewport

Krishna
quelle
0

Sie können zu ViewPager2 wechseln. Es ist eine aktualisierte Version von ViewPager. Es funktioniert genauso wie ViewPager, jedoch intelligenter und effizienter. ViewPager2 bietet eine Vielzahl neuer Funktionen. Natürlich wurde das Problem mit dem Wrap-Inhalt von ViewPager2 behoben.

Aus Android-Dokumenten: "ViewPager2 ersetzt ViewPager und behebt die meisten Probleme des Vorgängers, einschließlich Unterstützung des Layouts von rechts nach links, vertikale Ausrichtung, modifizierbare Fragmentsammlungen usw."

Ich empfehle diesen Artikel für Anfänger:

https://medium.com/google-developer-experts/exploring-the-view-pager-2-86dbce06ff71

seyfullah.bilgin
quelle
Dieses Problem ist immer noch vorhanden. Check out issuetracker.google.com/u/0/issues/143095219
Somesh Kumar