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,但即使imageview仅为200dp,寻呼机仍会填满整个屏幕。我试图将寻呼机的高度替换为“ 200”,但这在多种分辨率下给了我不同的结果。我无法将“ dp”添加到该值。如何在寻呼机的布局中添加200dp?


Answers:


408

ViewPager如下重写onMeasure 将使其达到当前拥有的最大孩子的身高。

@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仅将其实际子项的最大尺寸调整为最大,即仅当前可见的项和直接相邻的子项。在ViewPager上调用setOffscreenPageLimit(子级总数)可以解决此问题,并导致ViewPager的大小设置为所有项的最大值,并且从不调整大小。 2. WebView在尝试对其进行度量时存在一些奇怪的问题。加载某些内容后在WebView上调用requestLayout()可以解决此问题。
0101100101 2014年

3
我要解决的只是一个小问题:如果viewPager对GONE具有可见性,并且将其设置为visible,则在创建其片段之前会调用onMeasure。这样最终高度将为0。如果有人有主意,欢迎他。我想我会在片段创建时进行回调
edoardotognoni

4
如果您有装饰子视图,这将不起作用-这是因为ViewPager.onMeasure()测量装饰视图并先为其分配空间,然后将其余空间分配给非装饰子视图。尽管如此,这是迄今为止到目前为止最不正确的解决方案,因此我表示赞成;)
本杰明·多贝尔

3
每当我使用ViewPager时,我都会一直回到这个话题
ono

7
在ViewPager上已执行setAdapter()时,getChildCount()可能返回0!实际的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"/>

优点:这种方法允许在包括RelativeLayout在内的任何布局中使用ViewPager来覆盖其他ui元素。

缺点之一仍然是:如果要使用边距,则必须创建两个嵌套布局,并为内部布局提供所需的边距。

这是代码:

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
当viewpager销毁并再次打开时,其他人在当前项目旁边得到空白页吗?
Zyoo 2014年

1
我也有空白页。
aeren 2014年

10
你只需要合并这个问题的两个顶级的答案在我的博客中描述:pristalovpavel.wordpress.com/2014/12/26/...
阿尼尔

4
只需用“ DanielLópezLacalle”给出的答案替换“ onMeasure”方法的代码即可。
Yog Guru

1
大..!为我工作.. @cybergen非常感谢您保存我的一天..!
Dnyanesh M

59

我的答案基于DanielLópezLacalle和这篇文章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 '16

为什么在这个ViewPager图像actualy比那些在使用相同的ImageView的短scaleType,同样,layout_width=match_parent还有layout_height=wrap_content?那里缺少20dp。
鲨鱼

鲨鱼,我真的不确定。这可能与您的比例类型的实际作用有关。可能想尝试设置高度。
MinceMan '16

1
我无法相信弗里金!我花了两天时间将自定义viewpager粘在一起,陷入一个问题,当时我的初始视图无法显示,我只是想不通为什么!// 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

根据您的需要,您可以在自己的xml文件中创建ViewPager,并将layout_height设置为200dp,然后在您的代码中,而不是从头开始创建新的ViewPager,您可以为该xml文件充气:

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

3
好的答案,有点烦人的默认行为是“做一些难以理解的事情”。感谢您的解释。
克里斯·范德维尔德

8
@ChrisVandevelde,这似乎是某些android库的常见租户。一旦学习了基础知识,您就会意识到没有任何基础知识
CQM 2013年

1
但是@Jave,为什么每次加载其子级时viewpager都不能调整其高度?
2014年

确实是@CQM!将ViewPagerIndicator库与layout_height设置为时存在相同的问题wrap_content,但由于将其设置为固定数量的简单变通办法不起作用,因此情况更糟。
Giulio Piancastelli 2014年

20

使用DanielLópezLocalle的答案,我在Kotlin中创建了此类。希望它可以节省更多时间

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

我已经在几个项目中遇到过这个问题,但是从来没有一个完整的解决方案。因此,我创建了一个WrapContentViewPager github项目,以替代ViewPager。

https://github.com/rnevet/WCViewPager

该解决方案的灵感来自此处的一些答案,但在以下方面有所改进:

  • 根据当前视图动态地更改ViewPager的高度,包括滚动时。
  • 考虑“装饰”视图(如PagerTabStrip)的高度。
  • 考虑所有填充。

更新了支持库版本24,该版本破坏了先前的实现。


@mvai您可以打开一个问题,还是派发它并修改示例应用程序?
拉南2015年

1
我发现RecyclerView也有一些wrap_content问题;如果您使用像这样的自定义LinearLayoutManager,则此方法有效。因此,您的库没有任何问题。
natario 2015年

1
仍然需要修复的是它与FragmentStatePagerAdapter一起使用。看起来像是在布置碎片之前测量孩子,因此可以减小高度。对我有用的是@logan的答案,尽管我仍在努力。您可能想尝试将该方法合并到您的库中。我不熟悉github,对不起。
natario 2015年

谢谢,我来研究一下。
拉南2015年

1
对于想知道如何使用FragmentPagerAdapter进行工作的任何人,请使您的适配器通过在内部保留一个Fragments列表来实现ObjectAtPositionInterface,以便它可以从getObjectAtPosition方法返回相应的Fragment。
巴勃罗(Pablo)

15

我只是碰到了同样的问题。我有一个ViewPager,我想在它的按钮上显示一个广告。我发现的解决方案是将传呼机放入RelativeView并将其layout_above设置为我想在其下面看到的视图ID。对我有用。

这是我的布局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 ”。
Martin Marconcini

2
您的问题根本不一样。将ViewPager设置为match_parent时,您的布局可以正常工作-OP遇到一种情况,他希望ViewPager包装到其内容。
k2col '16

9

我也遇到了这个问题,但对我来说我有一个FragmentPagerAdapter这是供应ViewPager用其网页。我的问题是,onMeasure()ViewPager任何被调用前Fragments已经建立(因此不能大小本身正确)。

有点试验和错误之后,我发现finishUpdate()后的FragmentPagerAdapter的方法被调用Fragments(从已初始化instantiateItem()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()(否则它不会重绘视图)。

我也忘记将父母的填充物添加到最终高度。

我还放弃了保留原始的宽度/高度MeasureSpecs,而是根据需要创建一个新的宽度/高度MeasureSpecs。相应地更新了代码。

我遇到的另一个问题是,它在a中无法正确调整大小,ScrollView并发现罪魁祸首是用MeasureSpec.EXACTLY而不是来测量孩子MeasureSpec.UNSPECIFIED。更新以反映这一点。

这些更改已全部添加到代码中。您可以查看历史记录,以查看旧的(不正确的)版本。


为什么不将您忘记的代码添加到代码中。
哈桑

@hasan我已经做过,对您造成的任何困惑感到抱歉!也会更新答案,说出答案
logan 2014年

太棒了!很高兴:)
logan 2014年

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

where mPages是内部PageInfo动态添加到的结构的内部列表,PagerAdapter并且CustomImageView只是ImageView使用overrided onMeasure()方法(根据指定的宽度设置其高度并保持图像长宽比)的常规规则。

您可以ViewPagersetPrimaryItem()方法中强制高度:

@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不更新显示页面(将其显示为空白)的恼人错误,当上一页的高度为零(即中的null可绘制CustomImageView)时,每个奇数在两页之间来回滑动。


在我看来,这是正确的方法,但是我需要广告item.mImageView.measure(..)以获取getMeasuredXXX()方法中的正确尺寸。
Gianluca P.

6

当在viewpager内使用静态内容并且您不希望花哨的动画时,可以使用以下view pager

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

但是,采用第一个要素可能是错误的。
Tobias Reich

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

从爆米花时代的android应用程序的源代码中,我发现了此解决方案,该解决方案根据当前子项的大小动态调整具有漂亮动画的viewpager的大小。

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

改进的DanielLópezLacalle答案,用Kotlin重写:

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环绕其内容。使用Cyber​​gen的上述答案,我将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个自定义ViewPagers ScrollView。它们的顶部是根据长宽比测量的,其余的只是纵横比layout_height=wrap_content。我试过cybergen丹尼尔·洛佩斯拉卡列的解决方案。他们都不适合我。

我猜为什么Cyber​​gen无法在> 1的页面上工作,是因为它基于1来计算寻呼机的高度,如果您进一步滚动,它将被隐藏。

无论cybergen丹尼尔·洛佩斯拉卡列的建议有怪异的行为在我的情况: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否则您可以得到height = 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

对于有此问题并使用C#为Xamarin Android进行编码的人,这也可能是一个快速的解决方案:

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

如果您的子视图高度相同,这主要有用。否则,将需要您在要检查的所有子级上存储某种“ minimumHeight”值,即使这样,您可能也不想在较小的子级视图下看到空白。

虽然解决方案本身对我来说还不够,但这似乎是因为我的子项是listViews,并且它们的MeasuredHeight计算不正确。


这对我有用。我在viewpager中的所有子视图都具有相同的高度。
德米特里

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团队为什么不能在其代码库中使用它呢?
Mohanakrrishna

这是您必须根据自己的需要进行自定义的事情之一,谷歌还在2019年的Google I / O中引入了viewPager2,并取代了旧的ViewPager(后者于2011年创建),实现为'androidx.viewpager2:viewpager2 :1.0.0-alpha04'
霍山哈桑

2

我编辑了cybergen的答案,使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

@rosuh您何时遇到此问题?我仅在带有ViewPager的TabLayout中使用了此功能,所以我不确定它是否在所有地方都可以正常使用
geggiamarti

@geggiamarti问题是某些页面将被回收。并在用户向其滑动时重新创建,因此measure将被多次调用。它可能会增加高度列表。另一种情况是用户可以为此viewPager手动调用requestLayout(或setLayoutParams方法,就像您所做的一样),也会多次测量。
rosuh

1

如果ViewPager您使用的是ScrollView ANDPagerTitleStrip孩子,并且您有一个孩子,则需要对已经提供的重要答案稍加修改。供参考,我的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,你必须添加的的是measuredHeight PagerTitleStrip如果发现。否则,即使它占用了额外的空间,它的高度也不会被视为所有孩子的最大身高。

希望这对其他人有帮助。抱歉,这有点hack ...

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,并且如果ScrollView的内容不需要对话框可用的全部高度,则该页面(以及整个对话框)应为WRAP_CONTENT。

现有答案中没有一个完全适用于此特定方案。等等-这是一个坎bump的旅程。

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

我在我的情况下添加android:fillViewport =“ true”解决了该问题


0

就我而言,在应用尺寸时,我需要一个带有wrap_content的viewpager用于当前选择的元素和动画。在下面您可以看到我的实现。有人可以派上用场吗?

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(View):

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带有各种新功能。当然,“换行内容”问题已由ViewPager2解决。

来自Android文档:“ ViewPager2取代了ViewPager,解决了其前任的大部分难题,包括从右到左的布局支持,垂直方向,可修改的Fragment集合等。”

我向初学者推荐这篇文章:

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


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.