Измерение ViewPager - PullRequest
       1

Измерение ViewPager

17 голосов
/ 16 февраля 2012

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

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

Есть идеи, почему это происходит и как его исправить?

Ответы [ 6 ]

55 голосов
/ 20 февраля 2013

Я не был очень доволен принятым ответом (ни решением «предварительно раздуть все просмотры» в комментариях), поэтому я собрал 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);
        }
    }
}
11 голосов
/ 16 февраля 2012

Просмотр внутренних элементов класса 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, которое измеряет дочерние элементы. Одна из проблем, с которой вы столкнетесь, заключается в том, что пейджер вида не был изменен по ширине, поскольку отображаются разные виды, поэтому вам, вероятно, придется выбрать размер и остаться с ним

3 голосов
/ 01 марта 2014

Если вы установитеTag (положение) в 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 голосов
/ 22 января 2015

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

height = firstChild.getMeasuredHeight();

до

height = firstChild.getMeasuredHeight() + getPaddingTop() + getPaddingBottom();
0 голосов
/ 19 июня 2014

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

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

@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 голосов
/ 05 июня 2013

Следуя приведенному выше примеру, я обнаружил, что измерение высоты дочерних представлений не всегда дает точные результаты. Решение состоит в том, чтобы измерить высоту любых статических представлений (определенных в 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);
}}

ура!

...