Android: não consigo ter o ViewPager WRAP_CONTENT

258

Eu configurei um ViewPager simples que possui 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 visualização de imagem tenha apenas 200dp. Tentei substituir a altura do pager por "200", mas isso me deu resultados diferentes com várias resoluções. Não consigo adicionar "dp" a esse valor. Como adiciono 200dp ao layout do pager?

Adão
fonte
1
marque a
Christ

Respostas:

408

Substituir onMeasure do ViewPagerseguinte modo fará com que ele obtenha a altura do filho maior que ele tem 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);
}
Daniel López Lacalle
fonte
24
Isso se aproxima mais do que eu preciso, mas há duas coisas a serem adicionadas: 1. O ViewPager redimensiona apenas o maior de seus filhos reais, ou seja, apenas o item atualmente visível e os itens diretamente adjacentes. A chamada de setOffscreenPageLimit (número total de filhos) no ViewPager resolve isso e resulta em um ViewPager cujo tamanho é definido como o maior de todos os seus itens e nunca é redimensionado. 2. Os WebViews têm alguns problemas estranhos ao tentar medi-los. Chamar requestLayout () em um WebView após carregar algo resolve isso.
0101100101
3
Vou corrigir apenas um pequeno problema: se o viewPager tiver visibilidade para GONE e você configurá-lo para visível, onMeasure será chamado antes que seu fragmento seja criado. Por isso, terá uma altura de 0. Se alguém tiver uma ideia, é bem-vindo. Acho que vou ir com um retorno de chamada para quando o fragmento é criado
edoardotognoni
4
Isso não funcionará se você tiver visualizações filho decorativas - isso ocorre porque o ViewPager.onMeasure () mede as visualizações decorativas e aloca espaço para elas primeiro e, em seguida, fornece o restante do espaço para as crianças não decorativas. No entanto, este foi de longe a solução menos incorreta aqui para que eu tenha upvoted;)
Benjamin Dobell
3
Continuo voltando a isso toda vez que uso um ViewPager
ono 07/07
7
getChildCount () pode retornar 0 enquanto você já executou setAdapter () no ViewPager! A chamada populate () real (que cria as visualizações) acontece dentro do super.onMeasure (widthMeasureSpec, heightMeasureSpec); ligar. Colocar a chamada extra super.onMeasure () no início dessa função fez o truque. Além disso, verifique stackoverflow.com/questions/38492210/...
Southerton
106

Outra solução mais genérica é começar wrap_contenta trabalhar.

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

Você pode usar este ViewPager em seus layouts XML, exatamente como no 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 RelativeLayout, para sobrepor outros elementos da interface do usuário.

Uma desvantagem permanece: se você deseja usar margens, é necessário criar dois layouts aninhados e fornecer 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;
    }

}
cybergen
fonte
34
mais alguém recebeu uma página em branco ao lado do item atual quando o viewpager foi destruído e aberto novamente?
Zyoo
1
Também tenho páginas em branco.
aeren 28/09/14
10
Você só precisa mesclar as duas principais respostas desta pergunta, conforme descrito no meu blog: pristalovpavel.wordpress.com/2014/12/26/…
anil
4
Basta substituir o código do método 'onMeasure' pela resposta dada por 'Daniel López Lacalle'.
precisa
1
Ótimo..! Trabalhou para mim .. @cybergen Muito obrigado vc salvou meu dia ..!
Dnyanesh M
59

Baseei minha resposta em Daniel López Lacalle e neste 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 altura zero. Infelizmente, a solução foi 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 definir uma altura no ViewPager, se você desejar, ou apenas wrap_content.

MinceMan
fonte
Eu tive o mesmo problema e resolvi-o com sua resposta, obrigado. Mas alguma explicação sobre o porquê?
Bart Burg
Eu acho que eles não pretendiam que o conteúdo de quebra-cabeça fosse suportado, pois não acho que eles pensassem que era um caso de uso normal. Para apoiá-lo, temos que medir a nós mesmos depois que nossos filhos são medidos, para que possamos quebrar o conteúdo.
MinceMan 15/02
Por que as imagens neste ViewPager são realmente mais curtas que as de um ImageView que usam o mesmo scaleTypee o mesmo, layout_width=match_parentassim como layout_height=wrap_content? há 20dp faltando lá.
tubarão
Tubarão, eu realmente não tenho certeza. Isso pode ter algo a ver com o que o seu tipo de escala está realmente fazendo. Pode querer tentar definir uma altura.
MinceMan 29/05
1
EU NÃO POSSO FRIKIN ACREDITAR! Passei dois dias colando meu viewpager personalizado e fiquei preso em um problema, quando minha visualização inicial não apareceu e eu simplesmente não conseguia descobrir o porquê! // super has to be called in the beginning so the child views can be initialized.<----- Esse era o motivo, tinha que chamá-lo no início e no final da função onMeasure. Yippiii, virtual toca em mim hoje!
Starwave
37

Eu estava apenas respondendo uma pergunta muito semelhante sobre isso e descobri isso ao procurar um link para fazer backup de minhas reivindicações, que sorte sua :)

Minha outra resposta:
o ViewPager não suporta wrap_content, pois (normalmente) nunca possui 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ê alterna) página).

No entanto, você pode definir uma dimensão precisa (por exemplo, 150dp) e também match_parentfunciona.
Você também pode modificar as dimensões dinamicamente a partir de seu código, alterando o height-attribute 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 seguida, no seu código, em vez de criar um novo ViewPager do zero, você pode aumentar o arquivo xml:

LayoutInflater inflater = context.getLayoutInflater();
inflater.inflate(R.layout.viewpagerxml, layout, true);
Jave
fonte
3
Boa resposta, meio irritante que o comportamento padrão seja "faça algo um tanto incompreensível". Obrigada pelo esclarecimento.
Chris Vandevelde
8
@ ChrisVandevelde, este parece ser um inquilino comum de algumas bibliotecas do Android. Assim que você aprender os fundamentos, você percebe nada segue-os
CQM
1
Mas @Jave, por que o viewpager não pode ajustar sua altura toda vez que seus filhos são carregados?
Diffy
@CQM de fato! A biblioteca ViewPagerIndicator tem o mesmo problema com layout_heightdefinido como wrap_content, mas é ainda pior, pois a solução simples para configurá-lo em um valor fixo não funciona.
Giulio Piancastelli
20

Usando a resposta de Daniel López Localle , criei esta classe no 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)
}}
Felipe Castilhos
fonte
16

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

https://github.com/rnevet/WCViewPager

A solução foi inspirada por algumas das respostas aqui, mas aprimora:

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

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

Raanan
fonte
@mvai, você pode abrir um problema ou bifurcá-lo e modificar o aplicativo de exemplo?
Raanan
1
Descobri que o RecyclerView também tem alguns problemas com wrap_content; ele funciona se você usar um LinearLayoutManager costume, como este . Portanto, nada de errado com sua biblioteca.
Natario 7/08
1
O que ainda precisa ser corrigido é seu uso com o FragmentStatePagerAdapter. Parece que está medindo childs antes que os fragmentos sejam dispostos, dando assim menor altura. O que funcionou para mim foi a resposta do @logan, embora eu ainda esteja trabalhando nisso. Você pode tentar mesclar essa abordagem na sua biblioteca. Não estou familiarizado com o github, desculpe.
Natario 7/08
Obrigado, vou dar uma olhada nisso.
Raanan
1
Para quem quer saber como fazer isso funcionar com um FragmentPagerAdapter, faça seu Adaptador implementar o ObjectAtPositionInterface, mantendo uma lista de fragmentos internamente, para que ele possa retornar o fragmento correspondente do método getObjectAtPosition.
Pablo Pablo
15

Acabei de esbarrar no mesmo problema. Eu tinha um ViewPager e queria exibir um anúncio no botão dele. A solução que encontrei foi obter o pager em um RelativeView e defini-lo layout_above para o ID da visualização que desejo ver abaixo. isso funcionou para mim.

aqui está o meu XML de layout:

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

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

    <android.support.v4.view.ViewPager
        android:id="@+id/mainpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/AdLayout" >
    </android.support.v4.view.ViewPager>
</RelativeLayout>
Idan
fonte
4
apenas para referência, você não precisa de xmlns: android = " schemas.android.com/apk/res/android " em ambos, apenas no primeiro.
Martin Marconcini 19/07/2013
2
Seu problema não era o mesmo. Seu layout funciona bem com o ViewPager definido como match_parent - o OP teve uma situação em que ele queria que o ViewPager envolvesse seu conteúdo.
K2col
9

Eu também tive esse problema, mas no meu caso eu tinha um FragmentPagerAdapterque estava fornecendo as ViewPagerpáginas. O problema que tive foi onMeasure()o de ViewPagerchamado antes de qualquer um Fragmentsdeles ter sido criado (e, portanto, não era possível dimensionar-se corretamente).

Após algumas tentativas e erros, descobri que o finishUpdate()método do FragmentPagerAdapter é chamado após a Fragmentsinicialização (a partir 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 na minha FragmentPagerAdaptere chamo:

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

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

o que, por sua vez, me permite chamar setVariableHeight()minha CustomViewPagerimplementação:

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

Não tenho certeza de que é a melhor abordagem, gostaria de comentar se você acha que é bom / ruim / mau, mas parece estar funcionando muito bem na minha implementação :)

Espero que isto seja útil a alguém!

EDIT: esqueci de adicionar um requestLayout()depois de chamar super.measure()(caso contrário, não redesenhar a exibição).

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

Também deixei cair mantendo o MeasureSpecs de largura / altura original em favor da criação de um novo, conforme necessário. Atualizou o código de acordo.

Outro problema que tive foi que ele não se dimensionaria corretamente em um ScrollViewe descobriu que o culpado estava medindo a criança em MeasureSpec.EXACTLYvez 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.

logan
fonte
Por que você não adiciona aqueles que esqueceu ao código, por favor.
Hasan
@hasan eu já fiz, desculpe por qualquer confusão! Atualiza a resposta para dizer isso também
logan
Impressionante! Ainda bem que ajudou :)
logan
8

Outra solução é atualizar a ViewPageraltura de acordo com a altura da página atual PagerAdapter. Supondo que você esteja criando suas ViewPagerpáginas 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 mPagesuma lista interna de PageInfoestruturas é adicionada dinamicamente ao método PagerAdaptere CustomImageViewé regular ImageViewcom o onMeasure()método de substituição que define sua altura de acordo com a largura especificada e mantém a proporção da imagem.

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

@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 o Math.max(height, 1). Isso corrige um bug irritante que ViewPagernão atualiza a página exibida (mostra em branco), quando a página anterior tem altura zero (ou seja, drawable nulo na CustomImageView), cada golpe ímpar para frente e para trás entre duas páginas.

Blackhex
fonte
parece-me o caminho certo a seguir, mas eu precisava de um anúncio item.mImageView.measure(..)para obter as dimensões corretas nos getMeasuredXXX()métodos.
Gianluca P.
6

Ao usar conteúdo estático dentro do viewpager e você não deseja animação animada, use o seguinte pager de visualização

public class HeightWrappingViewPager extends ViewPager {

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

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

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)   {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      View firstChild = getChildAt(0);
      firstChild.measure(widthMeasureSpec, heightMeasureSpec);
      super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(firstChild.getMeasuredHeight(), MeasureSpec.EXACTLY));
  }
}
Kirill Kulakov
fonte
Isso funciona bem. Eu o estendi fazendo um loop pelas crianças e pegando aquela com altura máxima.
Javier Mendonça
Funciona bem mesmo sob vista reciclador
kanudo
Estou recebendo esta exceção - java.lang.NullPointerException: tentativa de invocar o método virtual 'void android.view.View.measure (int, int)' em uma referência de objeto nulo
PJ2104
Mas pegar o primeiro elemento pode ser o errado.
Tobias Reich
4
public CustomPager (Context context) {
    super(context);
}

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

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

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

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

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

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

No código-fonte do aplicativo Android do tempo da pipoca, encontrei esta solução que ajusta dinamicamente o tamanho do viewpager com uma bela animação, 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);
    }
}
Vihaan Verma
fonte
4

Caso você precise do ViewPager que ajuste seu tamanho para cada criança , não apenas para a maior, escrevi um código que faz isso. Observe que não há animação sobre essa alteração (não é necessário no meu caso)

A bandeira android: minHeight também é suportada.

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

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

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

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

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

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

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

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

                ViewGroup.LayoutParams layoutParams = ChildWrappingAdjustableViewPager.this.getLayoutParams();
                layoutParams.height = childHeights.get(position);
                ChildWrappingAdjustableViewPager.this.setLayoutParams(layoutParams);
                ChildWrappingAdjustableViewPager.this.invalidate();
            }
        });
    }
}
Phatee P
fonte
Portanto, este adaptador tem um enorme problema quando a quantia de itens nas mudanças adaptador
jobbert
você pode esclarecer sua afirmação?
Phatee P
Esse código pode causar ponteiros nulos, pois nem todos os filhos são calculados no início. Experimente um layout de guia e role de 1 a 5 ou código e você verá.
jobbert
4

Melhor resposta de Daniel López Lacalle , reescrita em Kotlin :

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

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

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

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

Eu me deparei com o mesmo problema e também tive que fazer com que o ViewPager envolvesse seu conteúdo quando o usuário rolasse entre as páginas. Usando a resposta acima do cybergen, defini o método onMeasure da seguinte maneira:

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

avlacatus
fonte
Apenas o conteúdo mais altura é apareceu com a sua resposta, o outro conteúdo é desapareceu ...
chama Tama
2

Nada do sugerido acima funcionou para mim. Meu caso de uso está tendo 4 ViewPagers personalizados ScrollView. O topo deles é medido com base na proporção, e o resto apenas layout_height=wrap_content. Eu tentei soluções cybergen , Daniel López Lacalle . Nenhum deles funciona totalmente para mim.

Meu palpite por que o 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.

As sugestões de cybergen e Daniel López Lacalle têm um comportamento estranho no meu caso: 2 de 3 estão carregadas ok e 1 de altura aleatória é 0. Parece que onMeasurefoi chamado antes das crianças serem preenchidas. Então, eu vim com uma mistura dessas 2 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 é permitir ViewPagercalcular as dimensões das crianças e salvar a altura calculada da primeira página nos parâmetros de layout da ViewPager. Não se esqueça de definir a altura do layout do fragmento para wrap_contentque você possa obter height = 0. Eu usei este:

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

Observe que esta solução funciona bem se todas as suas páginas tiverem a mesma altura . Caso contrário, você precisará recalcular a ViewPageraltura com base no filho ativo atual. Não preciso, mas se você sugerir a solução, ficarei feliz em atualizar a resposta.

mente
fonte
Você ainda pode atualizar sua resposta depois de todos esses anos? Iria me ajudar uma tonelada
Denny
2

Para pessoas com esse problema e codificando 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 visualizações do seu filho tiverem a mesma altura. Caso contrário, você seria obrigado a armazenar algum tipo de valor "minimumHeight" em todos os filhos com os quais você faz check-list e, mesmo assim, talvez não deseje ter espaços vazios visíveis abaixo das visualizações filho 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, ao que parece.

compat
fonte
Isso funcionou para mim. Todas as visualizações de meus filhos no viewpager têm a mesma altura.
Dmitry
2

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, ela parou de funcionar. Acontece que a solução antiga estava sendo usada getChildAt(getCurrentItem())para obter a visão filho atual para medir o que não está funcionando. Consulte a solução aqui: https://stackoverflow.com/a/16512217/1265583

Abaixo, trabalha com a API 23:

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

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
Howard Lin
fonte
Obrigado!! Estou tentando respostas há horas e este é o único que funciona totalmente para mim. Ele precisa ser combinado com um adaptador personalizado, onde 'setPrimaryItem () `chama uma função no pager que chama, requestLayout()para que a altura seja ajustada à medida que avançamos de uma guia para a próxima. Você se lembra por que superprecisa ser chamado duas vezes? Notei que não funcionará de outra maneira.
M3RS 6/08
Funciona com API 28.
Khalid Lakhani
2

O código abaixo é a única coisa que funcionou para mim

1. Use esta classe para declarar um 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. Insira o pager da exibição de quebra de altura no seu arquivo xml:

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

3. Declare seu pager de exibição:

HeightWrappingViewPager mViewPager;
mViewPager = (HeightWrappingViewPager) itemView.findViewById(R.id.pager);
CustomAdapter adapter = new CustomAdapter(context);
mViewPager.setAdapter(adapter);
mViewPager.measure(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
Hossam Hassan
fonte
Obrigado. Isso funcionou. Mas por que a equipe do Android não pode ter isso em sua base de código?
Mohanakrrishna 28/11/19
Essa é uma das coisas que você precisa para personalizar depende da sua necessidade, também o Google apresentou o viewPager2 neste ano de 2019 no Google I / O e é uma substituição do antigo ViewPager, criado em 2011, com a implementação 'androidx.viewpager2: viewpager2 : 1.0.0-alpha04 '
Hossam Hassan
2

Eu edito a resposta cibernética para fazer com que o viewpager altere a altura, dependendo do item selecionado. A classe é a mesma do cybergen, mas adicionei um Vetor de números inteiros que são todas as alturas de visualizações filho do viewpager e podemos acessá-lo quando a página muda para atualizar a altura

Esta é a 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;
    }
}

Em sua atividade, adicione um 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) {}
});

E aqui está o xml:

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

Por favor, corrija meu inglês, se necessário

geggiamarti
fonte
Isso tem alguns problemas. A heightslista pode aumentar o infinito.
rosuh 21/04
@rosuh Quando você encontrou o problema? Eu usei isso apenas no TabLayout com o ViewPager, por isso não tenho certeza se funciona bem em todos os lugares
geggiamarti
@geggiamarti O problema é que algumas páginas seriam recicladas. E recriado quando o usuário desliza para eles, portanto, a measurechamada seria várias vezes. Pode aumentar a lista de alturas. Outra situação é que o usuário pode chamar requestLayout(ou setLayoutParamsmétodo, exatamente como o que você fez) para este viewPager manualmente, também medirá várias vezes.
rosuh 22/04
1

Se o que ViewPagervocê está usando é filho de um ScrollView AND e tem um PagerTitleStripfilho, você precisará usar uma pequena modificação das ótimas respostas já fornecidas. Para referência, meu XML se parece com isso:

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

No seu, onMeasurevocê precisa ADICIONAR a altura medida de PagerTitleStripse um for encontrado. Caso contrário, sua altura não será considerada na maior altura de todas as crianças, mesmo que ocupe espaço adicional.

Espero que isso ajude outra pessoa. Desculpe que é um pouco de um 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);
    }
}
alexgophermix
fonte
1

A maioria das soluções que vejo aqui parece estar fazendo uma medição dupla: primeiro medindo as visualizações da criança e depois chamando o super.onMeasure()

Eu criei um costume WrapContentViewPagerque é mais eficiente, funciona bem com o RecyclerView e o Fragment

Você pode conferir a demonstração aqui:

github / ssynhtn / WrapContentViewPager

e o código da classe aqui: WrapContentViewPager.java

ssynhtn
fonte
0

Eu tenho um cenário semelhante (mas mais complexo). Eu tenho uma caixa de diálogo que contém um ViewPager.
Uma das páginas filhas é curta, com uma altura estática.
Outra página filha deve sempre ser 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 do ScrollView não precisar da altura total disponível para a caixa de diálogo.

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

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 a @Raanan pelo código para medir 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.

Patrick
fonte
0

no meu caso, a adição clipToPaddingresolveu o problema.

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

Felicidades!

Mario
fonte
0

No meu caso, a adição do android: fillViewport = "true" resolveu o problema

hiten pannu
fonte
0

No meu caso, eu precisava de um viewpager com um wrap_content para o elemento e animação atualmente selecionados ao aplicar o tamanho. Abaixo você pode ver minha implementação. Alguém pode vir a calhar.

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

Adicione attrs.xml no projeto:

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

E use:

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

Esse ViewPager é redimensionado apenas para os filhos visíveis atuais (não o maior de seus filhos reais)

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

}

Erfan Eghterafi
fonte
0

Meça a altura do 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;
    }

}

chamada setPrimaryView (Exibir) :

public class ZGAdapter extends PagerAdapter {

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

}
wslaimin
fonte
0

Forneça o layout pai do ViewPager como 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ão se esqueça de definir android:fillViewport="true"

Isso expandirá a visualização de rolagem e o conteúdo de seu filho para preencher a janela de visualização.

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

Krishna
fonte
0

Você pode mudar para o ViewPager2. É uma versão atualizada do ViewPager. Ele faz o mesmo que o ViewPager, mas de maneira mais inteligente e eficiente. O ViewPager2 vem com uma variedade de novos recursos. Obviamente, o problema do Wrap Wrap foi resolvido pelo ViewPager2.

Da documentação do Android: "O ViewPager2 substitui o ViewPager, abordando a maioria dos pontos problemáticos de seu antecessor, incluindo suporte de layout da direita para a esquerda, orientação vertical, coleções de fragmentos modificáveis ​​etc."

Eu recomendo este artigo para iniciantes:

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

seyfullah.bilgin
fonte
Esta questão ainda está lá. Confira issuetracker.google.com/u/0/issues/143095219
Somesh Kumar