我已经设置了一个简单的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添加到寻呼机的布局?
以上建议对我都没用。我的用例是在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的高度。我不需要它,但如果你提出解决方案,我很乐意更新答案。
并非所有答案都完美无缺。所以我创建了一个。下面的类将请求布局时,一个新的页面被选择,使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)
}
}
我的答案基于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。
对于有此问题并使用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调整它的大小到每个子,而不仅仅是最大的一个,我写了一段代码来做这件事。注意,在这个变化上没有动画(在我的例子中没有必要)
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();
}
});
}
}
给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