IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> ViewPager设置android:layout_height=“wrap_content“失效的根本原因和解决方案 -> 正文阅读

[移动开发]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实现解决方案的。

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

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-21 15:35:19  更:2021-08-21 15:37:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/30 22:32:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码