Android: je ne parviens pas à avoir ViewPager WRAP_CONTENT


258

J'ai installé un simple ViewPager qui a une ImageView avec une hauteur de 200dp sur chaque page.

Voici mon téléavertisseur:

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

Malgré la hauteur définie comme wrap_content, le pager remplit toujours l'écran même si la vue d'image n'est que de 200dp. J'ai essayé de remplacer la hauteur du téléavertisseur par "200" mais cela me donne des résultats différents avec plusieurs résolutions. Je ne peux pas ajouter "dp" à cette valeur. Comment ajouter 200dp à la disposition du pager?


Réponses:


408

En remplaçant la mesure de votre ViewPagercomme suit, elle atteindra la taille du plus grand enfant qu'elle a actuellement.

@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
Cela se rapproche le plus de ce dont j'ai besoin, mais il y a deux choses à ajouter: 1. Le ViewPager ne redimensionne que le plus grand de ses enfants réels, c'est-à-dire uniquement l'élément actuellement visible et les éléments directement adjacents. L'appel de setOffscreenPageLimit (nombre total d'enfants) sur le ViewPager résout ce problème et entraîne un ViewPager dont la taille est définie sur le plus grand de tous ses éléments et ne se redimensionne jamais. 2. Les WebViews ont des problèmes étranges lorsque vous essayez de les mesurer. L'appel de requestLayout () sur une WebView après le chargement de quelque chose résout cela.
0101100101

3
Il y a juste un petit problème que je vais résoudre: si le viewPager a sa visibilité sur GONE et que vous le définissez sur visible, onMeasure est appelé avant que son fragment ne soit créé. Il finira donc par avoir une hauteur de 0. Si quelqu'un a une idée, il est le bienvenu. Je pense que j'irai avec un rappel pour quand le fragment sera créé
edoardotognoni

4
Cela ne fonctionnera pas si vous avez des vues enfant de décor - c'est parce que ViewPager.onMeasure () mesure les vues de décor et leur alloue d'abord de l'espace, puis donne le reste de l'espace aux enfants non décor. Néanmoins, c'est de loin la solution la moins incorrecte ici, j'ai donc voté positivement;)
Benjamin Dobell

3
Je reviens à chaque fois que j'utilise un ViewPager
ono

7
getChildCount () peut retourner 0 alors que vous avez déjà exécuté setAdapter () sur ViewPager! L'appel populate () réel (qui crée les vues) se produit à l'intérieur du super.onMeasure (widthMeasureSpec, heightMeasureSpec); appel. Mettre l'appel supplémentaire super.onMeasure () au début de cette fonction a fait l'affaire. Consultez également stackoverflow.com/questions/38492210/…
southerton

106

Une autre solution plus générique consiste à se wrap_contentcontenter de travailler.

J'ai étendu ViewPagerpour remplacer onMeasure(). La hauteur est enroulée autour de la première vue enfant. Cela peut entraîner des résultats inattendus si les vues enfant ne sont pas exactement de la même hauteur. Pour cela, la classe peut être facilement étendue pour, disons, animer à la taille de la vue / page actuelle. Mais je n'en avais pas besoin.

Vous pouvez utiliser ce ViewPager dans vos présentations XML comme le ViewPager d'origine:

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

Avantage: Cette approche permet d'utiliser le ViewPager dans n'importe quelle mise en page, y compris RelativeLayout pour superposer d'autres éléments d'interface utilisateur.

Un inconvénient demeure: si vous souhaitez utiliser des marges, vous devez créer deux dispositions imbriquées et donner à l'intérieur les marges souhaitées.

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

}

34
quelqu'un d'autre a obtenu une page vierge à côté de l'élément actuel lorsque le viseur a été détruit et rouvert?
Zyoo

1
J'ai aussi des pages blanches.
aeren

10
Vous avez juste besoin de fusionner deux réponses principales à cette question comme décrit dans mon blog: pristalovpavel.wordpress.com/2014/12/26/…
anil

4
Il suffit de remplacer le code de la méthode «onMeasure» par la réponse donnée par «Daniel López Lacalle».
Yog Guru

1
Génial..! A travaillé pour moi .. @cybergen Merci beaucoup, vous avez sauvé ma journée ..!
Dnyanesh M

59

J'ai basé ma réponse sur Daniel López Lacalle et ce post http://www.henning.ms/2013/09/09/viewpager-that-simply-dont-measure-up/ . Le problème avec la réponse de Daniel est que dans certains cas, mes enfants avaient une hauteur de zéro. La solution était malheureusement de mesurer deux fois.

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

Cela vous permet également de définir une hauteur sur le ViewPager si vous le souhaitez ou simplement wrap_content.


J'ai eu le même problème et l'ai résolu avec votre réponse, merci. Mais une explication sur pourquoi?
Bart Burg

Je pense qu'ils ne voulaient pas que le contenu de l'emballage soit pris en charge car je ne pense pas qu'ils pensaient que c'était un cas d'utilisation normal. Pour le soutenir, nous devons mesurer à nouveau nous-mêmes après que nos enfants sont mesurés afin que nous puissions envelopper le contenu.
MinceMan

Pourquoi les images dans cette ViewPager plus courtes que celles actuellement dans un ImageView qui utilise le même scaleTypeet de même, layout_width=match_parentainsi que layout_height=wrap_content? il y a comme 20dp manquant là-bas.
Shark

Shark, je ne suis vraiment pas sûr. Cela pourrait avoir quelque chose à voir avec ce que fait réellement votre type de balance. Pourrait vouloir essayer de définir une hauteur.
MinceMan

1
JE NE PEUX PAS FRIKIN LE CROIRE! J'ai passé 2 jours à coller mon viseur personnalisé et je suis resté coincé sur un problème, lorsque ma vue initiale ne s'affichait pas et que je n'arrivais pas à comprendre pourquoi! // super has to be called in the beginning so the child views can be initialized.<----- C'ÉTAIT la raison, a dû l'appeler au début et à la fin de la fonction onMeasure. Yippiii, high fives virtuels sur moi aujourd'hui!
Starwave

37

Je venais de répondre à une question très similaire à ce sujet, et je l'ai trouvé en recherchant un lien pour sauvegarder mes réclamations, alors vous avez de la chance :)

Mon autre réponse:
le ViewPager ne prend pas en charge wrap_contentcar il (généralement) ne charge jamais tous ses enfants en même temps, et ne peut donc pas obtenir une taille appropriée (l'option serait d'avoir un pager qui change de taille à chaque fois que vous avez changé page).

Vous pouvez cependant définir une dimension précise (par exemple 150dp) et match_parentfonctionne également.
Vous pouvez également modifier les dimensions dynamiquement à partir de votre code en modifiant l' heightattribut -attitude dans son LayoutParams.

Pour vos besoins, vous pouvez créer le ViewPager dans son propre fichier xml, avec le layout_height défini sur 200dp, puis dans votre code, plutôt que de créer un nouveau ViewPager à partir de zéro, vous pouvez gonfler ce fichier xml:

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

3
Bonne réponse, un peu ennuyeux que le comportement par défaut soit "faire quelque chose d'incompréhensible". Merci pour l'explication.
Chris Vandevelde

8
@ChrisVandevelde, cela semble être un locataire commun de certaines bibliothèques Android. Dès que vous apprenez les fondamentaux, vous réalisez que rien ne les suit
CQM

1
Mais @Jave, pourquoi le visualiseur ne peut-il pas ajuster sa hauteur à chaque fois que ses enfants sont chargés?
Diffy

@CQM en effet! La bibliothèque ViewPagerIndicator a le même problème avec layout_heightset to wrap_content, mais c'est encore pire car la solution de contournement simple pour la configurer à un montant fixe ne fonctionne pas.
Giulio Piancastelli

20

En utilisant la réponse de Daniel López Localle , j'ai créé cette classe dans Kotlin. J'espère que cela vous fera gagner plus de temps

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

J'ai déjà rencontré ce problème dans plusieurs projets et je n'ai jamais eu de solution complète. J'ai donc créé un projet github WrapContentViewPager en remplacement sur place de ViewPager.

https://github.com/rnevet/WCViewPager

La solution a été inspirée par certaines des réponses ici, mais améliore:

  • Modifie dynamiquement la hauteur du ViewPager en fonction de la vue actuelle, y compris lors du défilement.
  • Prend en compte la hauteur des vues "décor" comme PagerTabStrip.
  • Prend en considération tous les rembourrages.

Mis à jour pour la bibliothèque de support version 24 qui a cassé l'implémentation précédente.


@mvai pouvez-vous ouvrir un problème, ou le modifier et modifier l'exemple d'application?
Raanan

1
J'ai découvert que RecyclerView avait aussi des problèmes avec wrap_content; cela fonctionne si vous utilisez un LinearLayoutManager personnalisé, comme celui-ci . Donc, rien de mal avec votre bibliothèque.
natario

1
Ce qui doit encore être corrigé, c'est son utilisation avec FragmentStatePagerAdapter. On dirait qu'il mesure les enfants avant que les fragments ne soient disposés, donnant ainsi une plus petite hauteur. Ce qui a fonctionné pour moi, c'est la réponse de @logan, même si j'y travaille toujours. Vous voudrez peut-être essayer de fusionner cette approche dans votre bibliothèque. Je ne connais pas github, désolé.
natario

Merci, je vais y jeter un œil.
Raanan

1
Pour quiconque se demande comment faire fonctionner cela avec un FragmentPagerAdapter, faites en sorte que votre adaptateur implémente ObjectAtPositionInterface en conservant une liste de fragments en interne, afin qu'il puisse retourner le fragment correspondant à partir de la méthode getObjectAtPosition.
Pablo

15

Je suis juste tombé sur le même problème. J'avais un ViewPager et je voulais afficher une annonce au bouton de celui-ci. La solution que j'ai trouvée consistait à placer le pager dans un RelativeView et à définir son layout_above sur l'ID de vue que je veux voir en dessous. cela a fonctionné pour moi.

voici ma mise en page 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
juste pour référence, vous n'avez pas besoin de xmlns: android = " schemas.android.com/apk/res/android " dans les deux, seulement dans le premier.
Martin Marconcini

2
Votre problème n'était pas du tout le même. Votre mise en page fonctionne bien avec ViewPager défini sur match_parent - l'OP a eu une situation où il voulait que ViewPager encapsule son contenu.
k2col

9

J'ai également rencontré ce problème, mais dans mon cas, j'avais un FragmentPagerAdapterqui fournissait le ViewPageravec ses pages. Le problème que j'ai eu était que onMeasure()le a ViewPagerété appelé avant la Fragmentscréation de l'un (et ne pouvait donc pas se dimensionner correctement).

Après un peu d'essais et d'erreurs, j'ai trouvé que la finishUpdate()méthode de FragmentPagerAdapter est appelée après Fragmentsavoir été initialisée (à partir instantiateItem()de la FragmentPagerAdapter) et également après / pendant le défilement de la page. J'ai fait une petite interface:

public interface AdapterFinishUpdateCallbacks
{
    void onFinishUpdate();
}

que je passe dans mon FragmentPagerAdapteret appelle:

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

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

ce qui me permet à mon tour de faire appel setVariableHeight()à ma CustomViewPagermise en œuvre:

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

Je ne suis pas sûr que ce soit la meilleure approche, j'adorerais les commentaires si vous pensez que c'est bon / mauvais / mauvais, mais cela semble fonctionner assez bien dans ma mise en œuvre :)

J'espère que cela aide quelqu'un là-bas!

EDIT: J'ai oublié d'ajouter un requestLayout()après l'appel super.measure()(sinon il ne redessine pas la vue).

J'ai également oublié d'ajouter le rembourrage du parent à la hauteur finale.

J'ai également abandonné la conservation de la largeur / hauteur originale MeasureSpecs en faveur de la création d'une nouvelle au besoin. Avoir mis à jour le code en conséquence.

Un autre problème que j'avais était qu'il ne se dimensionnerait pas correctement dans un ScrollViewet a trouvé que le coupable mesurait l'enfant avec MeasureSpec.EXACTLYau lieu de MeasureSpec.UNSPECIFIED. Mis à jour pour refléter cela.

Ces modifications ont toutes été ajoutées au code. Vous pouvez consulter l'historique pour voir les anciennes versions (incorrectes) si vous le souhaitez.


Pourquoi vous n'ajoutez pas ceux que vous avez oubliés au code s'il vous plaît.
hasan

@hasan je l'ai déjà fait, désolé pour toute confusion! Mettra à jour la réponse pour le dire également
logan

Impressionnant! Heureux que cela ait aidé :)
logan

8

Une autre solution consiste à mettre à jour la ViewPagerhauteur en fonction de la hauteur de page actuelle dans son PagerAdapter. En supposant que vous créez vos ViewPagerpages de cette façon:

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

mPagesest la liste interne des PageInfostructures ajoutée dynamiquement à PagerAdapteret CustomImageViewest juste régulière ImageViewavec la onMeasure()méthode surchargée qui définit sa hauteur en fonction de la largeur spécifiée et conserve le rapport hauteur / largeur de l'image.

Vous pouvez forcer la ViewPagerhauteur dans la setPrimaryItem()méthode:

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

Notez le Math.max(height, 1). Cela corrige un bug ennuyeux qui ViewPagerne met pas à jour la page affichée (l'affiche vide), lorsque la page précédente a une hauteur nulle (c'est-à-dire nul dessinable dans le CustomImageView), chaque balayage impair va et vient entre deux pages.


il me semble que la bonne voie à suivre, mais je devais ajouter une item.mImageView.measure(..)pour obtenir les bonnes dimensions dans les getMeasuredXXX()méthodes.
Gianluca P.

6

Lorsque vous utilisez du contenu statique dans le viewpager et que vous ne souhaitez aucune animation de fantaisie, vous pouvez utiliser le view pager suivant

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

Cela fonctionne bien. Je l'ai étendu en faisant une boucle à travers les enfants et en prenant celui avec la taille maximum.
Javier Mendonça

Fonctionne bien même sous la vue du recycleur
kanudo

Je reçois cette exception - java.lang.NullPointerException: Tentative d'invoquer la méthode virtuelle 'void android.view.View.measure (int, int)' sur une référence d'objet nul
PJ2104

Mais prendre le premier élément pourrait être le mauvais.
Tobias Reich du

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

À partir du code source de l'application Android de Popcorn Time, j'ai trouvé cette solution qui ajuste dynamiquement la taille du viseur avec une belle animation en fonction de la taille de l'enfant actuel.

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

Dans le cas où vous avez besoin de ViewPager qui ajuste sa taille à chaque enfant , pas seulement au plus grand, j'ai écrit un morceau de code qui le fait. Notez qu'il n'y a pas d'animation sur ce changement (pas nécessaire dans mon cas)

android: le drapeau minHeight est également pris en charge.

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

Cet adaptateur a donc un énorme problème lorsque la quantité d'éléments dans l'adaptateur change
jobbert

pouvez-vous clarifier votre déclaration?
Phatee P

Ce code peut provoquer des pointeurs nuls car tous les enfants ne sont pas calculés au démarrage. Essayez une disposition d'onglet et faites défiler de 1 à 5 ou par code et vous le verrez.
jobbert

4

Amélioration de la réponse de Daniel López Lacalle , réécrite dans 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

Je suis tombé sur le même problème et j'ai également dû faire en sorte que le ViewPager encapsule son contenu lorsque l'utilisateur faisait défiler les pages. En utilisant la réponse ci-dessus de cybergen, j'ai défini la méthode onMeasure comme suit:

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

De cette façon, la méthode onMeasure définit la hauteur de la page actuelle affichée par le ViewPager.


Seul le contenu le plus haut est apparu avec votre réponse, l'autre contenu a disparu ...
Blaze Tama

2

Rien de suggéré ci-dessus n'a fonctionné pour moi. Mon cas d'utilisation est d'avoir 4 ViewPagers personnalisés ScrollView. Le haut d'eux est mesuré en fonction du rapport d'aspect et le reste a juste layout_height=wrap_content. J'ai essayé les solutions cybergen , Daniel López Lacalle . Aucun d'eux ne fonctionne pleinement pour moi.

Je suppose que cybergen ne fonctionne pas sur la page> 1, car il calcule la hauteur du pager en fonction de la page 1, qui est masqué si vous faites défiler plus loin.

Les suggestions de cybergen et de Daniel López Lacalle ont un comportement étrange dans mon cas: 2 sur 3 sont chargés correctement et 1 au hasard est 0. Apparaît qui a onMeasureété appelé avant que les enfants ne soient peuplés. J'ai donc trouvé un mélange de ces 2 réponses + mes propres correctifs:

@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'idée est de laisser ViewPagercalculer les dimensions des enfants et d'enregistrer la hauteur calculée de la première page dans les paramètres de mise en page du ViewPager. N'oubliez pas de définir la hauteur de mise en page du fragment sur wrap_contentsinon vous pouvez obtenir hauteur = 0. J'ai utilisé celui-ci:

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

Veuillez noter que cette solution fonctionne très bien si toutes vos pages ont la même hauteur . Sinon, vous devez recalculer la ViewPagerhauteur en fonction de l'enfant actuellement actif. Je n'en ai pas besoin, mais si vous suggérez la solution, je serais heureux de mettre à jour la réponse.


Pourriez-vous toujours mettre à jour votre réponse après toutes ces années? M'aiderait une tonne
Denny

2

Pour les personnes ayant ce problème et codant pour Xamarin Android en C #, cela pourrait également être une solution rapide:

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

Ceci est principalement utile si les vues de vos enfants sont de la même hauteur. Sinon, vous devrez stocker une sorte de valeur "minimumHeight" sur tous les enfants que vous comparez, et même dans ce cas, vous ne voudrez peut-être pas que des espaces vides soient visibles sous vos vues enfant plus petites.

La solution elle-même n'est cependant pas suffisante pour moi, mais c'est parce que mes éléments enfants sont listViews et leur MeasuredHeight n'est pas calculé correctement, semble-t-il.


Cela a fonctionné pour moi. Toutes mes vues enfant dans le viseur sont de la même hauteur.
Dmitry

2

J'ai une version de WrapContentHeightViewPager qui fonctionnait correctement avant l'API 23 qui redimensionnera la base de hauteur de la vue parent sur la vue enfant actuelle sélectionnée.

Après la mise à niveau vers API 23, il a cessé de fonctionner. Il s'avère que l'ancienne solution était utilisée getChildAt(getCurrentItem())pour obtenir la vue enfant actuelle pour mesurer ce qui ne fonctionne pas. Voir la solution ici: https://stackoverflow.com/a/16512217/1265583

Ci-dessous fonctionne avec l'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);
}

Je vous remercie!! J'ai essayé des réponses pendant des heures et c'est la seule qui fonctionne parfaitement pour moi. Il doit être combiné avec un adaptateur personnalisé où 'setPrimaryItem () `appelle une fonction dans le pager qui appelle requestLayout()donc la hauteur est ajustée lorsque nous passons d'un onglet à l'autre. Vous rappelez-vous pourquoi superdoit être appelé deux fois? J'ai remarqué que cela ne fonctionnerait pas autrement.
M3RS

Fonctionne avec l'API 28.
Khalid Lakhani

2

Le code ci-dessous est la seule chose qui a fonctionné pour moi

1. Utilisez cette classe pour déclarer un 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. Insérez le pager de vue d'habillage de hauteur dans votre fichier xml:

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

3. Déclarez votre téléavertisseur de vue:

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

Merci. Cela a fonctionné. Mais pourquoi l'équipe Android ne peut-elle pas avoir cela dans sa base de code?
Mohanakrrishna

C'est l'une des choses que vous devez personnaliser vous-même en fonction de vos besoins, Google a également introduit viewPager2 en cette année 2019 Google I / O et est un remplacement de l'ancien ViewPager, qui a été créé en 2011, implémentation 'androidx.viewpager2: viewpager2 : 1.0.0-alpha04 '
Hossam Hassan

2

Je modifie la réponse de cybergen pour que le viewpager change de hauteur en fonction de l'élément sélectionné.La classe est la même que celle de cybergen, mais j'ai ajouté un vecteur d'entiers qui correspond à toutes les hauteurs de vue enfant du viewpager et nous pouvons y accéder lorsque la page change pour mettre à jour la hauteur

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

Ensuite, dans votre activité, ajoutez 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) {}
});

Et voici xml:

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

Veuillez corriger mon anglais si nécessaire


Cela a quelques problèmes. La heightsliste peut augmenter l'infini.
rosuh

@rosuh Quand avez-vous rencontré le problème? Je l'ai utilisé uniquement dans TabLayout avec ViewPager, donc je ne suis pas sûr que cela fonctionne bien partout
geggiamarti

@geggiamarti Le problème est que certaines pages seraient recyclées. Et recréé lorsque l'utilisateur glisse vers eux, le measureserait donc appelé plusieurs fois. Cela peut augmenter la liste des hauteurs. Une autre situation est que l'utilisateur peut appeler requestLayout(ou une setLayoutParamsméthode, comme vous l'avez fait) pour ce viewPager manuellement, mesurera également plusieurs fois.
rosuh

1

Si ViewPagervous utilisez est un enfant d'un ScrollView ET a un PagerTitleStripenfant, vous devrez utiliser une légère modification des bonnes réponses déjà fournies. Pour référence, mon XML ressemble à ceci:

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

Dans votre, onMeasurevous devez AJOUTER la hauteur mesurée du PagerTitleStripsi on en trouve. Sinon, sa hauteur ne sera pas considérée comme la plus grande de tous les enfants, même si elle prend plus de place.

J'espère que ceci aide quelqu'un d'autre. Désolé que ce soit un peu un hack ...

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 plupart des solutions que je vois ici semblent faire une double mesure: mesurer d'abord les vues des enfants, puis appeler super.onMeasure()

J'ai trouvé une personnalisation WrapContentViewPagerplus efficace, qui fonctionne bien avec RecyclerView et Fragment

Vous pouvez consulter la démo ici:

github / ssynhtn / WrapContentViewPager

et le code de la classe ici: WrapContentViewPager.java


0

J'ai un scénario similaire (mais plus complexe). J'ai une boîte de dialogue, qui contient un ViewPager.
L'une des pages enfants est courte, avec une hauteur statique.
Une autre page enfant doit toujours être aussi haute que possible.
Une autre page enfant contient un ScrollView, et la page (et donc la boîte de dialogue entière) doit WRAP_CONTENT si le contenu de ScrollView n'a pas besoin de toute la hauteur disponible pour la boîte de dialogue.

Aucune des réponses existantes n'a fonctionné complètement pour ce scénario spécifique. Tenez, c'est un parcours cahoteux.

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

Un grand merci à @Raanan pour le code pour mesurer les vues et mesurer la hauteur du décor. J'ai rencontré des problèmes avec sa bibliothèque - l'animation a bégayé, et je pense que mon ScrollView ne défilerait pas lorsque la hauteur de la boîte de dialogue était suffisamment courte pour l'exiger.


0

dans mon cas, l'ajout a clipToPaddingrésolu le problème.

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

À votre santé!


0

Je mon cas en ajoutant Android: fillViewport = "true" a résolu le problème


0

Dans mon cas, j'avais besoin d'un viewpager avec un wrap_content pour l'élément et l'animation actuellement sélectionnés lors de l'application de la taille. Ci-dessous, vous pouvez voir ma mise en œuvre. Quelqu'un peut-il être 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)
            }
        }
    }
}

Ajoutez attrs.xml dans le projet:

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

Et utilise:

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

0

Ce ViewPager ne redimensionne que les enfants visibles actuels (pas le plus grand de ses enfants réels)

L'idée de 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

Mesurez la hauteur de 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;
    }

}

appelez setPrimaryView (Voir):

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

Donner la disposition parent de ViewPager en tant que 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>

N'oubliez pas de régler android:fillViewport="true"

Cela étendra scrollview et le contenu de son enfant pour remplir la fenêtre.

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


0

Vous pouvez basculer vers ViewPager2. Il s'agit d'une version mise à jour de ViewPager. Il fait la même chose que ViewPager mais de manière plus intelligente et efficace. ViewPager2 est livré avec une variété de nouvelles fonctionnalités. Bien sûr, le problème de Wrap Content a été résolu par ViewPager2.

À partir de documents Android: "ViewPager2 remplace ViewPager, abordant la plupart des problèmes de son prédécesseur, y compris la prise en charge de la mise en page de droite à gauche, l'orientation verticale, les collections de fragments modifiables, etc."

Je recommande cet article aux débutants:

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


Ce problème est toujours là consultez issuetracker.google.com/u/0/issues/143095219
Somesh Kumar
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.