ti-enxame.com

Android: não consigo ter o ViewPager WRAP_CONTENT

Eu configurei um ViewPager simples que tem um ImageView com uma altura de 200dp em cada página.

Aqui está o meu pager:

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

Apesar da altura definida como wrap_content, o pager sempre preenche a tela mesmo que a imageview tenha apenas 200dp. Eu tentei substituir a altura do pager com "200", mas isso me dá resultados diferentes com várias resoluções. Não consigo adicionar "dp" a esse valor. Como faço para adicionar 200dp ao layout do pager?

212
Adam

Substituir onMeasure da sua ViewPager da seguinte maneira fará com que ela obtenha a altura do maior filho que possui atualmente.

@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);
}
351
Daniel López Lacalle

Outra solução mais genérica é obter wrap_content para funcionar.

Eu estendi ViewPager para substituir onMeasure(). A altura é envolvida em torno da primeira vista infantil. Isso pode levar a resultados inesperados se as exibições filhas não tiverem exatamente a mesma altura. Para isso, a classe pode ser facilmente estendida para, digamos, animar o tamanho da visão/página atual. Mas eu não precisava disso.

Você pode usar este ViewPager em seus layouts XML como o ViewPager original:

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

Vantagem: Essa abordagem permite usar o ViewPager em qualquer layout, incluindo o RelativeLayout, para sobrepor outros elementos da interface do usuário.

Uma desvantagem permanece: Se você quiser usar margens, você deve criar dois layouts aninhados e dar ao interno as margens desejadas.

Aqui está o código:

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

}
95
cybergen

Eu baseei minha resposta em Daniel López Lacalle e este post http://www.henning.ms/2013/09/09/viewpager-that-simply-dont-measure-up/ . O problema com a resposta de Daniel é que, em alguns casos, meus filhos tinham uma altura de zero. A solução foi, infelizmente, medir duas vezes.

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

Isso também permite que você defina uma altura no ViewPager, se desejar, ou apenas wrap_content.

47
MinceMan

Eu estava apenas respondendo a uma pergunta muito semelhante sobre isso, e aconteceu de encontrar isso ao procurar um link para fazer backup de minhas reivindicações, por isso você tem sorte :) 

Minha outra resposta:
O ViewPager não suporta wrap_content já que (normalmente) nunca tem todos os seus filhos carregados ao mesmo tempo, e portanto não pode obter um tamanho apropriado (a opção seria ter um pager que muda de tamanho toda vez que você mudaram de página).

No entanto, você pode definir uma dimensão precisa (por exemplo, 150 dp) e o match_parent também funciona.
Você também pode modificar as dimensões dinamicamente de seu código alterando o atributo height- em sua LayoutParams.

Para suas necessidades você pode criar o ViewPager em seu próprio arquivo xml, com o layout_height definido como 200dp e, em seu código, em vez de criar um novo ViewPager a partir do zero, você pode inflar esse arquivo xml:

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

Já enfrentei esse problema em vários projetos e nunca tive uma solução completa. Então, criei um projeto github WrapContentViewPager como substituto no local para o ViewPager.

https://github.com/rnevet/WCViewPager

A solução foi inspirada em algumas das respostas aqui, mas melhora em:

  • Altera dinamicamente a altura do ViewPager de acordo com a visualização atual, incluindo durante a rolagem.
  • Leva em consideração a altura das visualizações "decor", como PagerTabStrip.
  • Leva em consideração todo o preenchimento.

Atualizado para a versão 24 da biblioteca de suporte, que quebrou a implementação anterior.

16
Raanan

Eu encontrei o mesmo problema. Eu tinha um ViewPager e queria exibir um anúncio no botão dele. A solução que encontrei foi colocar o pager em um RelativeView e defini-lo como layout_above para o id da vista que eu quero ver abaixo dele. isso funcionou para mim.

aqui está meu 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>
15
Idan

Eu também corri para este problema, mas no meu caso eu tinha um FragmentPagerAdapter que estava fornecendo o ViewPager com suas páginas. O problema que tive foi que onMeasure() do ViewPager foi chamado antes que qualquer um dos Fragments tivesse sido criado (e, portanto, não poderia se dimensionar corretamente).

Após um pouco de tentativa e erro, descobri que o método finishUpdate() do FragmentPagerAdapter é chamado após o Fragments ter sido inicializado (de instantiateItem() no FragmentPagerAdapter), e também após/durante a rolagem da página. Eu fiz uma pequena interface:

public interface AdapterFinishUpdateCallbacks
{
    void onFinishUpdate();
}

que eu passo para o meu FragmentPagerAdapter e chamo:

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

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

que por sua vez me permite chamar setVariableHeight() na minha implementação CustomViewPager:

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

Eu não tenho certeza se é a melhor abordagem, adoraria comentários se você acha que é bom/ruim/mal, mas parece estar funcionando muito bem na minha implementação :)

Espero que isto seja útil a alguém!

EDIT: Eu esqueci de adicionar um requestLayout() depois de chamar super.measure() (caso contrário ele não redesenhará a view).

Também esqueci de adicionar o preenchimento dos pais à altura final.

Também deixei de manter os MeasureSpecs width/height originais em favor de criar um novo, conforme necessário. Atualizou o código de acordo.

Outro problema que tive foi que ele não se dimensionava corretamente em um ScrollView e achava que o culpado estava medindo o filho com MeasureSpec.EXACTLY em vez de MeasureSpec.UNSPECIFIED. Atualizado para refletir isso.

Todas essas alterações foram adicionadas ao código. Você pode verificar o histórico para ver as versões antigas (incorretas), se desejar.

9
logan

Outra solução é atualizar a altura ViewPager de acordo com a altura da página atual em seu PagerAdapter. Supondo que você esteja criando suas páginas ViewPager desta maneira:

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

Onde mPages é uma lista interna de PageInfo as estruturas adicionadas dinamicamente a PagerAdapter e CustomImageView são apenas ImageView regulares com o método overriden onMeasure() que define sua altura de acordo com a largura especificada e mantém a proporção da imagem.

Você pode forçar a altura ViewPager no método setPrimaryItem():

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

Observe a Math.max(height, 1). Isso corrige um bug irritante que ViewPager não atualiza a página exibida (mostra em branco), quando a página anterior tem altura zero (isto é, drawable nulo no CustomImageView), cada passo estranho vai para frente e para trás entre duas páginas.

8
Blackhex

Usando Daniel López Localle answer, criei esta classe em Kotlin. Espero que você economize mais 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)
}}
8
Felipe Castilhos

Ao usar conteúdo estático dentro do viewpager e você não deseja nenhuma animação sofisticada, você pode usar o pager de visualização a seguir

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

A partir do código fonte do aplicativo Popcorn time Android, encontrei esta solução que ajusta dinamicamente o tamanho do viewpager com animação Nice, dependendo do tamanho do filho atual.

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
Vihaan Verma
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
hasanul hakim

No caso de você precisar do ViewPager que ajuste seu tamanho para cada criança , não apenas para o maior deles, eu escrevi um pedaço de código que faz isso. Note que não há animação sobre essa mudança (não é necessário no meu caso)

Android: o sinalizador minHeight também é suportado.

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

Eu encontrei o mesmo problema, e também tive que fazer o ViewPager envolver seu conteúdo quando o usuário rolou entre as páginas. Usando a resposta acima de cybergen, defini o método onMeasure da seguinte forma:

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

Dessa forma, o método onMeasure define a altura da página atual exibida pelo ViewPager.

3
avlacatus

Nada do sugerido acima funcionou para mim. Meu caso de uso é ter 4 ViewPagers personalizados em ScrollView. O topo deles é medido com base na proporção e o restante tem apenas layout_height=wrap_content. Eu tentei cybergen , Daniel López Lacalle solutions. Nenhum deles funciona totalmente para mim. 

Meu palpite por que cybergen não funciona na página> 1 é porque calcula a altura do pager com base na página 1, que fica oculta se você rolar mais.

Ambas as sugestões cybergen e Daniel López Lacalle têm um comportamento estranho no meu caso: 2 de 3 são carregadas ok e 1 aleatoriamente a altura é 0. Aparece que onMeasure foi chamada antes dos filhos serem preenchidos. Então eu criei uma mistura dessas duas respostas + minhas próprias correções:

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

A idéia é deixar ViewPager calcular as dimensões das crianças e salvar a altura calculada da primeira página em parâmetros de layout da ViewPager. Não se esqueça de definir a altura do layout do fragmento para wrap_content, caso contrário você poderá obter height = 0. Eu usei este aqui:

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

Por favor, note que esta solução funciona muito bem se todas as suas páginas tiverem a mesma altura. Caso contrário, você precisará recalcular a altura ViewPager com base no filho atual ativo. Eu não preciso disso, mas se você sugerir a solução, eu ficaria feliz em atualizar a resposta.

2
mente

Eu tenho uma versão do WrapContentHeightViewPager que estava funcionando corretamente antes da API 23 que redimensionará a base de altura da exibição pai na exibição filho atual selecionada.

Após a atualização para a API 23, ele parou de funcionar. Acontece que a solução antiga estava usando getChildAt(getCurrentItem()) para obter a visão infantil atual para medir o que não está funcionando. Veja a solução aqui: https://stackoverflow.com/a/16512217/1265583

Abaixo trabalha com 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);
}
1
Howard Lin

Se o ViewPager que você está usando for um filho de um ScrollViewEtiver um filho PagerTitleStrip, você precisará usar uma pequena modificação das ótimas respostas já fornecidas. Para referência, meu XML se parece com isto:

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

Em seu onMeasure você tem queADDmeasurementHeight do PagerTitleStrip se um for encontrado. Caso contrário, sua altura não será considerada a maior altura de todas as crianças, embora ocupe espaço adicional.

Espero que isso ajude alguém. Desculpe que é um pouco de truque ...

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
alexgophermix

Para as pessoas que têm esse problema e codificam para Xamarin Android em C #, isso também pode ser uma solução rápida:

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

Isso é útil principalmente se as exibições de seus filhos tiverem a mesma altura. Caso contrário, você seria solicitado a armazenar algum tipo de valor "minimumHeight" em todos os filhos contraídos e, mesmo assim, talvez não deseje ter espaços vazios visíveis abaixo de suas exibições de filhos menores. 

A solução em si não é suficiente para mim, mas isso é porque meus itens filhos são listViews e seu MeasuredHeight não é calculado corretamente, parece.

1
compat

no meu caso, adicionando clipToPadding resolveu o problema.

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

Felicidades!

0
Mario

Eu no meu caso adicionando Android: fillViewport = "true" resolveu o problema

0
hiten pannu

Eu tenho um cenário semelhante (mas mais complexo). Eu tenho um diálogo, que contém um ViewPager.
Uma das páginas filhas é curta, com uma altura estática.
Outra página infantil deve ser sempre a mais alta possível.
Outra página filha contém um ScrollView, e a página (e, portanto, a caixa de diálogo inteira) deve WRAP_CONTENT se o conteúdo ScrollView não precisar da altura total disponível para o diálogo.

Nenhuma das respostas existentes funcionou completamente para este cenário específico. Espere, é um passeio acidentado.

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

Muito obrigado ao @Raanan pelo código para medir as visualizações e medir a altura da decoração. Eu tive problemas com a biblioteca dele - a animação gaguejou, e acho que meu ScrollView não rolaria quando a altura da caixa de diálogo fosse curta o suficiente para exigir isso.

0
Patrick