Android: non riesco ad avere ViewPager WRAP_CONTENT


258

Ho installato un semplice ViewPager che ha un ImageView con un'altezza di 200 dpi su ogni pagina.

Ecco il mio cercapersone:

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

Nonostante l'altezza impostata su wrap_content, il cercapersone riempie sempre lo schermo anche se l'immagine visualizzata è solo di 200 dpi. Ho provato a sostituire l'altezza del cercapersone con "200" ma ciò mi dà risultati diversi con risoluzioni multiple. Non riesco ad aggiungere "dp" a quel valore. Come faccio ad aggiungere 200dp al layout del cercapersone?


Risposte:


408

Sovrascrivere su La misura ViewPagercome segue farà in modo che raggiunga l'altezza del figlio maggiore che ha attualmente.

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

24
Questo si avvicina di più a quello di cui ho bisogno, ma ci sono due cose da aggiungere: 1. ViewPager si ridimensiona solo al più grande dei suoi figli reali, cioè solo l'elemento attualmente visibile e quelli direttamente adiacenti. Chiamare setOffscreenPageLimit (numero totale di figli) sul ViewPager risolve questo problema e si ottiene un ViewPager la cui dimensione è impostata sul più grande di tutti i suoi elementi e non viene mai ridimensionata. 2. Le visualizzazioni Web presentano strani problemi durante il tentativo di misurarle. Chiamare requestLayout () su un WebView dopo aver caricato qualcosa lo risolve.
0101100101,

3
C'è solo un piccolo problema che sto per risolvere: se viewPager ha la sua visibilità su GONE e lo imposti come visibile, onMeasure viene chiamato prima che il suo frammento venga creato. Quindi finirà per avere un'altezza di 0. Se qualcuno ha un'idea, è il benvenuto. Penso che andrò con un callback per quando il frammento viene creato
edoardotognoni,

4
Questo non funzionerà se si hanno viste figlio decorative - ciò è dovuto al fatto che ViewPager.onMeasure () misura le viste decorative e alloca prima lo spazio a esse, quindi fornisce il resto dello spazio ai bambini non decorati. Tuttavia, questa è di gran lunga la soluzione meno errata qui, quindi ho votato;)
Benjamin Dobell,

3
Continuo a tornare a questo ogni volta che uso un ViewPager
ono

7
getChildCount () potrebbe restituire 0 mentre hai già eseguito setAdapter () su ViewPager! La chiamata populate () effettiva (che crea le viste) avviene all'interno di super.onMeasure (widthMeasureSpec, heightMeasureSpec); chiamata. Mettere la chiamata extra super.onMeasure () all'inizio di questa funzione ha funzionato. Controllare anche stackoverflow.com/questions/38492210/...
Southerton

106

Un'altra soluzione più generica è mettersi wrap_contental lavoro.

Ho esteso ViewPagerper eseguire l'override onMeasure(). L'altezza è racchiusa nella vista del primo figlio. Ciò potrebbe portare a risultati imprevisti se le viste figlio non hanno esattamente la stessa altezza. Per questo la classe può essere facilmente estesa per così dire animata alla dimensione della vista / pagina corrente. Ma non ne avevo bisogno.

Puoi utilizzare questo ViewPager nei tuoi layout XML proprio come il ViewPager originale:

<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"/>

Vantaggio: questo approccio consente di utilizzare ViewPager in qualsiasi layout, incluso RelativeLayout, per sovrapporre altri elementi dell'interfaccia utente.

Rimane uno svantaggio: se si desidera utilizzare i margini, è necessario creare due layout nidificati e assegnare a quello interno i margini desiderati.

Ecco il codice:

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

}

34
qualcun altro ha una pagina vuota accanto all'elemento corrente quando il viewpager viene distrutto e riaperto?
Zyoo,

1
Ho anche pagine bianche.
aeren,

10
Hai solo bisogno di unire due risposte principali a questa domanda come descritto nel mio blog: pristalovpavel.wordpress.com/2014/12/26/…
anil

4
Basta sostituire il codice del metodo "onMeasure" con la risposta data da "Daniel López Lacalle".
Yog Guru,

1
Grande..! Ha funzionato per me .. @cybergen Grazie mille mi hai salvato la giornata ..!
Dnyanesh M,

59

Ho basato la mia risposta su Daniel López Lacalle e su questo post http://www.henning.ms/2013/09/09/viewpager-that-simply-dont-measure-up/ . Il problema con la risposta di Daniel è che in alcuni casi i miei figli avevano un'altezza zero. Purtroppo la soluzione era misurare due volte.

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

Questo ti consente anche di impostare un'altezza sul ViewPager se lo desideri o semplicemente wrap_content.


Ho avuto lo stesso problema e l'ho risolto con la tua risposta, grazie. Ma qualche spiegazione sul perché?
Bart Burg,

Penso che non intendessero supportare il contenuto a capo automatico poiché non penso che pensassero che fosse un normale caso d'uso. Per supportarlo dobbiamo misurare nuovamente noi stessi dopo che i nostri figli sono stati misurati in modo da poter avvolgere il contenuto.
MinceMan,

Perché le immagini in questo ViewPager sono in realtà più brevi di quelle in un ImageView che usa lo stesso scaleTypee allo stesso modo, layout_width=match_parentcosì come layout_height=wrap_content? ci sono 20dp mancanti lì.
Squalo,

Squalo, non ne sono davvero sicuro. Ciò potrebbe avere qualcosa a che fare con ciò che il tuo tipo di scala sta effettivamente facendo. Potrebbe voler provare a impostare un'altezza.
MinceMan,

1
NON POSSO FRIKIN CREDARLO! Ho trascorso 2 giorni a incollare insieme il mio viewpager personalizzato e mi sono bloccato su un problema, quando la mia vista iniziale non veniva visualizzata e non riuscivo a capire perché! // super has to be called in the beginning so the child views can be initialized.<----- QUELLO era il motivo, doveva chiamarlo all'inizio e alla fine della funzione onMeasure. Yippiii, alti cinque virtuali su di me oggi!
Starwave,

37

Stavo solo rispondendo a una domanda molto simile su questo, e mi è capitato di trovarlo quando cercavo un link per il backup delle mie affermazioni, così fortunato tu :)


Un'altra mia risposta: ViewPager non supporta wrap_contentpoiché (di solito) non ha mai caricato tutti i suoi figli contemporaneamente, e quindi non può ottenere una dimensione appropriata (l'opzione sarebbe quella di avere un cercapersone che cambi dimensione ogni volta che si cambia pagina).

Puoi comunque impostare una dimensione precisa (ad es. 150dp) e match_parentfunziona anche.
Puoi anche modificare le dimensioni in modo dinamico dal tuo codice cambiando l' heightattributo al suo interno LayoutParams.

Per le tue esigenze puoi creare ViewPager nel suo file xml, con layout_height impostato su 200dp, e quindi nel tuo codice, anziché creare un nuovo ViewPager da zero, puoi gonfiare quel file xml:

LayoutInflater inflater = context.getLayoutInflater();
inflater.inflate(R.layout.viewpagerxml, layout, true);

3
Buona risposta, un po 'fastidioso che il comportamento predefinito sia "fare qualcosa di incomprensibile". Grazie per la spiegazione.
Chris Vandevelde,

8
@ChrisVandevelde questo sembra essere un inquilino comune di alcune librerie Android. Non appena apprendi i fondamenti, ti rendi conto che nulla li segue
CQM,

1
Ma @Jave, perché il viewpager non può regolare la sua altezza ogni volta che vengono caricati i suoi figli?
Diffy

@CQM davvero! La libreria ViewPagerIndicator presenta lo stesso problema con layout_heightset to wrap_content, ma è anche peggio in quanto la semplice soluzione alternativa per impostarlo su un importo fisso non funziona.
Giulio Piancastelli,

20

Usando la risposta di Daniel López Localle , ho creato questa classe a Kotlin. Spero che ti faccia risparmiare più tempo

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

16

Ho già affrontato questo problema in diversi progetti e non ho mai avuto una soluzione completa. Così ho creato un progetto github di WrapContentViewPager come sostituto sul posto per ViewPager.

https://github.com/rnevet/WCViewPager

La soluzione è stata ispirata da alcune delle risposte qui, ma migliora:

  • Modifica dinamicamente l'altezza di ViewPager in base alla vista corrente, anche durante lo scorrimento.
  • Prende in considerazione l'altezza delle viste "decorative" come PagerTabStrip.
  • Prende in considerazione tutta l'imbottitura.

Aggiornato per la libreria di supporto versione 24 che ha interrotto l'implementazione precedente.


@mvai puoi aprire un problema o biforcarlo e modificare l'app di esempio?
Raanan,

1
Ho scoperto che anche RecyclerView presenta alcuni problemi con wrap_content; funziona se si utilizza un LinearLayoutManager personalizzato, come questo . Quindi niente di sbagliato nella tua biblioteca.
natario,

1
Ciò che deve ancora essere risolto è il suo utilizzo con FragmentStatePagerAdapter. Sembra che stia misurando i bambini prima che i frammenti siano disposti, dando così un'altezza più piccola. Ciò che ha funzionato per me è stata la risposta di @logan, anche se ci sto ancora lavorando. Potresti provare a fondere questo approccio nella tua libreria. Non ho familiarità con Github, scusa.
natario,

Grazie, ci darò un'occhiata.
Raanan,

1
Per chiunque si stia chiedendo come farlo funzionare con un FragmentPagerAdapter, fai in modo che il tuo Adapter implementi ObjectAtPositionInterface mantenendo un elenco di frammenti internamente, in modo che possa restituire il frammento corrispondente dal metodo getObjectAtPosition.
Pablo

15

Ho appena incontrato lo stesso problema. Avevo un ViewPager e volevo mostrare un annuncio al suo pulsante. La soluzione che ho trovato è stata quella di inserire il cercapersone in un RelativeView e impostarlo layout_above sull'ID di visualizzazione che voglio vedere sotto di esso. quello ha funzionato per me.

ecco il mio 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>

4
solo per riferimento, non hai bisogno di xmlns: android = " schemas.android.com/apk/res/android " in entrambi, solo nel primo.
Martin Marconcini,

2
Il tuo problema non era affatto lo stesso. Il layout funziona bene con ViewPager impostato su match_parent: l'OP ha avuto una situazione in cui voleva che ViewPager si adattasse al suo contenuto.
k2col,

9

Ho anche riscontrato questo problema, ma nel mio caso ho avuto un FragmentPagerAdapterche stava fornendo le ViewPagersue pagine. Il problema che avevo era quello onMeasure()di essere ViewPagerstato chiamato prima che uno qualsiasi Fragmentsfosse stato creato (e quindi non poteva dimensionarsi correttamente).

Dopo un po 'di tentativi ed errori, ho scoperto che il finishUpdate()metodo di FragmentPagerAdapter viene chiamato dopo che Fragmentssono stati inizializzati (da instantiateItem()in FragmentPagerAdapter) e anche dopo / durante lo scorrimento della pagina. Ho creato una piccola interfaccia:

public interface AdapterFinishUpdateCallbacks
{
    void onFinishUpdate();
}

che passo nel mio FragmentPagerAdaptere chiamo:

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

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

che a sua volta mi permette di chiedere la setVariableHeight()mia CustomViewPagerimplementazione:

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

Non sono sicuro che sia l'approccio migliore, amerei i commenti se pensi che sia buono / cattivo / cattivo, ma sembra funzionare abbastanza bene nella mia implementazione :)

Spero che questo aiuti qualcuno là fuori!

EDIT: ho dimenticato di aggiungere un requestLayout()dopo aver chiamato super.measure()(altrimenti non ridisegna la vista).

Ho anche dimenticato di aggiungere l'imbottitura del genitore all'altezza finale.

Ho anche lasciato cadere il MeasureSpecs originale larghezza / altezza a favore della creazione di uno nuovo come richiesto. Ho aggiornato il codice di conseguenza.

Un altro problema che ho avuto è stato che non si sarebbe adattato correttamente in a ScrollViewe ho scoperto che il colpevole stava misurando il bambino con MeasureSpec.EXACTLYinvece di MeasureSpec.UNSPECIFIED. Aggiornato per riflettere questo.

Queste modifiche sono state tutte aggiunte al codice. Puoi controllare la cronologia per vedere le versioni precedenti (errate) se lo desideri.


Perché non aggiungi quelli che hai dimenticato nel codice, per favore.
Hasan,

@hasan l'ho già fatto, scusa per la confusione! Aggiornerò la risposta per dire anche questo
logan,

Eccezionale! Sono contento che abbia aiutato :)
Logan,

8

Un'altra soluzione è aggiornare l' ViewPageraltezza in base all'altezza della pagina corrente nella sua PagerAdapter. Supponendo che tu stia creando le tue ViewPagerpagine in questo modo:

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

Dove si mPagestrova un elenco interno di PageInfostrutture aggiunto dinamicamente a PagerAdaptered CustomImageViewè semplicemente regolare ImageViewcon il onMeasure()metodo overriden che imposta la sua altezza in base alla larghezza specificata e mantiene le proporzioni dell'immagine.

È possibile forzare l' ViewPageraltezza nel setPrimaryItem()metodo:

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

Nota il Math.max(height, 1). Ciò risolve un fastidioso bug che ViewPagernon aggiorna la pagina visualizzata (la mostra in bianco), quando la pagina precedente ha un'altezza zero (cioè null trascinabile nella CustomImageView), ogni strana strisciare avanti e indietro tra due pagine.


mi sembra la strada giusta da seguire ma avevo bisogno di inserire un annuncio item.mImageView.measure(..)per ottenere le dimensioni corrette nei getMeasuredXXX()metodi.
Gianluca P.

6

Quando si utilizza contenuto statico all'interno del viewpager e non si desidera alcuna animazione di fantasia, è possibile utilizzare il seguente cercapersone di visualizzazione

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

Funziona benissimo. L'ho esteso facendo un giro tra i bambini e prendendo quello con altezza massima.
Javier Mendonça,

Funziona bene anche sotto la vista del riciclatore
kanudo

Ricevo questa eccezione- java.lang.NullPointerException: tentativo di invocare il metodo virtuale "void android.view.View.measure (int, int)" su un riferimento a oggetto null
PJ2104

Ma prendere il primo elemento potrebbe essere quello sbagliato.
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);
}

4

Dal codice sorgente di Android app Popcorn ho trovato questa soluzione che regola dinamicamente le dimensioni del viewpager con una bella animazione a seconda delle dimensioni del bambino corrente.

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

4

Nel caso abbiate bisogno di ViewPager che ne adegui le dimensioni per ogni bambino , non solo a quello più grande, ho scritto un pezzo di codice che lo fa. Nota che non c'è animazione su quel cambiamento (non necessario nel mio caso)

android: è supportato anche il flag minHeight .

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

Quindi questo adattatore ha un grosso problema quando cambia un sacco di elementi nell'adattatore
jobbert

puoi chiarire la tua affermazione?
Phatee P,

Questo codice può causare punti null poiché non tutti i bambini vengono calcolati all'avvio. Prova un layout a schede e scorri da 1 a 5 o saggiamente il codice e lo vedrai.
Jobbert,

4

Migliorata la risposta di Daniel López Lacalle , riscritta 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)
    }
}

3

Ho riscontrato lo stesso problema e ho anche dovuto fare in modo che ViewPager avvolgesse il suo contenuto quando l'utente scorreva tra le pagine. Utilizzando la risposta di cybergen sopra, ho definito il metodo onMeasure come segue:

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

In questo modo, il metodo onMeasure imposta l'altezza della pagina corrente visualizzata da ViewPager.


Viene visualizzato solo il contenuto più in altezza con la tua risposta, l'altro contenuto scompare ...
Blaze Tama

2

Nulla di quanto suggerito sopra ha funzionato per me. Nel mio caso d'uso sono presenti 4 ViewPager personalizzati in ScrollView. La parte superiore è misurata in base alle proporzioni e il resto ha appena layout_height=wrap_content. Ho provato Cybergen , Daniel López Lacalle soluzioni . Nessuno di loro lavora pienamente per me.

Immagino perché cybergen non funzioni nella pagina> 1 sia perché calcola l'altezza del cercapersone in base alla pagina 1, che è nascosto se si scorre ulteriormente.

Entrambi i suggerimenti di cybergen e Daniel López Lacalle hanno un comportamento strano nel mio caso: 2 su 3 sono caricati correttamente e 1 altezza a caso è 0. Appare che è onMeasurestato chiamato prima che i bambini fossero popolati. Quindi ho pensato a una combinazione di queste 2 risposte + le mie correzioni:

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

L'idea è quella di lasciare ViewPagercalcolare le dimensioni dei bambini e salvare l'altezza calcolata della prima pagina nei parametri di layout di ViewPager. Non dimenticare di impostare l'altezza del layout del frammento su wrap_contentaltrimenti puoi ottenere height = 0. Ho usato questo:

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

Nota che questa soluzione funziona alla grande se tutte le tue pagine hanno la stessa altezza . In caso contrario, è necessario ricalcolare l' ViewPageraltezza in base all'attuale figlio attivo. Non ne ho bisogno, ma se suggerisci la soluzione sarei felice di aggiornare la risposta.


Potresti ancora aggiornare la tua risposta dopo tutti quegli anni? Mi aiuterebbe molto
Denny,

2

Per le persone che hanno questo problema e la codifica per Xamarin Android in C #, questa potrebbe anche essere una soluzione rapida:

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

Ciò è utile soprattutto se le viste del bambino sono della stessa altezza. Altrimenti, ti verrà richiesto di memorizzare una sorta di valore "minimoAltezza" su tutti i bambini che controlli, e anche in questo caso potresti non voler avere spazi vuoti visibili sotto le viste dei bambini più piccoli.

La soluzione in sé non è sufficiente per me, ma è perché i miei elementi figlio sono listViews e il loro MeasuredHeight non è calcolato correttamente, a quanto pare.


Questo ha funzionato per me. Tutte le viste di mio figlio nel viewpager hanno la stessa altezza.
Dmitry il

2

Ho una versione di WrapContentHeightViewPager che funzionava correttamente prima dell'API 23 che ridimensionava l'altezza della vista padre sulla vista figlio corrente selezionata.

Dopo l'aggiornamento a API 23, ha smesso di funzionare. Si scopre che la vecchia soluzione stava usando getChildAt(getCurrentItem())per ottenere l'attuale vista figlio per misurare che non funziona. Vedi la soluzione qui: https://stackoverflow.com/a/16512217/1265583

Di seguito funziona con 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);
}

Grazie!! Ho provato a rispondere per ore e questo è l'unico che funziona pienamente per me. Deve essere combinato con un adattatore personalizzato in cui 'setPrimaryItem () `chiama una funzione nel cercapersone che chiama in requestLayout()modo che l'altezza venga regolata mentre passiamo da una scheda all'altra. Ricordi perché superdeve essere chiamato due volte? Ho notato che altrimenti non funzionerà.
M3RS

Funziona con API 28.
Khalid Lakhani

2

Il codice qui sotto è l'unica cosa che ha funzionato per me

1. Utilizzare questa classe per dichiarare 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. Inserisci il pager della vista di spostamento altezza nel tuo file xml:

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

3. Dichiara il tuo cercapersone di visualizzazione:

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

Grazie. Questo ha funzionato. Ma perché il team Android non può avere questo nella loro base di codice?
Mohanakrrishna,

Questa è una delle cose che devi personalizzare a seconda delle tue necessità, anche Google ha introdotto viewPager2 nell'anno 2019 Google I / O ed è una sostituzione del vecchio ViewPager, creato nel 2011, implementazione 'androidx.viewpager2: viewpager2 : 1.0.0-alpha04 '
Hossam Hassan,

2

Modifica la risposta di cybergen per fare in modo che viewpager cambi altezza a seconda dell'elemento selezionato La classe è la stessa di cybergen, ma ho aggiunto un vettore di numeri interi che è figlio di viewpager per tutte le altezze e possiamo accedervi quando si cambia pagina per aggiornare l'altezza

Questa è la classe:

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

Quindi nella tua attività aggiungi un OnPageChangeListener

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

Ed ecco xml:

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

Per favore, correggi il mio inglese se necessario


Questo ha alcuni problemi. L' heightselenco può aumentare l'infinito.
Rosuh,

@rosuh Quando hai riscontrato il problema? L'ho usato solo in TabLayout con ViewPager, quindi non sono sicuro che funzioni bene ovunque
geggiamarti,

@geggiamarti Il problema è che alcune pagine verrebbero riciclate. E ricreato quando l'utente passa il dito su di loro, quindi measureverrà chiamato più volte. Potrebbe aumentare l'elenco delle altezze. Un'altra situazione è che l'utente può chiamare requestLayout(o setLayoutParamsmetodo, proprio come quello che hai fatto) per questo viewPager manualmente, inoltre misurerà più volte.
Rosuh,

1

Se ViewPagerstai usando un figlio di un ScrollView AND ha un PagerTitleStripfiglio dovrai usare una leggera modifica delle grandi risposte già fornite. Per riferimento il mio XML si presenta così:

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

Nel tuo onMeasuredevi aggiungere il misurato altezza delPagerTitleStrip se uno viene trovato. In caso contrario, la sua altezza non sarà considerata l'altezza più grande di tutti i bambini, anche se occupa spazio aggiuntivo.

Spero che questo aiuti qualcun altro. Mi dispiace che sia un po 'un trucco ...

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

1

La maggior parte delle soluzioni che vedo qui sembrano fare una doppia misurazione: prima misurando le viste figlio, e poi chiamando il super.onMeasure()

Ho escogitato un'usanza WrapContentViewPagerpiù efficiente, che funziona bene con RecyclerView e Fragment

Puoi controllare la demo qui:

github / ssynhtn / WrapContentViewPager

e il codice della classe qui: WrapContentViewPager.java


0

Ho uno scenario simile (ma più complesso). Ho una finestra di dialogo che contiene un ViewPager.
Una delle pagine secondarie è breve, con un'altezza statica.
Un'altra pagina figlio dovrebbe essere sempre più alta possibile.
Un'altra pagina figlio contiene uno ScrollView e la pagina (e quindi l'intera finestra di dialogo) dovrebbe WRAP_CONTENT se i contenuti di ScrollView non necessitano dell'intera altezza disponibile per la finestra di dialogo.

Nessuna delle risposte esistenti ha funzionato completamente per questo specifico scenario. Aspetta, è un giro accidentato.

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

Mille grazie a @Raanan per il codice per misurare le viste e misurare l'altezza della decorazione. Ho riscontrato dei problemi con la sua libreria: l'animazione balbettava e penso che il mio ScrollView non scorresse quando l'altezza della finestra di dialogo era abbastanza breve da richiederla.


0

nel mio caso l'aggiunta ha clipToPaddingrisolto il problema.

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

Saluti!


0

Nel mio caso l'aggiunta di Android: fillViewport = "true" ha risolto il problema


0

Nel mio caso, avevo bisogno di un viewpager con un wrap_content per l'elemento e l'animazione attualmente selezionati quando si applicavano le dimensioni. Di seguito puoi vedere la mia implementazione. Qualcuno può tornare utile.

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

Aggiungi attrs.xml nel progetto:

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

E usa:

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

0

Questo ViewPager si ridimensiona solo agli attuali figli visibili (non il più grande dei suoi figli effettivi)

L'idea da 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;
}

}


0

Misura l'altezza di 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;
    }

}

call setPrimaryView (Visualizza) :

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

}

0

Fornisci layout padre di ViewPager come 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>

Non dimenticare di impostare android:fillViewport="true"

Ciò allungherà la vista di scorrimento e il contenuto di suo figlio per riempire la finestra.

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


0

È possibile passare a ViewPager2. È una versione aggiornata di ViewPager. Fa la stessa cosa di ViewPager ma in modo più intelligente ed efficiente. ViewPager2 include una varietà di nuove funzionalità. Ovviamente il problema Wrap Content è stato risolto da ViewPager2.

Dai documenti Android: "ViewPager2 sostituisce ViewPager, risolvendo la maggior parte dei punti deboli del suo predecessore, incluso il supporto del layout da destra a sinistra, l'orientamento verticale, raccolte di frammenti modificabili, ecc."

Consiglio questo articolo ai principianti:

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


Questo problema è ancora lì controlla issuetracker.google.com/u/0/issues/143095219
Somesh Kumar
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.