我有一个包含子ViewPager
的自定义ViewGroup。 ViewPager由PagerAdapter
提供,LinearLayout
向ViewPager
LayoutParams
提供WRAP_CONTENT
child.measure()
的身高和宽度。
视图显示正确,但在ViewPager上调用{{1}}方法时,它不返回LinearLayout的实际尺寸,但似乎填满了所有剩余空间。
为什么会发生这种情况以及如何修改它?
答案 0 :(得分:55)
我对接受的答案(以及评论中的pre-inflate-all-views解决方案)不是很满意,所以我整理了一个ViewPager
,它从第一个可用的孩子身上获得了它的高度。它通过执行第二次测量传递来实现此目的,允许您窃取第一个孩子的身高。
更好的解决方案是在android.support.v4.view
包中创建一个新类,该类实现更好的onMeasure
版本(可以访问包括populate()
等包的可见方法
但目前,下面的解决方案很适合我。
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);
boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec)
== MeasureSpec.AT_MOST;
if(wrapHeight) {
/**
* The first super.onMeasure call made the pager take up all the
* available height. Since we really wanted to wrap it, we need
* to remeasure it. Luckily, after that call the first child is
* now available. So, we take the height from it.
*/
int width = getMeasuredWidth(), height = getMeasuredHeight();
// Use the previously measured width but simplify the calculations
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
/* If the pager actually has any children, take the first child's
* height and call that our own */
if(getChildCount() > 0) {
View firstChild = getChildAt(0);
/* The child was previously measured with exactly the full height.
* Allow it to wrap this time around. */
firstChild.measure(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
height = firstChild.getMeasuredHeight();
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
答案 1 :(得分:11)
查看兼容性jar中ViewPager类的内部结构:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// For simple implementation, or internal size is always 0.
// We depend on the container to specify the layout size of
// our view. We can't really know what it is since we will be
// adding and removing different arbitrary views and do not
// want the layout to change as this happens.
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), getDefaultSize(0, heightMeasureSpec));
...
}
看起来ViewPager实现不会测量子视图,而只是根据父传入的内容将ViewPager设置为一个标准视图。当您传递wrap_content时,因为视图寻呼机实际上没有测量它内容占据了整个可用区域。
我的建议是根据子视图的大小在ViewPager上设置静态大小。如果这是不可能的(例如,子视图可能不同),您需要选择最大大小并在某些视图中处理额外空间或扩展ViewPager并提供测量子视图的onMeasure。您将遇到的一个问题是视图寻呼机的设计宽度不同,因为显示了不同的视图,因此您可能会被迫选择一个大小并保持不变
答案 2 :(得分:3)
如果你在PageAdapter的instantiateItem中设置了标签(位置):
@Override
public Object instantiateItem(ViewGroup collection, int page) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = (View) inflater.inflate(R.layout.page_item , null);
view.setTag(page);
然后可以使用OnPageChangeListener检索视图(适配器的页面),测量它,并调整ViewPager的大小:
private ViewPager pager;
@Override
protected void onCreate(Bundle savedInstanceState) {
pager = findViewById(R.id.viewpager);
pager.setOnPageChangeListener(new SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
resizePager(position);
}
});
public void resizePager(int position) {
View view = pager.findViewWithTag(position);
if (view == null)
return;
view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
//The layout params must match the parent of the ViewPager
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width , height);
pager.setLayoutParams(params);
}
}
答案 3 :(得分:0)
按照上面的例子,我发现测量子视图的高度并不总能返回准确的结果。解决方案是测量任何静态视图的高度(在xml中定义),然后添加在底部动态创建的片段的高度。 在我的例子中,静态元素是PagerTitleStrip,为了在横向模式下使用match_parent作为宽度,我还必须覆盖它。
所以这是我对Delyan代码的看法:
public class WrappingViewPager extends ViewPager {
public WrappingViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super has to be called in the beginning so the child views can be
// initialized.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getChildCount() <= 0)
return;
// Check if the selected layout_height mode is set to wrap_content
// (represented by the AT_MOST constraint).
boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec)
== MeasureSpec.AT_MOST;
int width = getMeasuredWidth();
View firstChild = getChildAt(0);
// Initially set the height to that of the first child - the
// PagerTitleStrip (since we always know that it won't be 0).
int height = firstChild.getMeasuredHeight();
if (wrapHeight) {
// Keep the current measured width.
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
}
int fragmentHeight = 0;
fragmentHeight = measureFragment(((Fragment) getAdapter().instantiateItem(this, getCurrentItem())).getView());
// Just add the height of the fragment:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + fragmentHeight,
MeasureSpec.EXACTLY);
// super has to be called again so the new specs are treated as
// exact measurements.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public int measureFragment(View view) {
if (view == null)
return 0;
view.measure(0, 0);
return view.getMeasuredHeight();
}}
自定义PagerTitleStrip:
public class MatchingPagerTitleStrip extends android.support.v4.view.PagerTitleStrip {
public MatchingPagerTitleStrip(Context arg0, AttributeSet arg1) {
super(arg0, arg1);
}
@Override
protected void onMeasure(int arg0, int arg1) {
int size = MeasureSpec.getSize(arg0);
int newWidthSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
super.onMeasure(newWidthSpec, arg1);
}}
干杯!
答案 4 :(得分:0)
通过参考上述解决方案,增加了一些声明,以获得查看寻呼机子视图的最大高度。
请参阅以下代码。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super has to be called in the beginning so the child views can be
// initialized.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getChildCount() <= 0)
return;
// Check if the selected layout_height mode is set to wrap_content
// (represented by the AT_MOST constraint).
boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;
int width = getMeasuredWidth();
int childCount = getChildCount();
int height = getChildAt(0).getMeasuredHeight();
int fragmentHeight = 0;
for (int index = 0; index < childCount; index++) {
View firstChild = getChildAt(index);
// Initially set the height to that of the first child - the
// PagerTitleStrip (since we always know that it won't be 0).
height = firstChild.getMeasuredHeight() > height ? firstChild.getMeasuredHeight() : height;
int fHeight = measureFragment(((Fragment) getAdapter().instantiateItem(this, index)).getView());
fragmentHeight = fHeight > fragmentHeight ? fHeight : fragmentHeight;
}
if (wrapHeight) {
// Keep the current measured width.
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
}
// Just add the height of the fragment:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + fragmentHeight, MeasureSpec.EXACTLY);
// super has to be called again so the new specs are treated as
// exact measurements.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
答案 5 :(得分:0)
更好的改变
height = firstChild.getMeasuredHeight();
到
height = firstChild.getMeasuredHeight() + getPaddingTop() + getPaddingBottom();