2012-02-16 4 views
16

У меня есть пользовательская группа ViewGroup, у которой есть ребенок ViewPager. ViewPager питается от PagerAdapter, который предоставляет LinearLayoutViewPager, который имеет LayoutParams от WRAP_CONTENT как по высоте, так и по ширине.Измерение ViewPager

Вид отображается правильно, но когда на ViewPager вызывается метод child.measure(), он не возвращает фактические размеры LinearLayout, но, похоже, заполняет все оставшееся пространство.

Любые идеи, почему это происходит и как его изменить?

+0

пожалуйста звезда вопрос https://code.google.com/p/android/issues/detail?id=54604 – Christ

+0

FYI: Вчера вопрос о выпуске 54604 был закрыт Google. Если у вас все еще есть проблема, я предлагаю вам открыть новую проблему и поместить сюда ссылку. – Christ

+0

Возможный дубликат [Android: я не могу иметь ViewPager WRAP \ _CONTENT] (http://stackoverflow.com/questions/8394681/android-i-am-unable-to-have-viewpager-wrap-content) – Raanan

ответ

54

Я был не очень доволен принятый ответ (равно как и решение с предварительным раздуванием всех представлений в комментариях), поэтому я собрал ViewPager, который занимает высоту от первого доступного дочернего элемента. Он делает это, выполняя второй шаг измерения, позволяя вам украсть высоту первого ребенка.

Лучшим решением было бы сделать новый класс внутри android.support.v4.view пакет, который реализует улучшенную версию onMeasure (с доступом к пакету видимых методов, как populate())

За время, хотя, решение ниже мне подходит.

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

Это спасло мне много головных болей - большое спасибо! – boz

+0

Почему ваш HeightWrappingViewPager не работает внутри ScrollView с android: fillViewport = "true"? Когда я заменяю ViewPager на HeightWrappingViewPager, я не могу прокручивать содержимое в ScrollView – Ziem

+0

@ Ziem, я понятия не имею. Работает ли обычный ViewPager? Я был бы очень удивлен, если ScrollView изменит свое поведение на основе размеров внутреннего ребенка. – Delyan

11

Глядя на внутренности класса ViewPager в банке совместимости:

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

    ... 
} 

Казалось бы, что реализация ViewPager не измеряет мнения детей, а просто устанавливает ViewPager быть один стандартный вид основан на том, что родитель переходит. Когда вы проходите wrap_content, поскольку пейджер представления фактически не измеряет его содержимое, он занимает всю доступную область.

Моей рекомендацией было бы установить статический размер на вашем ViewPager в зависимости от размера ваших представлений для детей. Если это невозможно (например, представления для детей могут отличаться), вам нужно будет либо выбрать максимальный размер, либо заняться дополнительным пространством в некоторых представлениях, либо расширить ViewPager и предоставить onMeasure, которые измеряют детей. Одна из проблем, с которой вы столкнетесь, заключается в том, что пейджер представления был разработан так, чтобы он не менялся по ширине, когда показывались разные виды, поэтому вы, вероятно, будете вынуждены выбирать размер и оставаться с ним

+0

I попытался расширить класс ViewPager и переопределить onMeasure, но, похоже, что ViewPager не имеет дочерних элементов, getChildCount() возвращает 0. Вы знаете, почему это может быть? –

+0

Похоже, у существующего onMeasure есть вызов для заполнения(), прежде чем он получит детей. Тебе тоже придется это делать. –

+1

Еще раз спасибо ... populate() был закрыт, так что работать не удалось, но я нашел способ передать EXACT MeasureSpec в ViewPager и заставить его работать. –

0

Следуя приведенному выше примеру, я обнаружил, что измерение высоты детских представлений не всегда возвращает точные результаты. Решение состоит в том, чтобы измерить высоту любых статических представлений (определенных в xml), а затем добавить высоту фрагмента, динамически созданного внизу. В моем случае статическим элементом был PagerTitleStrip, который мне также пришлось переопределить, чтобы включить использование match_parent для ширины в ландшафтном режиме.

Так вот мой взгляд на код из Делян:

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

И обычай PagerTitleStrip:

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

Ура!

+0

java.lang.StackOverflowError –

+0

Этот код протестирован еще на Android 4.2 и 4.3. Не могли бы вы рассказать о том, где вы получили сообщение об ошибке? – Vladislav

+0

Я проверяю его в 5.0, вы также можете его проверить –

3

Если вы SetTag (позиция) в instantiateItem вашего 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); 

затем может получить вид (страницы адаптера) с OnPageChangeListener, измерить его и изменить размер 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

С ссылкой на приведенные выше решения добавлено еще несколько инструкций, чтобы получить максимальную высоту представления пейджера.

См. Приведенный ниже код.

@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

Метод measureFragment (View) не определен для типа HeightWrappingViewPager –

0

лучше изменить

height = firstChild.getMeasuredHeight(); 

в

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