文章最先发布于个人微信公众号 Android_De_Home 中,欢迎你们扫描下面二维码关注微信公众获取更多知识内容。
本文为sydMobile原创文章,能够随意转载,但请务必注明出处!android
ScrollView嵌套ListView会出现的问题,相信你们已经见到的很是多了,对于解决方法也是了如指掌了。可是原理你清楚了吗?这里主要讲为何会出现这种问题,已经解决这个问题的原理。微信
ScrollView嵌套ListView会出现的问题,相信你们都已经见的很是多了,对于怎么解决也不陌生了。ide
这里再来讲一下:
出现的问题:
ListView会显示不全布局
解决方法:
最多见的一种方法:spa
本身继承ListView,重写onMeasure()方法3d
@Override
public void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
super.onMeasure(widthMeasureSpec,MeasureSpec.makeMeasureSpec
(Integer.MAX_VALUe)>>2,MeasureSpec.AT_MOST));
}
复制代码
通常的咱们仅仅须要这样重写这个方法就能够很顺利的解决ScrollView嵌套ListView出现的ListView显示不全的问题了。那么是由于什么呢?下面咱们就从原理上说说!code
提及原理就可MeasureSpec类分不开了,先来介绍一下这个类。
cdn
MeasureSpec类是View的一个内部静态类,MeasureSpec类封装了从父布局到子布局传递的布局需求。每一个MeasureSpec对象表明了宽度和高度的要求。一个MeasureSpec类的表示由控件大小和模式两组成。有三种模式:对象
MeasureSpec类为了减小对象的分配用了一个整数来实现这个功能(父布局传递到子布局的的布局需求),这个整数是用模式和大小来表示。blog
那么这个整数是怎么来实现这个功能的呢?
咱们都知道int类型的是32位,那么表示形式就是,向上面图中的那样,前两位表明了模式(就是前面提到的那三种),后30位表明了组件的大小。这样就用整数形式来表示模式和大小了。
UNSPECIFIED 模式
UNSPECIFIED == 0 << MODE_SHIFT 也就是 0 向左位移30位,结果就是int类型的最高位是 00
EXACTLY 模式
EXACTLY == 1 << MODE_SHIFT ;也就是 01向左位移30位,结果就是int类型的最高两位是 01
AT_MOST 模式 AT_MOST = 2 << MODE_SHIFT ;也就是 10 向左位移30位,结果就是int类型的最高两位是 10
在这个MeasureSpec类中最重要的一个方法恐怕就是makeMeasureSpec这个方法了。
这个方法就是用给定的大小和模式建立一个int类型的数来知足父布局到子布局传递的布局需求。第一个参数 size就是父布局给子布局传递的大小,第二个参数是模式(就是在上面的三个模式中选择一个)。好了,到这里makeMeasureSpec()这个方法也讲了。
方法一:
上面已经讲了,重写ListView的onMeasure()方法
@Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
super.onMeasure(widthMeasureSpec,MeasureSpec.makeMeasureSpec(Integer
.MAX_VALUE >> 2,MeasureSpec.AT_MOST));
}
复制代码
咱们在修改的时候并无改变widthMeasureSpec,仅仅是修改了heightMeasureSpec,由于ScrollView设置成了上下滑动,横向并无滑动,全部在横向上并无和ListView产生冲突。因此传入的widthMeasureSpec是正确的,而heightMeasureSpec是不正确的,由于ListView嵌套在ScrollView中,也就是说ScrollView是ListView的子布局,这个时候他们的滑动事件发生了冲突,这个值也就不正确了,不是LIstView的实际高度。因此咱们要重写传入height,第一个参数为何是Integer.MAX_VALUE >>2 呢 ?咱们说了MeasureSpec用 int类型表示前两位表明模式,后30位表明大小,咱们就须要让后面30位是int类型中最大的值就能够了。为何选择AT_MOST模式呢?这个模式是父布局给定一个值,不能超过这个值,咱们很显然已经给了最大值了。
方法二:
既然测不出高度,那么我就手中在代码中设置ListView的高度。
public static void setListViewHeightBasedOnChildren(ListView listView) {
if(listView == null) return;
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
// pre-condition
return;
}
int totalHeight = 0;
for (int i = 0; i < listAdapter.getCount(); i++) {
View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
}
复制代码
这种方法有前提条件限制:
Adapter中getView方法返回的View的必须由LinearLayout组成,由于只有LinearLayout才有measure()方法,若是使用其余的布局如RelativeLayout,在调用listItem.measure(0, 0);时就会抛异常,由于除LinearLayout外的其余布局的这个方法就是直接抛异常!
总结 自定义ListView比较好用,还有一个问题就是不管使用上面哪个方法,当你的ListView在加载数据的时候,若是当前页面没展现彻底,那么scrollView会自动往下滑动一点,也就是形成了你进入这个页面的时候,默认页面是往下滑动了一下,而不是在最顶端。 形成这个问题主要的缘由仍是焦点问题,ListView默认状况下,isFocusableInTouchMode和isFocusable都是false的,可是当在加载数据后这两个值就会变为true了。若是在布局中没有其余view获取焦点,这个时候ListView就争夺到了焦点,也就形成了滑动。
这个问题的解决方法:
1.在加载完数据后设置ScrollView滑动到顶部scrollView.smoothScrollTo(0,0)
这种作法是有缺点的,你会看到屏幕滑动一下。
2.使用descendantFousability属性
descendantFocusability有三种属性
beforeDescendant:viewgroup会优先其子类控件而获取到焦点。
afterDescendant:viewgroup只有当其子类控件不须要获取焦点的时候才获取焦点。
blocksDescendants: viewgroup会覆盖子类控件而直接得到焦点
在ScrollView的LinearLayout中添加android:denscendantFocusability = "blocksDescendants"就能够了。