ViewPager设置android:layout_height=“wrap_content“失效的根本原因和解决方案

准备知识:

1,xml布局文件:在LayoutInflater里的parser解析,就和普通的标签文件解析一样,通过反射实例化view并获得attrs.

2,LayoutParams:LayoutInflater.inflate时如果root!=null会去根据xml中解析出来的属性生成的LayoutParams,如果root==null则会出现xml中设置的LayoutParams参数失效。这个时候在addView时如果传入的LayoutParams为空则会generateDefaultLayoutParams生成一个默认的LayoutParams(丢失了xml中width/height等设置的属性值)

3,getMeasureWidth() :在执行完onMeasure(里面执行了setMeasuredDimension)后得到的measureWidth,

4,getWidth() :在执行完父容器的onLayout后(执行了自己的layout方法)得到的自己的width

5,计算自己的measureSize是通过onMeasure方法(里面执行了setMeasuredDimension)

6,其中计算子View的measureSpec(包含32位,前2位是mode,后30位是尺寸大小)方法主要是通过下面方法
下面传参分别:spec是当前ViewGroup的measureSpec,padding是ViewGroup的对应方向padding,childDimension一般是ViewGroup的子View在xml中设置的宽、高值。

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
1,原因分析(源码里找原因)

上面已经说了设置尺寸是在onMeasure里,直接看ViewPager里该方法

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
                getDefaultSize(0, heightMeasureSpec));
                ...
	}

里面是直接通过getDefaultSize获取尺寸的,就压根没有用到子View的measureSpec(一般通过getChildMeasureSpec获取),再看看getDefaultSize传参,一个size传0,measureSpec传的是ViewPager自己的measureSpec,这个还记得上面的方法getChildMeasureSpec,这里的ViewPager一般也是被它的父容器(一般measureSpec里的mode是AT_MOST/EXACTLY)这样计算来的,通过getChildMeasureSpec里面的逻辑知道此处ViewPager(xml中的height是wrap_content)从父容器那里获取的measureSpec的mode一般是AT_MOST,heightsize是当前ViewPager父容器的高度。

    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

所以通过getDefaultSize返回的高度result = specSize(父容器高度)。

2,解决方法
方法一、直接在xml中给ViewPager设置layout_witdh=100dp(具体的某个值)

因为上面说了用到的是ViewPager的measureSpec,根据getChildMeasureSpec里面的逻辑,可以直接让ViewPager的measureSpec尺寸变成具体的值,mode变成EXACTLY。这样大小就固定是设置的具体值了。

方法二、重写ViewPager的onMeasure,在里面修改它的measureSpec

直接上代码

public class MyViewPager extends ViewPager {
    public MyViewPager(@NonNull Context context) {
        super(context);
    }

    public MyViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int childSize = getChildCount();
        int maxHeight = 0;
        for (int i = 0; i < childSize; i++) {
            View child = getChildAt(i);
            ViewGroup.LayoutParams params = child.getLayoutParams();
            child.measure(widthMeasureSpec, getChildMeasureSpec(heightMeasureSpec , getPaddingTop()+getPaddingBottom() , params.height));
            if (child.getMeasuredHeight() > maxHeight) {
                maxHeight = child.getMeasuredHeight();
            }
        }

        //wrap_content一般对应的是mode:MeasureSpec.AT_MOST,size:parentSize所以下面针对MeasureSpec.AT_MOST处理
        // match_parent一般对应的是mode:MeasureSpec.EXACTLY,size:parentSize
        //具体的值一般对应的是mode:MeasureSpec.EXACTLY,size:selfSize(layout_width/heightde值在LayoutParams中的width/height对应关系是具体指具体的参数,match_parent代表-1,wrap_content代表-2)
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (maxHeight > 0 && heightMode == MeasureSpec.AT_MOST) {//保持match_parent和exactly具体参数的效果
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight ,  heightMode);
        }
        super.onMeasure(widthMeasureSpec , heightMeasureSpec);
    }

注意:如果用上面还是无效的,大概率是因为getChildMeasureSpec的第三个传参有问题,需要确认你自己PagerAdapter的instantiateItem方法里是否如下

            @Override
            public Object instantiateItem(@NonNull ViewGroup container, int position) {
                //子View的LayoutParams是在什么时候设置的(主要里面的width和height),如果是被addView是在父容器的addView里判断如果为空则会generateDefaultLayoutParams,如果ViewGroup里是LayoutParams.WRAP_CONTENT,会导致在xml文件里设置的具体尺寸丢失
//                View view = View.inflate(container.getContext() , R.layout.page , null);/这个执行完,view的LayoutParams为null,因为这里的root传值是null,会导致getChildMeasureSpec的第三个传参值丢失
                //用这个是可以的,因为里面执行了params = root.generateLayoutParams(attrs);根据item xml里设置的具体尺寸去生成LayoutParams
                View view = LayoutInflater.from(container.getContext()).inflate(R.layout.page,container,false);
                TextView textView = view.findViewById(R.id.content);
                textView.setText(String.valueOf(position));
                container.addView(view);
                return view;
            }

好了好了,上面都是通过改变ViewPager的measureSpec实现解决方案的。

建议:源码还是要多看多实践才能变得更加深刻。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值