Измерение ViewPager - программирование
Подтвердить что ты не робот

Измерение ViewPager

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

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

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

4b9b3361

Ответ 1

Я был не очень доволен принятым ответом (а также с пред-инфляционным представлением в комментариях), поэтому я собрал 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 
             * 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);
        }
    }
}

Ответ 2

Глядя на внутренности класса 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

Если вы установили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);
    }
}

Ответ 4

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

Итак, вот мой пример кода от 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();
}}

И пользовательский 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);
}}

Ура!

Ответ 5

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

Обратитесь к приведенному ниже коду.

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

Ответ 6

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

height = firstChild.getMeasuredHeight();

к

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