参考文献:
安卓自定义view源码解读
https://www.jianshu.com/p/705a6cb6bfee
https://www.jianshu.com/p/a09a0a1f33aa

Scrollview嵌套listview解决方案
https://blog.csdn.net/cxmscb/article/details/52551065

listview的onMeasure方法
https://blog.csdn.net/yu280265067/article/details/50755544
https://blog.csdn.net/chuyouyinghe/article/details/115256503

一、背景
在布局demo中,整体为一个scrollview,其中嵌套了一个listview,现在希望listview里的元素可以全都直接展示,而不是滑动显示,仅整体页面可以滑动。
默认状态下,listview只会展示一部分,当整体页面没超过手机屏幕高度时,点击到listview控件才会触发滑动条。

二、解决方案
第一种较简单,由于ListView的高度设置为wrap_content时会产生,一般情况下ListView只显示的第一个Item。所以只需要在xml里定义listview时将android:layout_height="wrap_content"改为较大的数值能完整显示需要展示的items即可。

不过这种有点蠢,可复用性也不高,所以需要用自定义view的方式来解决。

三、自定义view相关
图片说明

view的绘制过程分为三个阶段:图片说明

其中,MeasureSpec是View的内部类,它封装了一个View的尺寸,在onMeasure()当中会根据这个MeasureSpec的值来确定View的宽高。
MeasureSpec的值保存在一个int值当中。一个int值有32位,前两位表示模式mode后30位表示大小size。即MeasureSpec = mode + size。
在MeasureSpec当中一共存在三种mode:UNSPECIFIED、EXACTLY 、AT_MOST。
图片说明

EXACTLY:父亲指定了孩子的大小,不管孩子想要多大,就只给指定的大小

AT_MOST:父亲给孩子一个允许的最大大小,孩子想要多大,就给多大

UNSPECIFIED:父亲不限制孩子的大小,由孩子自己决定

四、code

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.widget.ListView;

public class MyListView extends ListView {
    public MyListView(Context context) {
        super(context);
    }

    public MyListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public MyListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2, // 设计一个较大的值和AT_MOST模式
                MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, newHeightMeasureSpec);//再调用原方法测量
    }
}

注意:在main类与xml中调用时也要注意将ListView改成MyListView,才会生效

定义了四个MyListView(),分别用于:
一个参数的构造函数 : 在java代码中创建对象时调用;
两个参数的构造函数:在加载xml文件时使用
三个参数的构造函数:是用于带有主题Style的构造
四个参数的构造函数:当我们有自己定义属性的构造函数

重写的方法中,这一句:

int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2,MeasureSpec.AT_MOST);

意思是把MAX_VAULE右移两位,一个int类型是有32位。而MeasureSpec的模式有三种,要表示三种状态,至少得2位二进制位。于是系统采用了最高的2位来表示模式。
00表示UNSPECIFIED
01表示EXACTLY
11表示AT_MOST

图片说明

Q:为什么是右移两位?
A:
第一个参数有个最大值的限制:1073741823(二进制的30个1),而MAX_VALUE的原始值是1个0加上31个1(二进制),所以实际上右移一位和不移位才是最大值,但是一般来说,右移2位后,即使不是最大整数了,listview的高度也不可能超过它。
由于最前面两位表示mode,而不是size,所有右移1位和右移2位的效果是一样的(前面两位的值都会被mode的代码覆盖)

Q:那么为什么不用其他模式呢?
A:
AT_MOST是在测量时提供一个最大值,那么 View 就先以自己的内容为准,在不超过最大值的前提下,最终确定自己的尺寸;
EXACTLY是根据设定的值(例如MeasureSpec.makeMeasureSpec(1000,MeasureSpec.EXACTLY))来确定实际尺寸,所以还是同样的问题,不可以复用,需要手动去设定高度;而且如果设置的measureSpec中size的值大于listview内容全部显示的高度,大于listview内容全部显示的高度的部分显示为空白。

UNSPECIFIED是不确定模式,用这个模式,View 就会任意确定自己的尺寸,不管传什么值进去都是没有意义。很少用,为了避免和 AT_MOST 搞混,暂时将它忽略。

int widthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
// 第二个参数为高度的测量规则,写 0  表示省略