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


当前回答

我的答案基于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, Daniel López Lacalle解决方案。没有一个是完全适合我的。

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

cybergen和Daniel López Lacalle建议在我的情况下都有奇怪的行为: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的高度。我不需要它,但如果你提出解决方案,我很乐意更新答案。

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

https://github.com/rnevet/WCViewPager

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

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

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

另一个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)
        }
    }
}

重写你的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);
}

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

其思想是测量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