我已经设置了一个简单的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添加到寻呼机的布局?


当前回答

另一种解决方案是根据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是动态添加到PagerAdapter的PageInfo结构的内部列表,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)));
}

注意数学。这修复了ViewPager不更新显示的页面(显示为空白),当前一页的高度为零时(即CustomImageView中可绘制的空),在两页之间每次奇怪的来回滑动。

其他回答

我也遇到了这个问题,但在我的情况下,我有一个FragmentPagerAdapter,它为ViewPager提供它的页面。我遇到的问题是,ViewPager的onMeasure()在任何片段被创建之前被调用(因此不能正确地大小自己)。

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

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

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

希望这能帮助到一些人!

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

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

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

我遇到的另一个问题是,它不会在ScrollView中正确地调整自己的大小,并发现罪魁祸首是用MeasureSpec测量孩子。EXACTLY而不是measurespect . 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。

另一种解决方案是根据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是动态添加到PagerAdapter的PageInfo结构的内部列表,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)));
}

注意数学。这修复了ViewPager不更新显示的页面(显示为空白),当前一页的高度为零时(即CustomImageView中可绘制的空),在两页之间每次奇怪的来回滑动。

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

}

对于那些想要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
}

我说“可悲”是因为它遍历了所有的页面,创建了它们的视图,度量了它们,并在此过程中调用了用于其他目的的函数。

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

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