初始情景
需要在ScrollView中嵌套ListView中,并且ListView高度有最大值,超出部分滑动展示,不足部分自适应大小。需要解决:
- ListView在ScrollView中使用只展示一项的问题;
- ListView和ScrollView的滑动冲突问题。
问题解决
只展示一项
重写一个继承自ListView的控件,重写onMeasure()测量方法,重新获取测量大小,并设置代码如下:
public class AgentListView extends ListView {
public AgentListView(Context context) {
super(context);
}
public AgentListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AgentListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 重写测量方法,自适应大小的高度,且最大高度约等Integer.MAX_VALUE/4
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
滑动冲突
mListView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
mScrollView.requestDisallowInterceptTouchEvent(false);
}else {
// 父类不拦截action
mScrollView.requestDisallowInterceptTouchEvent(true);
}
return false;
}
});
onMeasure(widthMeasureSpec, heightMeasureSpec)方法
View中使用此方法进行测量控件大小,传入两个参数,长度和宽度。
每一个int型参数都包含了两个信息:测量模式 & 测量大小。
一个int型整数,如何表示两个信息?
一个int型,4byte,32bit,即32位。
这里使用前两位作为测量模式:UNSPECIFIED, EXACTLY, AT_MOST
后30位表示控件的测量大小。
MeasureSpec源码如下:
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
// 测量模式注解
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
// UNSPECIFIED 未指定模式 前两位为: 00
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
// EXACTLY 精确测量模式 前两位为: 01
public static final int EXACTLY = 1 << MODE_SHIFT;
// AT_MOST 最大测量模式 前两位为: 10
public static final int AT_MOST = 2 << MODE_SHIFT;
/**
* 传入测量大小和测量模式,获取 规范测量值
* @params size 大小范围 0 ~ 2^30-1
* @params mode MeasureSpecMode模式三选一
*/
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
// 类似上面方法,UNSPECIFIED时返回0,用于兼容
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
// 获取测量模式
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
// 获取测量大小
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
static int adjust(int measureSpec, int delta) {
//省略
}
public static String toString(int measureSpec) {
// 省略
}
}
如上问题解决,即使用AT_MOST测量模式,且最大值为2^(31-2)-1,也几乎等于有多大就是多大。
下面测量方式,即最大高度为 2400/4=600,实现不足600自适应大小,最大高度600滑动展示剩余项。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(2400 >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}