Android:我无法拥有ViewPager WRAP_CONTENT

我已经设置了一个简单的ViewPager,它在每个页面上都有一个高度为200dp的ImageView。

这是我的寻呼机:

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添加到寻呼机的布局?

128852 次浏览

我只是在回答一个非常类似的问题,在寻找一个链接来支持我的说法时,碰巧发现了这个,你很幸运:)

< p > 我的另一个回答是: < br > ViewPager不支持wrap_content,因为它(通常)不会同时加载它的所有子元素,因此不能获得适当的大小(选项是有一个分页器,每次你切换页面时都会改变大小) 然而,你可以设置一个精确的尺寸(例如150dp), match_parent也可以 你也可以通过改变LayoutParams.

中的__abc0属性,从你的代码中动态修改维度

满足你的需要你可以在它自己的xml文件中创建ViewPager, layout_height设置为200dp,然后在你的代码中,而不是从头创建一个新的ViewPager,你可以扩展这个xml文件:

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

我也遇到了同样的问题。我有一个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>

另一个解决方案是根据PagerAdapter中的当前页高度更新ViewPager的高度。假设你这样创建你的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是动态添加到PagerAdapterPageInfo结构的内部列表,而CustomImageView只是常规的ImageView,带有重写的onMeasure()方法,该方法根据指定的宽度设置其高度并保持图像纵横比。

你可以在setPrimaryItem()方法中强制ViewPager高度:

@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中可绘制null),每次在两页之间来回滑动。

另一个更通用的解决方案是让wrap_content正常工作。

我扩展了ViewPager来覆盖onMeasure()。高度被包裹在第一个子视图周围。如果子视图的高度不完全相同,这可能会导致意想不到的结果。因此,类可以很容易地扩展,让我们说动画到当前视图/页面的大小。但我不需要。

你可以在yout XML布局中使用这个ViewPager,就像原来的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;
}


}

我遇到了同样的问题,当用户在页面之间滚动时,我还必须让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显示的当前页面的高度。

重写ViewPager的onMeasure将使它获得当前拥有的最大子结点的高度。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);


int height = 0;
int childWidthSpec = MeasureSpec.makeMeasureSpec(
Math.max(0, MeasureSpec.getSize(widthMeasureSpec) -
getPaddingLeft() - getPaddingRight()),
MeasureSpec.getMode(widthMeasureSpec)
);
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(childWidthSpec, MeasureSpec.UNSPECIFIED);
int h = child.getMeasuredHeight();
if (h > height) height = h;
}
    

if (height != 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}


super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

我也遇到了这个问题,但在我的例子中,我有一个FragmentPagerAdapter,它向ViewPager提供它的页面。我遇到的问题是ViewPageronMeasure()在任何Fragments被创建之前被调用(因此不能正确地调整自己的大小)。

经过一些尝试和错误,我发现FragmentPagerAdapter的finishUpdate()方法在Fragments被初始化之后(从FragmentPagerAdapter中的instantiateItem()开始)被调用,并且在页面滚动之后/期间被调用。我做了一个小界面:

public interface AdapterFinishUpdateCallbacks
{
void onFinishUpdate();
}

我把它传递给我的FragmentPagerAdapter并调用:

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


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

这反过来允许我在我的CustomViewPager实现上调用setVariableHeight():

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

我不确定这是最好的方法,如果你认为它是好的/坏的/邪恶的,我会喜欢评论,但它似乎在我的实现中工作得很好:)

希望这能帮助到一些人!

我忘记在调用super.measure()后添加requestLayout()(否则它不会重绘视图)。

我还忘记在最终高度中添加父元素的填充。

我还放弃了保留原来的宽度/高度度量,以便根据需要创建一个新的度量。已更新相应的代码。

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

这些更改都已添加到代码中。如果需要,您可以检查历史记录以查看旧的(不正确的)版本。

我的答案基于Daniel López Lacalle和这篇文章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。

以上建议对我都没用。我的用例是在ScrollView中有4个自定义ViewPagers。顶部是基于纵横比测量的,其余部分只有layout_height=wrap_content。我已经尝试过cybergen丹尼尔López拉卡勒解决方案。没有一个是完全适合我的。

我猜为什么cybergen不能在页面> 1上工作是因为它基于第1页计算页面的高度,如果你进一步滚动,这是隐藏的。

在我的例子中,cybergen丹尼尔López拉卡勒的建议都有奇怪的行为:3个中的2个加载ok, 1个随机高度为0。似乎onMeasure在子节点被填充之前被调用。所以我想出了这两个答案加上我自己的修正:

@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的布局参数中保存第一页的计算高度。不要忘记将fragment的布局高度设置为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的高度。我不需要它,但如果你提出解决方案,我很乐意更新答案。

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

对于有此问题并使用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似乎没有正确计算。

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

https://github.com/rnevet/WCViewPager

解决方案的灵感来自这里的一些答案,但改进了:

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

为支持库版本24更新,打破了以前的实现

如果你正在使用的ViewPagerScrollView的子函数,有一个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中,你必须添加 PagerTitleStrip的measuredHeight(如果找到的话)。否则,即使它占用了额外的空间,它的高度也不会被认为是所有子结点的最大高度。

希望这能帮助到其他人。很抱歉,这有点黑…

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

从爆米花时间安卓应用程序的源代码,我发现这个解决方案,动态调整大小的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);
}
}

当在viewpager中使用静态内容时,你不希望有花哨的动画,你可以使用下面的viewpager

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

我有一个版本的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);
}
我有一个类似的(但更复杂的场景)。我有一个对话框,其中包含一个ViewPager.
其中一个子页面是短的,具有静态高度 另一个子页面应该总是尽可能高 另一个子页面包含一个ScrollView,如果ScrollView内容不需要对话框的完整高度,那么该页面(以及整个对话框)应该WRAP_CONTENT

现有的答案都不完全适用于这个特定的场景。坚持住,这是一段颠簸的旅程。

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无法滚动。

我发现了一个解决方案,它有点像这里提到的一些解决方案的合并。

其思想是测量ViewPager的当前视图。

以下是完整的代码:

MainActivity.kt

class MainActivity : AppCompatActivity() {


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewPager.adapter = WrapHeightViewPager.CustomPagerAdapter(this)
}
}

activity_main.xml

<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">


<com.android.myapplication.WrapHeightViewPager
android:layout_width="match_parent" android:id="@+id/viewPager"
android:background="#33ff0000"
android:layout_height="wrap_content"/>


</FrameLayout>

view_pager_page.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content">


<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:src="@android:drawable/sym_def_app_icon"/>




<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/textView"/>
</LinearLayout>

WrapHeightViewPager.kt

class WrapHeightViewPager : ViewPager {
constructor(context: Context) : super(context) {}


constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {}


override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val adapter = adapter as CustomPagerAdapter?
val currentView = adapter?.currentView
if (currentView != null) {
currentView.measure(widthMeasureSpec, heightMeasureSpec)
super.onMeasure(
widthMeasureSpec,
View.MeasureSpec.makeMeasureSpec(currentView.measuredHeight, View.MeasureSpec.EXACTLY)
)
return
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}


class CustomPagerAdapter(private val context: Context) : PagerAdapter() {
var currentView: View? = null


override fun instantiateItem(parent: ViewGroup, position: Int): Any {
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.view_pager_page, parent, false)
view.textView.text = "item$position"
parent.addView(view)
return view
}


override fun setPrimaryItem(container: ViewGroup, position: Int, obj: Any) {
super.setPrimaryItem(container, position, obj)
currentView = obj as View
}


override fun destroyItem(collection: ViewGroup, position: Int, view: Any) = collection.removeView(view as View)


override fun getCount(): Int = 3


override fun isViewFromObject(view: View, obj: Any) = view === obj


override fun getPageTitle(position: Int): CharSequence? = "item $position"


}
}

如果你使用< >强RecyclerPagerAdapter图书馆< / >强,获取"currentView"的方法是从你设置的pager-view-holder中获取:

    val item = obj as PagerViewHolder
currentView = item.itemView

在我的例子中,添加clipToPadding解决了这个问题。

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

干杯!

使用Daniel López Localle answer,我在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)
}}

如果你需要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();
}
});
}
}

我的情况下添加android:fillViewport="true"解决了这个问题

我在这里看到的大多数解决方案似乎都在做双重测量:首先测量子视图,然后调用super.onMeasure()

我提出了一个自定义的WrapContentViewPager是更有效的,与RecyclerView和Fragment工作得很好

你可以在这里查看演示:

github/ssynhtn/WrapContentViewPager .

和这里类的代码: WrapContentViewPager.java < / p >

在我的例子中,当应用大小时,我需要一个带有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" />

下面的代码是唯一对我有用的东西

1. 使用这个类来声明一个hightwrappingviewpager:

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

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

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


}


改进的丹尼尔López拉卡勒答案,在芬兰湾的科特林中重写:

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

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

这将拉伸滚动视图及其子内容以填充视口。

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

我编辑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"/>

如有必要请纠正我的英语

并非所有答案都完美无缺。所以我创建了一个。当选择一个新页面以使viewPager的高度为当前子视图的高度时,下面的类将请求布局。

class WrapContentViewPager : ViewPager {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)


private var curPos = 0


init {
addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {}


override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {}


override fun onPageSelected(position: Int) {
curPos = position
requestLayout()
}
})
}


override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if (childCount == 0) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
return
}
measureChildren(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(measuredWidth, getChildAt(curPos).measuredHeight)
}
}

另一个Kotlin代码

class DynamicViewPager @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : ViewPager(context, attrs) {


override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var height = 0
(0 until childCount).forEach {
val child = getChildAt(it)
child.measure(
widthMeasureSpec,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
)
height = max(height, child.measuredHeight)
}
if (height > 0) {
super.onMeasure(
widthMeasureSpec,
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
)
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
}

对于那些想要ViewPager2的解决方案,将ViewPager2具有与所有页面的最大高度相同的高度,遗憾的是,我只找到了这个解决方案:

viewPager.doOnPreDraw {
//workaround to set the viewPagerheight the same as its children
var height = 0
for (i in 0 until featuresViewPager.adapter!!.itemCount) {
val viewHolder = viewPager.adapter!!.createViewHolder(viewPager, 0)
viewPager.adapter!!.bindViewHolder(viewHolder, i)
val child: View = viewHolder.itemView
child.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(viewPager.width, View.MeasureSpec.EXACTLY)
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
child.measure(widthMeasureSpec, heightMeasureSpec)
val childHeight = child.measuredHeight
child.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
if (childHeight > height)
height = childHeight
}
viewPager.layoutParams.height = height
}

我说“悲伤”;因为它遍历所有的页面,创建它们的视图,度量它们,并调用用于其他目的的函数。

应该在大多数情况下工作良好。

如果你知道更好的解决办法,请告诉我。