我已经设置了一个简单的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添加到寻呼机的布局?
并非所有答案都完美无缺。所以我创建了一个。下面的类将请求布局时,一个新的页面被选择,使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)
}
}
以上建议对我都没用。我的用例是在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只调整到当前可见的子节点(不是它实际的最大子节点)
这个想法来自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的父布局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
从爆米花时间安卓应用程序的源代码,我发现这个解决方案,动态调整大小的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);
}
}
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);
}