一.背景: 第一次写博客还是技术性博客,为了回答CSDN上一位网友的问题,决定写一篇博客既帮助别人又帮助自己,经常看鸿洋大神,郭大神的博客,两位大神真是业界良心,不仅仅技术厉害,博客也写的让人一目了然,自身觉得自己内心知道的知识讲给别人或者是像这样写博客给别人看,让别人也了解,是一件很厉害的事。所以第一次写这种技术性博客,不知道看到的人是否能看懂得到一些启发,如果有什么不足的地方希望大家多多指正。 二.概述: 好了,开始转入正题,关于ViewPager大家应该都很熟悉,大多数APP中必然会用到的控件(说的有点绝对,但看过的APP貌似都离不开它)。常见的两种ViewPager,一种是viewpager+imageview(滚动的banner),一种则是viewpager+fragment。无论哪种,这里讲到的自适应都可以实现。 三.实现效果: 哎,没有时间弄一幅动图,那我就口述一下吧,很多Android开发者会遇到的问题,例如,你的整个布局在一个线性布局(Linearlayout)中,简单的(vertical)竖行排列,你的viewpager上面有一些其他布局,viewpager在最下面,所以很可能嵌套在scrollview中,为了解决viewpager和scorllview的冲突,你很可能去百度viewpager的高度自适应,结果如图:
/**
* 自动适应高度的ViewPager
* @author
*
*/
public class CustomViewPager extends ViewPager {
public CustomViewPager(Context context) {
super(context);
}
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int 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.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
这是最常见的一种处理办法,选择你fragment中高度最大的那个作为你整个viewpager的高度。解决了冲突问题,但你会遇到这样一个棘手的问题:所有viewpager中的fragment都是那个最大的高度,如果你的fragment中view的高度很小的话,或者view的高度过大的话,会导致自身或者其他fragment中出现大面积空白。所以综上所述,我们要达到的效果是去除这空白,使viewpager的高度真正“自适应”。 四.具体实现 我们就拿viewpager+fragment,最常见的scrollview嵌套viewpager的例子,首先看我自定义的viewpager
package com.xxx.xxx.views;
import android.content.Context;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import java.util.HashMap;
import java.util.Map;
/**
* Created by vipui on 16/8/25.
*/public class CustomViewpager extends ViewPager {
private int current;
private int height = 0;
private boolean scrollble = true;
public CustomViewpager(Context context) {
super(context);
}public CustomViewpager(Context context, AttributeSet attrs) {
super(context, attrs);
}@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (getChildCount() > current) {
View child = getChildAt(current);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
height = h;
}heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}public voidresetHeight(int current) {
this.current = current;
if (getChildCount() > current) {
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
if (layoutParams == null) {
layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, height);
} else {
layoutParams.height = height;
}
setLayoutParams(layoutParams);
}
}@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!scrollble) {
return true;
}
return super.onTouchEvent(ev);
}public boolean isScrollble() {
return scrollble;
}public void setScrollble(boolean scrollble) {
this.scrollble = scrollble;
}}
onMeasure()测量控件的方法,resetHeight()重置viewpager的高度的方法,从代码中可以看出在调用resetHeight()方法中传入实参current后,viewpager的高度会变成你传入实参对应下标的fragment的高度,那么在哪里调用这个方法呢?请看代码:
activityScdetailsBottomVp.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}@Override
public void onPageSelected(int position) {
activityScdetailsBottomVp.resetHeight(position);
if (position == 0) {activityScdetailsBottomLinear.setBackgroundResource(R.drawable.fishbone_diagram_list_btn_1);
activityScdetailsBottomTaskTv.setTextColor(Color.parseColor("#ffffff"));
activityScdetailsBottomInfoTv.setTextColor(Color.parseColor("#c1c1c1"));
activityScdetailsBottomTimeTv.setTextColor(Color.parseColor("#c1c1c1"));
} else if (position == 1) {activityScdetailsBottomLinear.setBackgroundResource(R.drawable.fishbone_diagram_list_btn_2);
activityScdetailsBottomTaskTv.setTextColor(Color.parseColor("#c1c1c1"));
activityScdetailsBottomInfoTv.setTextColor(Color.parseColor("#ffffff"));
activityScdetailsBottomTimeTv.setTextColor(Color.parseColor("#c1c1c1"));
} else {activityScdetailsBottomLinear.setBackgroundResource(R.drawable.fishbone_diagram_list_btn_3);
activityScdetailsBottomTaskTv.setTextColor(Color.parseColor("#c1c1c1"));
activityScdetailsBottomInfoTv.setTextColor(Color.parseColor("#c1c1c1"));
activityScdetailsBottomTimeTv.setTextColor(Color.parseColor("#ffffff"));
}
}
@Override
public void onPageScrollStateChanged(int state) {}
});
activityScdetailsBottomVp.resetHeight(0);
}
在viewpager中的onpagerChagelistener的方法中,当你改变viewpager的pager页位置时重置viewpager的高度。好了如果你按照这个逻辑去做已经很接近实现了,但要说明一个问题,很重要的一个问题,在低版本的SDK下,似乎没什么问题,但是在高版本SDK下,就有了问题。这个问题纠结了我一天多,因为我在Android4.3的手机,完全实现了,但是在队友Android6.0的手机下就出现了问题。( 这是因为高版本中viewpager有改动,并不知道有什么改动,觉得是预加载的改动)对的,高度不对应,就是你viewpager中的fragment不是自己本身的高度,可能是其他fragment的高度,这个问题,大家都应该想的到,viewpager的预加载导致的(3个或3个以上的子view),viewpager在加载当前fragment的过程中会预加载临近两个的fragment,所以,拿viewpager中有三个fragment来说,你的第一个fragment的高度是第三个fragment的高度,(因为预加载到第三个)第一你们第二个fragment的高度是你 第一个fragment的高度(预加载到第一个),以此类推。解决这个问题的方法有两个,
第一个,看代码(对照上面代码)
activityScdetailsBottomVp.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}@Override
public void onPageSelected(int position) {
if (position == 0) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
activityScdetailsBottomVp.resetHeight(1);
} else {
activityScdetailsBottomVp.resetHeight(0);
}
activityScdetailsBottomLinear.setBackgroundResource(R.drawable.fishbone_diagram_list_btn_1);
activityScdetailsBottomTaskTv.setTextColor(Color.parseColor("#ffffff"));
activityScdetailsBottomInfoTv.setTextColor(Color.parseColor("#c1c1c1"));
activityScdetailsBottomTimeTv.setTextColor(Color.parseColor("#c1c1c1"));
} else if (position == 1) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
activityScdetailsBottomVp.resetHeight(2);
} else {
activityScdetailsBottomVp.resetHeight(1);
}
activityScdetailsBottomLinear.setBackgroundResource(R.drawable.fishbone_diagram_list_btn_2);
activityScdetailsBottomTaskTv.setTextColor(Color.parseColor("#c1c1c1"));
activityScdetailsBottomInfoTv.setTextColor(Color.parseColor("#ffffff"));
activityScdetailsBottomTimeTv.setTextColor(Color.parseColor("#c1c1c1"));
} else {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
activityScdetailsBottomVp.resetHeight(0);
} else {
activityScdetailsBottomVp.resetHeight(2);
}
activityScdetailsBottomLinear.setBackgroundResource(R.drawable.fishbone_diagram_list_btn_3);
activityScdetailsBottomTaskTv.setTextColor(Color.parseColor("#c1c1c1"));
activityScdetailsBottomInfoTv.setTextColor(Color.parseColor("#c1c1c1"));
activityScdetailsBottomTimeTv.setTextColor(Color.parseColor("#ffffff"));
}
}@Override
public void onPageScrollStateChanged(int state) {}});
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
activityScdetailsBottomVp.resetHeight(1);
} else {
activityScdetailsBottomVp.resetHeight(0);
}
}
简单粗暴的办法,用版本控制你要加载的页面高度,亲测可以解决,但是并不知道到了哪个高版本出现了变化,我这里用的是KITKAT(android4.4),这个临界点有待商量。 第二种:(简单粗暴的方法毕竟不合理)所以介绍另一种更加合理的方法。(先看我的自定义viewpager,可以对比一下上面的)
package com.xx.xxxx.views;
import android.content.Context;
import android.os.Build;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Created by vipui on 16/8/25.
*/public class CustomViewpager extends ViewPager {
private int current;
private int height = 0;
/**
* 保存position与对于的View
*/
private HashMap mChildrenViews = new LinkedHashMap();
private boolean scrollble = true;
public CustomViewpager(Context context) {
super(context);
}public CustomViewpager(Context context, AttributeSet attrs) {
super(context, attrs);
}@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mChildrenViews.size() > current) {
View child = mChildrenViews.get(current);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
height = child.getMeasuredHeight();
}heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}public void resetHeight(int current) {
this.current = current;
if (mChildrenViews.size() > current) {LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
if (layoutParams == null) {
layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, height);
} else {
layoutParams.height = height;
}
setLayoutParams(layoutParams);
}
}
/**
* 保存position与对于的View
*/
public void setObjectForPosition(View view, int position)
{
mChildrenViews.put(position, view);
}@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!scrollble) {
return true;
}
return super.onTouchEvent(ev);
}public boolean isScrollble() {
return scrollble;
}public void setScrollble(boolean scrollble) {
this.scrollble = scrollble;
}}
setObjectForPosition()方法中是为了调用存放你的view和他对应的position,这个是参考了鸿洋大神的一篇文章,链接:http://blog.csdn.net/lmj623565791/article/details/38026503,为了防止预加载导致的高度不匹配,我们加自身的fragment和position对应起来放在linkedmap里。
好了,剩下的就是调用
setObjectForPosition()这个方法了,请看我的一个fragment
public SecurityInfoFragment(CustomViewpager vp) {
this.vp = vp;
}@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fg_sc_filght_info, null);
ButterKnife.bind(this, view);
vp.setObjectForPosition(view,1);
return view;
}
在你的fragment中将其view放进去。 好了,最后一步,就是调用resetheight()方法了,看代码吧
activityScdetailsBottomVp.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}@Override
public void onPageSelected(int position) {
activityScdetailsBottomVp.resetHeight(position);
if (position == 0) {activityScdetailsBottomLinear.setBackgroundResource(R.drawable.fishbone_diagram_list_btn_1);
activityScdetailsBottomTaskTv.setTextColor(Color.parseColor("#ffffff"));
activityScdetailsBottomInfoTv.setTextColor(Color.parseColor("#c1c1c1"));
activityScdetailsBottomTimeTv.setTextColor(Color.parseColor("#c1c1c1"));
} else if (position == 1) {
//if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
//activityScdetailsBottomVp.resetHeight(2);
//} else {
//activityScdetailsBottomVp.resetHeight(1);
//}
activityScdetailsBottomLinear.setBackgroundResource(R.drawable.fishbone_diagram_list_btn_2);
activityScdetailsBottomTaskTv.setTextColor(Color.parseColor("#c1c1c1"));
activityScdetailsBottomInfoTv.setTextColor(Color.parseColor("#ffffff"));
activityScdetailsBottomTimeTv.setTextColor(Color.parseColor("#c1c1c1"));
} else {activityScdetailsBottomLinear.setBackgroundResource(R.drawable.fishbone_diagram_list_btn_3);
activityScdetailsBottomTaskTv.setTextColor(Color.parseColor("#c1c1c1"));
activityScdetailsBottomInfoTv.setTextColor(Color.parseColor("#c1c1c1"));
activityScdetailsBottomTimeTv.setTextColor(Color.parseColor("#ffffff"));
}
}
@Override
public void onPageScrollStateChanged(int state) {}
});
activityScdetailsBottomVp.resetHeight(0);
}
恩恩,这就基本实现了高度自适应,我也是查阅了很多人的资料,才解决了这个问题,希望看到的人,对你有所帮助,第一次写,写的不好,欢迎吐槽
【关于ViewPager高度自适应(随着pager页的高度改变Viewpager的高度)】
推荐阅读
- 从零开发一个完整的Android项目(九)——图片浏览
- Android开发|ViewPager自适应高度问题
- Android|Android 指定销毁一个Activity
- 【Android】简单图片浏览器
- 理解ButterKnife(自动生成绑定资源的代码)
- MAC下搭建Android Studio
- NestedScrollingParent 和NestedScrollingChild 实现嵌套滑动
- android用shape画一条横线
- 华为推送 的坑
- Duplicate class com.alipay.a.a.a found in modules classes.jar (:alipaySdk-15.6.2-20190416165036:) an