Android: у меня не работает ViewPager WRAP_CONTENT


258

Я установил простой ViewPager, который имеет ImageView с высотой 200dp на каждой странице.

Вот мой пейджер:

pager = new ViewPager(this);
pager.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
pager.setBackgroundColor(Color.WHITE);
pager.setOnPageChangeListener(listener);
layout.addView(pager);

Несмотря на высоту, заданную как wrap_content, пейджер всегда заполняет экран, даже если просмотр изображения только 200dp. Я попытался заменить высоту пейджера на «200», но это дает мне разные результаты с несколькими разрешениями. Я не могу добавить «dp» к этому значению. Как добавить 200dp в макет пейджера?


1
пожалуйста, пометьте
Христос

Ответы:


408

Переопределение параметра «Измерить ваше» ViewPagerследующим образом позволит ему получить рост самого большого ребенка, которого он имеет в настоящее время.

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

24
Это ближе всего к тому, что мне нужно, но есть две вещи, которые нужно добавить: 1. ViewPager изменяет размер только для самого большого из его реальных потомков, то есть только видимого в данный момент элемента и непосредственно смежных. Вызов setOffscreenPageLimit (общее количество дочерних элементов) в ViewPager решает эту проблему и приводит к ViewPager, размер которого устанавливается равным наибольшему из всех его элементов и никогда не изменяет размер. 2. У WebViews есть некоторые странные проблемы при попытке их измерить. Вызов requestLayout () для WebView после загрузки чего-либо решает эту проблему.
0101100101,

3
Есть только небольшая проблема, которую я собираюсь исправить: если viewPager имеет видимость GONE, и вы устанавливаете его в visible, onMeasure вызывается до того, как его фрагмент создается. Так что в итоге он будет иметь высоту 0. Если у кого-то есть идея, он приветствуется. Я думаю, что я пойду с обратным вызовом, когда фрагмент будет создан
edoardotognoni

4
Это не будет работать, если у вас есть дочерние виды декорации - это потому, что ViewPager.onMeasure () измеряет декорационные виды и сначала выделяет им пространство, а затем отдает оставшуюся часть пространства дочерним элементам без декора. Тем не менее, это, безусловно, наименее неправильное решение здесь, так что я проголосовал;)
Бенджамин Добелл

3
Я продолжаю возвращаться к этому каждый раз, когда использую ViewPager
ono

7
getChildCount () может вернуть 0, пока вы уже выполнили setAdapter () в ViewPager! Фактический вызов populate () (который создает представления) происходит внутри super.onMeasure (widthMeasureSpec, heightMeasureSpec); вызов. Помещение дополнительного вызова super.onMeasure () в начале этой функции сделало свое дело. Также проверьте stackoverflow.com/questions/38492210/...
southerton

106

Другое более общее решение - wrap_contentпросто начать работать.

Я расширил, ViewPagerчтобы переопределить onMeasure(). Высота оборачивается вокруг первого дочернего вида. Это может привести к неожиданным результатам, если дочерние представления имеют разную высоту. Для этого класс можно легко расширить, скажем, до размера текущего представления / страницы. Но мне это не нужно.

Вы можете использовать этот ViewPager в своих XML-макетах так же, как и оригинальный ViewPager:

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

Преимущество: этот подход позволяет использовать ViewPager в любом макете, включая RelativeLayout, для наложения других элементов пользовательского интерфейса.

Остается один недостаток: если вы хотите использовать поля, вы должны создать два вложенных макета и дать внутреннему желаемые поля.

Вот код:

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

}

34
Кто-нибудь еще получил пустую страницу рядом с текущим элементом, когда просмотрщик уничтожен и открыт снова?
Zyoo

1
У меня тоже есть пустые страницы.
Aeren

10
Вам просто нужно объединить два главных ответа на этот вопрос, как описано в моем блоге: pristalovpavel.wordpress.com/2014/12/26/…
anil

4
Просто замените код метода onMeasure на ответ Даниэля Лопеса Лакалье.
Йог Гуру

1
Большой..! Работал на меня .. @cybergen Спасибо, ты спас мой день ..!
Днянешь М

59

Я основал свой ответ на Даниэле Лопесе Лакалье и на этом посте http://www.henning.ms/2013/09/09/viewpager-that-simply-dont-measure-up/ . Проблема с ответом Даниэля в том, что в некоторых случаях мои дети имели нулевой рост. Решение было, к сожалению, измерить дважды.

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

Это также позволяет вам установить высоту на ViewPager, если вы этого хотите, или просто wrap_content.


У меня была такая же проблема и я решил ее с вашим ответом, спасибо. Но какое-либо объяснение, почему?
Барт Бург

Я думаю, что они не намеревались поддерживать контент для переноса, так как я не думаю, что они думали, что это был нормальный вариант использования. Чтобы поддержать это, мы должны измерить себя после того, как наши дети будут измерены, чтобы мы могли обернуть содержимое.
MinceMan

Почему изображения в этом ViewPager на самом деле короче, чем изображения в ImageView, который использует то же scaleTypeи аналогично, layout_width=match_parentа также layout_height=wrap_content? там как 20dp там не хватает.
Акула

Акула, я действительно не уверен. Это может быть связано с тем, что на самом деле делает ваш тип шкалы. Могу попробовать установить высоту.
MinceMan

1
Я не могу поверить в это! Я потратил 2 дня, склеивая свой пользовательский видовой пейджер, и застрял в проблеме, когда мой первоначальный вид не появился, и я просто не мог понять, почему! // super has to be called in the beginning so the child views can be initialized.<----- Это была причина, пришлось вызывать ее в начале и в конце функции onMeasure. Yippiii, виртуальная пятерка на мне сегодня!
Звездная волна

37

Я просто отвечал на очень похожий вопрос по этому поводу, и случайно нашел его, когда искал ссылку для подтверждения своих утверждений, так что удачи вам :)

Мой другой ответ:
ViewPager не поддерживает, wrap_contentпоскольку у него (как правило) никогда не загружаются все его дочерние элементы одновременно, и поэтому он не может получить соответствующий размер (можно использовать пейджер, который меняет размер при каждом переключении страница).

Тем не менее, вы можете установить точный размер (например, 150dp) и match_parentработает также.
Вы также можете динамически изменять размеры из своего кода, изменив height-attribute в его LayoutParams.

Для ваших нужд вы можете создать ViewPager в своем собственном xml-файле с значением layout_height, равным 200dp, а затем в своем коде, вместо создания нового ViewPager с нуля, вы можете надуть этот xml-файл:

LayoutInflater inflater = context.getLayoutInflater();
inflater.inflate(R.layout.viewpagerxml, layout, true);

3
Хороший ответ, немного раздражающий, что поведение по умолчанию - «сделать что-то непонятное». Спасибо за объяснение.
Крис Вандевельде

8
@ChrisVandevelde, кажется, это обычный арендатор некоторых библиотек Android. Как только вы изучаете основы, вы понимаете, что за ними ничего не следует
CQM

1
Но @Jave, почему просмотрщик не может регулировать свою высоту каждый раз, когда загружаются его дочерние элементы?
Diffy

@ CQM действительно! Библиотека ViewPagerIndicator имеет ту же проблему с layout_heightустановленным значением wrap_content, но это еще хуже, поскольку простой обходной путь для установки ее на фиксированную величину не работает.
Джулио Пьянкастелли

20

Используя ответ Daniel López Localle , я создал этот класс в Котлине. Надеюсь, это сэкономит вам больше времени

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

16

Я уже сталкивался с этой проблемой в нескольких проектах и ​​никогда не имел полного решения. Поэтому я создал проект github WrapContentViewPager в качестве замены ViewPager на месте.

https://github.com/rnevet/WCViewPager

Решение было вдохновлено некоторыми ответами здесь, но улучшается:

  • Динамически изменяет высоту ViewPager в соответствии с текущим View, в том числе при прокрутке.
  • Принимает во внимание высоту «декора» просмотров, таких как PagerTabStrip.
  • Принимает во внимание все Padding.

Обновлен для поддержки библиотеки версии 24, которая сломала предыдущую реализацию.


@mvai. Можете ли вы открыть проблему или разложить ее и изменить пример приложения?
Раанан

1
Я обнаружил, что у RecyclerView также есть некоторые проблемы с wrap_content; это работает, если вы используете пользовательский LinearLayoutManager, как это . Так что ничего плохого в вашей библиотеке.
natario

1
Что еще нужно исправить, так это его использование с FragmentStatePagerAdapter. Похоже, он измеряет дочерние элементы до того, как фрагменты выложены, тем самым давая меньшую высоту. Для меня сработал ответ @logan, хотя я все еще работаю над этим. Вы можете попробовать объединить этот подход с вашей библиотекой. Я не знаком с GitHub, извините.
natario

Спасибо, я посмотрю на это.
Раанан

1
Для всех, кто интересуется, как заставить это работать с FragmentPagerAdapter, сделайте так, чтобы ваш адаптер реализовал ObjectAtPositionInterface, сохранив внутренний список фрагментов, чтобы он мог вернуть соответствующий фрагмент из метода getObjectAtPosition.
Пабло

15

Я просто столкнулся с той же проблемой. У меня был ViewPager, и я хотел показать объявление на кнопке. Решение, которое я нашел, состояло в том, чтобы вставить пейджер в RelativeView и установить его layout_above с идентификатором представления, который я хочу видеть под ним. это сработало для меня.

вот мой макет XML:

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

4
просто для справки вам не нужны xmlns: android = " schemas.android.com/apk/res/android " в обоих, только в первом.
Мартин Маркончини

2
Ваша проблема не была такой же. Ваш макет работает нормально, если ViewPager установлен в match_parent - у OP была ситуация, когда он хотел, чтобы ViewPager перенес его содержимое.
k2col

9

Я также столкнулся с этой проблемой, но в моем случае у меня была страница, FragmentPagerAdapterкоторая снабжала ViewPagerее страницами. Проблема, с которой я столкнулся, заключалась в том, что onMeasure()метод ViewPagerвызывался до того, как какой-либо из Fragmentsних был создан (и поэтому не мог правильно определить размер).

После нескольких проб и ошибок я обнаружил, что finishUpdate()метод FragmentPagerAdapter вызывается после Fragmentsинициализации (из instantiateItem()in FragmentPagerAdapter), а также после / во время прокрутки страницы. Я сделал небольшой интерфейс:

public interface AdapterFinishUpdateCallbacks
{
    void onFinishUpdate();
}

который я передаю в мой FragmentPagerAdapterи называю:

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

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

что в свою очередь позволяет мне обратиться setVariableHeight()к моей CustomViewPagerреализации:

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

Я не уверен, что это лучший подход, хотел бы услышать комментарии, если вы думаете, что это хорошо / плохо / зло, но, похоже, в моей реализации это работает довольно хорошо :)

Надеюсь, это поможет кому-то там!

РЕДАКТИРОВАТЬ: я забыл добавить requestLayout()после вызова super.measure()(в противном случае это не перерисовать представление).

Я также забыл добавить отступы родителя до конечной высоты.

Я также прекратил сохранять исходные параметры MeasureSpec ширины / высоты в пользу создания нового по мере необходимости. Обновили код соответствующим образом.

Еще одна проблема, с которой я столкнулся, заключалась в том, что он не мог правильно ScrollViewопределить размер в a и обнаружил, что виновник измерял ребенка MeasureSpec.EXACTLYвместо MeasureSpec.UNSPECIFIED. Обновлено, чтобы отразить это.

Все эти изменения были добавлены в код. Вы можете проверить историю, чтобы увидеть старые (неправильные) версии, если хотите.


Почему бы вам не добавить тех, кого вы забыли, в код, пожалуйста.
Хасан

@ Хасан Я уже сделал, извините за путаницу! Буду обновлять ответ, чтобы сказать, что также
логан

Потрясающие! Рад, что помогло :)
логан

8

Другое решение заключается в обновлении ViewPagerвысоты в соответствии с текущей высотой страницы PagerAdapter. Предполагая, что вы создаете свои ViewPagerстраницы таким образом:

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

Где mPagesвнутренний список PageInfoструктур, динамически добавляемый в PagerAdapterи CustomImageViewпросто обычный ImageViewс переопределенным onMeasure()методом, который устанавливает его высоту в соответствии с заданной шириной и сохраняет пропорции изображения.

Вы можете форсировать ViewPagerвысоту в setPrimaryItem()методе:

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

Обратите внимание Math.max(height, 1). Это исправляет досадную ошибку, ViewPagerкоторая не обновляет отображаемую страницу (показывает ее пустой), когда предыдущая страница имеет нулевую высоту (то есть нулевую отрисовку в CustomImageView), каждый нечетный пролистывание назад и вперед между двумя страницами.


Мне кажется, что это правильный путь, но мне нужно было добавить item.mImageView.measure(..)правильные измерения в getMeasuredXXX()методы.
Джанлука П.

6

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

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

Это отлично работает. Я расширил его, перебирая детей и выбирая ребенка с максимальной высотой.
Хавьер Мендонса

Прекрасно работает даже под видом переработчика
kanudo

Я получаю это исключение - java.lang.NullPointerException: попытка вызвать виртуальный метод «void android.view.View.measure (int, int)» для ссылки на пустой объект
PJ2104

Но первый элемент может быть неправильным.
Тобиас Райх

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

4

Исходя из исходного кода приложения Popcorn time для Android, я нашел это решение, которое динамически регулирует размер окна просмотра с приятной анимацией в зависимости от размера текущего потомка.

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

4

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

android: флаг minHeight также поддерживается.

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

Так что у этого адаптера есть огромная проблема, когда меняется количество элементов в адаптере
Jobbert

Вы можете уточнить свое заявление?
Phatee P

Этот код может вызвать нулевые указатели, так как не каждый дочерний элемент рассчитывается при запуске. Попробуйте раскладку вкладки и прокрутите от 1 до 5 или используйте код, и вы увидите это.
Jobbert

4

Улучшенный ответ Даниэля Лопеса Лакалье , переписанный в Котлине :

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

3

Я столкнулся с той же проблемой, и мне также пришлось заставить ViewPager обернуть его содержимое, когда пользователь прокручивал страницы. Используя приведенный выше ответ Cybergen, я определил метод onMeasure следующим образом:

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

Таким образом, метод onMeasure устанавливает высоту текущей страницы, отображаемой ViewPager.


С вашим ответом появляется только самый высокий контент, другой контент исчезает ...
Blaze Tama

2

Ничто из предложенного выше не помогло мне. Мой вариант использования имеет 4 пользовательских ViewPager в ScrollView. Верхняя часть из них измеряется в зависимости от соотношения сторон, а остальное просто имеет layout_height=wrap_content. Я пробовал киберген , Daniel López Lacalle . Никто из них не работает полностью для меня.

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

В моем случае предложения кибергена и Даниэля Лопеса Лакалля ведут себя странно: 2 из 3 загружены нормально, а 1 случайно высотой равно 0. Появляется, что onMeasureбыл вызван до того, как дети были заполнены. Итак, я придумал смесь этих 2 ответов + мои собственные исправления:

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

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

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

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


Не могли бы вы обновить свой ответ после всех этих лет? Поможет мне тонну
Денни

2

Для людей, имеющих эту проблему и программирующих для Xamarin Android в C #, это также может быть быстрым решением:

pager.ChildViewAdded += (sender, e) => {
    e.Child.Measure ((int)MeasureSpecMode.Unspecified, (int)MeasureSpecMode.Unspecified);
    e.Parent.LayoutParameters.Height = e.Child.MeasuredHeight;
};

Это в основном полезно, если ваши детские взгляды имеют одинаковую высоту. В противном случае вам потребуется хранить какое-то значение «минимума» по всем дочерним элементам, с которыми вы проверяете, и даже в этом случае вы можете не захотеть, чтобы пустые места были видны под вашими дочерними представлениями меньшего размера.

Однако само по себе решение не является достаточным для меня, но это потому, что мои дочерние элементы являются listViews, и их MeasuredHeight, похоже, вычисляется неправильно.


Это сработало для меня. Все мои дочерние виды в окне просмотра имеют одинаковую высоту.
Дмитрий

2

У меня есть версия WrapContentHeightViewPager, которая работала правильно до API 23, которая будет изменять размер высоты родительского представления в текущем выбранном дочернем представлении.

После обновления до API 23 он перестал работать. Оказывается, старое решение использовалось getChildAt(getCurrentItem())для получения текущего дочернего представления для измерения, которое не работает. Смотрите решение здесь: https://stackoverflow.com/a/16512217/1265583

Ниже работает с 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);
}

Спасибо!! Я пытался найти ответы в течение нескольких часов, и это единственный, который полностью работает для меня. Его необходимо объединить с настраиваемым адаптером, где setPrimaryItem () вызывает функцию в пейджере, которая вызывает requestLayout()так, что высота регулируется при переходе от одной вкладки к другой. Ты помнишь, почему superнужно звонить дважды? Я заметил, что иначе это не сработает.
M3RS

Работает с API 28.
Халид Лахани

2

Код ниже - единственное, что сработало для меня

1. Используйте этот класс, чтобы объявить 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. Вставьте пейджер просмотра переноса высоты в ваш xml-файл:

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

3. Объявите свой вид пейджер:

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

Спасибо. Это сработало. Но почему команда Android не может иметь это в своей базе кода?
Моханакришна

Это одна из вещей, которую вы должны настроить самостоятельно, зависит от ваших потребностей, также Google представила viewPager2 в этом 2019 году Google I / O и является заменой старого ViewPager, который был создан в 2011 году, реализация 'androidx.viewpager2: viewpager2 : 1.0.0-alpha04 '
Хоссам Хассан,

2

Я редактирую ответ cybergen для make viewpager, чтобы изменить высоту в зависимости от выбранного элемента. Класс такой же, как у cybergen, но я добавил Вектор целых чисел, который является высотой дочерних представлений всех viewpager, и мы можем получить к нему доступ при изменении страницы для обновления высоты.

Это класс:

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

Затем в вашей деятельности добавьте 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) {}
});

А вот и XML:

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

Пожалуйста, исправьте мой английский, если необходимо


Это имеет некоторые проблемы. heightsСписок может увеличиться в бесконечность.
Роз

@rosuh Когда вы столкнулись с проблемой? Я использовал это только в TabLayout с ViewPager, так что я не уверен, что он везде работает хорошо
geggiamarti

@geggiamarti Проблема в том, что некоторые страницы будут переработаны. И воссоздан, когда пользователь проведет по ним пальцем, поэтому measureон будет вызываться несколько раз. Это может увеличить список высот. Другая ситуация - пользователь может вызвать requestLayout(или setLayoutParamsметод, точно так же, как вы сделали) для этого viewPager вручную, а также будет измерять несколько раз.
Розу

1

Если ViewPagerвы используете дочерний элемент ScrollView AND, у которого есть PagerTitleStripдочерний элемент, вам нужно будет слегка изменить уже предоставленные отличные ответы. Для справки мой XML выглядит так:

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

В ваших onMeasureвы должны ADD в measuredHeight изPagerTitleStrip если один найден. В противном случае его высота не будет считаться самой большой из всех детей, даже если она занимает дополнительное место.

Надеюсь, это поможет кому-то еще. Извините, что это немного взломать ...

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

1

Кажется, что большинство решений, которые я вижу здесь, выполняют двойное измерение: сначала измеряют дочерние представления, а затем вызывают super.onMeasure()

Я пришел к заказу, WrapContentViewPagerкоторый является более эффективным, хорошо работает с RecyclerView и Fragment

Вы можете проверить демо здесь:

GitHub / ssynhtn / WrapContentViewPager

и код класса здесь: WrapContentViewPager.java


0

У меня похожий (но более сложный сценарий). У меня есть диалог, который содержит ViewPager.
Одна из дочерних страниц короткая, со статической высотой.
Другая дочерняя страница всегда должна быть максимально высокой.
Другая дочерняя страница содержит ScrollView, и страница (и, следовательно, весь диалог) должна WRAP_CONTENT, если содержимому ScrollView не нужна полная высота, доступная для диалога.

Ни один из существующих ответов не работал полностью для этого конкретного сценария. Держись - это ухабистая поездка.

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

Большое спасибо @Raanan за код для измерения видов и измерения высоты декора. Я столкнулся с проблемами с его библиотекой - анимация заикалась, и я думаю, что мой ScrollView не будет прокручиваться, когда высота диалога была достаточно короткой, чтобы этого потребовать.


0

в моем случае добавление clipToPaddingрешило проблему.

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

Ура!



0

В моем случае мне понадобился просмотрщик с wrap_content для выбранного в данный момент элемента и анимация при применении размера. Ниже вы можете увидеть мою реализацию. Может кому-нибудь пригодится.

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

Добавьте attrs.xml в проект:

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

И использовать:

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

0

Этот ViewPager изменяет размеры только для текущих видимых потомков (не самый большой из его реальных потомков)

Идея от 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;
}

}


0

Измерьте высоту 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;
    }

}

вызов setPrimaryView (Просмотр) :

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

}

0

Предоставить родительский макет ViewPager как 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>

Не забудьте установить android:fillViewport="true"

Это растянет scrollview и его дочернее содержимое, чтобы заполнить область просмотра.

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


0

Вы можете переключиться на ViewPager2. Это обновленная версия ViewPager. Он делает то же самое, что и ViewPager, но умнее и эффективнее. ViewPager2 поставляется с множеством новых функций. Конечно, проблема с Wrap Content была решена ViewPager2.

Из документов Android: «ViewPager2 заменяет ViewPager, решая большинство проблем его предшественника, включая поддержку разметки справа налево, вертикальную ориентацию, изменяемые коллекции фрагментов и т. Д.»

Я рекомендую эту статью для начинающих:

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


Эта проблема все еще существует, закажите файл editionetracker.google.com/u/0/issues/143095219
Сомеш Кумар
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.