2012-02-16 13 views
16

Ho un ViewGroup personalizzato con un figlio ViewPager. ViewPager è alimentato da un PagerAdapter che fornisce uno LinearLayout allo ViewPager che ha LayoutParams di WRAP_CONTENT su altezza e larghezza.Misurazione di un ViewPager

la vista visualizza correttamente, ma quando il metodo viene chiamato child.measure() sul ViewPager non restituisce le dimensioni effettive del LinearLayout ma sembra riempire tutto lo spazio rimanente.

Qualche idea del perché questo sta accadendo e come modificarlo?

+0

prega protagonista problema https://code.google.com/p/android/issues/detail?id=54604 – Christ

+0

FYI: Il problema 54604 è stato chiuso alla rinfusa da Google ieri. Se hai ancora il problema, ti suggerisco di riaprire un nuovo problema e inserire il link qui. – Christ

+0

possibile duplicato di [Android: non riesco ad avere ViewPager WRAP \ _CONTENT] (http://stackoverflow.com/questions/8394681/android-i-am-unable-to-have-viewpager-wrap-content) – Raanan

risposta

54

Non ero molto contento la risposta accettata (né con la soluzione pre-gonfia-all-views nei commenti), quindi ho messo insieme uno ViewPager che prende la sua altezza dal primo figlio disponibile. Lo fa facendo un secondo passaggio di misurazione, permettendoti di rubare l'altezza del primo bambino.

Una migliore soluzione sarebbe quella di effettuare una nuova classe all'interno dell'imballaggio android.support.v4.view che implementa una versione migliore di onMeasure (con accesso a metodi del pacchetto visibile come populate())

Per il momento, però, la soluzione qui di seguito mi sta bene

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

     boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) 
       == MeasureSpec.AT_MOST; 

     if(wrapHeight) { 
      /** 
      * The first super.onMeasure call made the pager take up all the 
      * available height. Since we really wanted to wrap it, we need 
      * to remeasure it. Luckily, after that call the first child is 
      * now available. So, we take the height from it. 
      */ 

      int width = getMeasuredWidth(), height = getMeasuredHeight(); 

      // Use the previously measured width but simplify the calculations 
      widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); 

      /* If the pager actually has any children, take the first child's 
      * height and call that our own */ 
      if(getChildCount() > 0) { 
       View firstChild = getChildAt(0); 

       /* The child was previously measured with exactly the full height. 
       * Allow it to wrap this time around. */ 
       firstChild.measure(widthMeasureSpec, 
         MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); 

       height = firstChild.getMeasuredHeight(); 
      } 

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

      super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
     } 
    } 
} 
+0

Questo mi ha risparmiato un sacco di mal di testa - grazie mille! – boz

+0

Perché il tuo HeightWrappingViewPager non funziona all'interno di ScrollView con Android: fillViewport = "true"? Quando sostituisco ViewPager con HeightWrappingViewPager, non riesco a scorrere il contenuto all'interno di ScrollView – Ziem

+0

@Ziem, non ne ho idea. Un normale ViewPager funziona? Sarei molto sorpreso se ScrollView modificasse il suo comportamento in base alle dimensioni del bambino interiore. – Delyan

11

Guardando le interni della classe ViewPager nel vaso di compatibilità:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
{ 
    // For simple implementation, or internal size is always 0. 
    // We depend on the container to specify the layout size of 
    // our view. We can't really know what it is since we will be 
    // adding and removing different arbitrary views and do not 
    // want the layout to change as this happens. 
    setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), getDefaultSize(0, heightMeasureSpec)); 

    ... 
} 

sembrerebbe che l'attuazione ViewPager non misura il punto di vista dei bambini, ma solo imposta il ViewPager per essere una visualizzazione standard in base a ciò il genitore sta passando. Quando passi wrap_content, dato che il pager della vista non misura effettivamente il suo contenuto, occupa l'intera area disponibile.

La mia raccomandazione sarebbe quella di impostare una dimensione statica sul ViewPager in base alle dimensioni delle viste figlio. Se ciò è impossibile (ad esempio, le visualizzazioni secondarie possono variare) dovrai scegliere una dimensione massima e occupare lo spazio extra in alcune viste OPPURE estendere ViewPager e fornire una misura On che misura i bambini. Un problema a cui ti imbatterai è che la visualizzazione del cercapersone è stata progettata per non variare di larghezza in quanto vengono visualizzate diverse visualizzazioni, quindi probabilmente sarai costretto a scegliere una dimensione e stare con essa

+0

I provato ad estendere la classe ViewPager e a sovrascrivere onMeasure ma sembra che ViewPager non abbia figli, getChildCount() restituisce 0. Hai qualche idea del perché possa essere? –

+0

Sembra che l'esistente onMeasure abbia una chiamata a popolare() prima di ottenere i bambini. Dovrai farlo anche tu. –

+1

Grazie ancora ... populate() era privato, quindi non poteva funzionare, ma ho trovato un modo per passare EXACT MeasureSpec al ViewPager e farlo funzionare. –

0

Seguendo l'esempio precedente ho scoperto che misurare l'altezza delle viste figlio non sempre restituisce risultati accurati. La soluzione è misurare l'altezza di qualsiasi vista statica (definita in xml) e quindi aggiungere l'altezza del frammento che viene creato dinamicamente nella parte inferiore. Nel mio caso l'elemento statico era il PagerTitleStrip, che dovevo anche Sovrascrivere per abilitare l'uso di match_parent per la larghezza in modalità orizzontale.

ecco il mio prendere sul codice da Delyan:

public class WrappingViewPager extends ViewPager { 

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

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
    // super has to be called in the beginning so the child views can be 
    // initialized. 
    super.onMeasure(widthMeasureSpec, heightMeasureSpec); 

    if (getChildCount() <= 0) 
     return; 

    // Check if the selected layout_height mode is set to wrap_content 
    // (represented by the AT_MOST constraint). 
    boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) 
      == MeasureSpec.AT_MOST; 

    int width = getMeasuredWidth(); 

    View firstChild = getChildAt(0); 

    // Initially set the height to that of the first child - the 
    // PagerTitleStrip (since we always know that it won't be 0). 
    int height = firstChild.getMeasuredHeight(); 

    if (wrapHeight) { 

     // Keep the current measured width. 
     widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); 

    } 

    int fragmentHeight = 0; 
    fragmentHeight = measureFragment(((Fragment) getAdapter().instantiateItem(this, getCurrentItem())).getView()); 

    // Just add the height of the fragment: 
    heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + fragmentHeight, 
      MeasureSpec.EXACTLY); 

    // super has to be called again so the new specs are treated as 
    // exact measurements. 
    super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
} 

public int measureFragment(View view) { 
    if (view == null) 
     return 0; 

    view.measure(0, 0); 
    return view.getMeasuredHeight(); 
}} 

E il PagerTitleStrip personalizzato:

public class MatchingPagerTitleStrip extends android.support.v4.view.PagerTitleStrip { 

public MatchingPagerTitleStrip(Context arg0, AttributeSet arg1) { 
    super(arg0, arg1); 

} 

@Override 
protected void onMeasure(int arg0, int arg1) { 

    int size = MeasureSpec.getSize(arg0); 

    int newWidthSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); 

    super.onMeasure(newWidthSpec, arg1); 
}} 

Cheers!

+0

java.lang.StackOverflowError –

+0

Questo codice è stato testato su un sistema operativo ancora in esecuzione su Android 4.2 e 4.3. Potresti per favore approfondire dove ottieni l'errore? – Vladislav

+0

lo sto verificando in 5.0 puoi anche controllarlo –

3

Se setTag (posizione) nel instantiateItem del vostro PageAdapter:

@Override 
public Object instantiateItem(ViewGroup collection, int page) { 
    LayoutInflater inflater = (LayoutInflater) context 
      .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
    View view = (View) inflater.inflate(R.layout.page_item , null); 
    view.setTag(page); 

quindi può recuperare la vista (pagina della adattatore) con un'OnPageChangeListener, misurarla, e ridimensionare il vostro ViewPager:

private ViewPager pager; 
@Override 
protected void onCreate(Bundle savedInstanceState) { 
    pager = findViewById(R.id.viewpager); 
    pager.setOnPageChangeListener(new SimpleOnPageChangeListener() { 
     @Override 
     public void onPageSelected(int position) { 
      resizePager(position); 
     } 
    }); 

    public void resizePager(int position) { 
     View view = pager.findViewWithTag(position); 
     if (view == null) 
      return; 
     view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 
     int width = view.getMeasuredWidth(); 
     int height = view.getMeasuredHeight(); 
      //The layout params must match the parent of the ViewPager 
     RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width , height); 
     pager.setLayoutParams(params); 
    } 
} 
0

Con riferimento alle soluzioni di cui sopra, aggiunto qualche altra istruzione per ottenere l'altezza massima della vista del cercapersone figlio.

Fare riferimento al codice riportato di seguito.

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
    // super has to be called in the beginning so the child views can be 
    // initialized. 
    super.onMeasure(widthMeasureSpec, heightMeasureSpec); 

    if (getChildCount() <= 0) 
     return; 

    // Check if the selected layout_height mode is set to wrap_content 
    // (represented by the AT_MOST constraint). 
    boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST; 

    int width = getMeasuredWidth(); 

    int childCount = getChildCount(); 

    int height = getChildAt(0).getMeasuredHeight(); 
    int fragmentHeight = 0; 

    for (int index = 0; index < childCount; index++) { 
     View firstChild = getChildAt(index); 

     // Initially set the height to that of the first child - the 
     // PagerTitleStrip (since we always know that it won't be 0). 
     height = firstChild.getMeasuredHeight() > height ? firstChild.getMeasuredHeight() : height; 

     int fHeight = measureFragment(((Fragment) getAdapter().instantiateItem(this, index)).getView()); 

     fragmentHeight = fHeight > fragmentHeight ? fHeight : fragmentHeight; 

    } 

    if (wrapHeight) { 

     // Keep the current measured width. 
     widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); 

    } 

    // Just add the height of the fragment: 
    heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + fragmentHeight, MeasureSpec.EXACTLY); 

    // super has to be called again so the new specs are treated as 
    // exact measurements. 
    super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
} 
+0

Il metodo measureFragment (View) non è definito per il tipo HeightWrappingViewPager –

0

meglio il cambiamento

height = firstChild.getMeasuredHeight(); 

a

height = firstChild.getMeasuredHeight() + getPaddingTop() + getPaddingBottom();