今天遇到一个坑爹的需求,需求如题,就是想实现ViewPager在切换的时候自身的高度随itemView的高度调整.
使用过ViewPager的人都知道,即使你在布局中写的高度是wrap_content,但是运行起来就会发现他其实是match_parent的效果,也就是填充整个屏幕,除非你写死一个高度.
解决这个问题,那就只能自定义一个View继承ViewPager然后重写onMeasure方法了.在onMeasure方法中获取当前正在展示的子View,然后测量其高度.然后再调用ViewPager的setMeasuredDimension方法设置高度的时候先判断当前ViewPager是否是match_parent,如果时则使用ViewPager测量的高度,否则就取子View和ViewPager各自测量高度的最小值.
那么问题来了.onMeasure方法如果才能获取到当前正在展示的子View呢?
通过getCurrentItem()可以获取当前正在展示的子View的position,然后再调用getChildAt(int position)就可以获取到该子View了.
好了,新问题来了.onMeasure方法通常只会回调2次,而且再以后的子View切换的时候并不会再回调onMeasure,该怎么解决呢?
哈,我的第一反应就是手动刷新onMeasure()方法,刚好requestLayout()方法可以帮我实现,调用这个方法,它不但可以帮我们调用onMeasure()还会调用layout()方法.那么,我只需要监听ViewPager的滑动切换就好了, 然后在
onPageSelected()方法中去调用requestLayout()方法即可,完美!
看看效果图:
展示了2张图,高度不一样,下面的红色区域代表其他布局.ViewPager会随着图片的高度自适应,下面红色区域也会随着调整.
来看看代码:
/**
* 根据View的内容自动适应高度的ViewPager
* Created by mChenys on 2017/1/11.
*/
public class AutofitViewPager extends ViewPager {
private static final String TAG = "AutofitViewPager";
public AutofitViewPager(Context context) {
this(context,null);
}
public AutofitViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
addOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
requestLayout();
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d(TAG, "onMeasure");
// find the current child view
View view = getChildAt(getCurrentItem());//注意:这里是有bug的,看文末的解决方式
if (view != null) {
// measure the current child view with the specified measure spec
view.measure(widthMeasureSpec, heightMeasureSpec);
}
setMeasuredDimension(getMeasuredWidth(), measureHeight(heightMeasureSpec, view));
}
/**
* Determines the height of this view
*
* @param measureSpec A measureSpec packed into an int
* @param view the base view with already measured height
*
* @return The height of the view, honoring constraints from measureSpec
*/
private int measureHeight(int measureSpec, View view) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
// set the height from the base view if available
if (view != null) {
result = view.getMeasuredHeight();
}
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
}
测试类
public class TestViewPager extends AppCompatActivity {
private ViewPager mViewPager;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_vp);
mViewPager = (ViewPager) findViewById(R.id.vp);
mViewPager.setAdapter(new PagerAdapter() {
@Override
public int getCount() {
return 2;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
View view = null;
if (position == 0) {
view = View.inflate(getBaseContext(),R.layout.item_vp1,null);
} else if (position == 1) {
view = View.inflate(getBaseContext(),R.layout.item_vp2,null);
}
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
});
}
}
测试类布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<mchenys.net.csdn.blog.AutofitViewPager
android:id="@+id/vp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent"></FrameLayout>
</LinearLayout>
在使用过程中发现bug,当ViewPager滑动的position大于3时,AutofitViewPager 代码的第36行获取itemView的时候返回null.
View view = getChildAt(getCurrentItem());
导致后面获取的测量高度永远都是0.猜测是viewpager里面最多有3个view,超过三个的时候就把旧的回收了,解决这个问题也挺巧妙的,就是在AutofitViewPager 设置adapter的时候在 instantiateItem方法中给返回的itemView设置一个id,该id就赋值为当前的position,
修改测试类TestViewPager 的PagerAdapter的初始化Item的方法.
@Override
public Object instantiateItem(ViewGroup container, int position) {
View view = null;
if (position == 0) {
view = View.inflate(getBaseContext(),R.layout.item_vp1,null);
} else if (position == 1) {
view = View.inflate(getBaseContext(),R.layout.item_vp2,null);
}
container.addView(view);
//注意:这里必须要给返回的view设置一个id,id的值就是当前的position
view.setId(position);
return view;
}
然后再将AutofitViewPager 类的第36行代码替换为:
View view = findViewById(getCurrentItem());
这样就解决了.
最后再提醒下,在显示网络图片的时候ImageView最好是在布局中实例化出来的,而不要直接在instantiateItem方法内通过new来创建,我试过用Glide库加载网络图片通过new来创建ImageView设置的LayoutParams宽高信息后,AutofitViewPager去获取ImageView的测量高度永远都是0,什么也看不到.如果是本地图片则不会有影响.