从原理上说说ScrollView嵌套ListView的问题

ScrollView嵌套ListView会出现的问题,相信大家已经见到的非常多了,对于解决方法也是了如指掌了。但是原理你清楚了吗?这里主要讲为什么会出现这种问题,已经解决这个问题的原理。

ScrollView嵌套ListView出现的问题

ScrollView嵌套ListView会出现的问题,相信大家都已经见的非常多了,对于怎么解决也不陌生了。

这里再来说一下:
出现的问题:
ListView会显示不全

解决方法:
最常见的一种方法:

自己继承ListView,重写onMeasure()方法

    @Override
    public void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
        super.onMeasure(widthMeasureSpec,MeasureSpec.makeMeasureSpec
        (Integer.MAX_VALUe)>>2,MeasureSpec.AT_MOST));

    }

一般的我们仅仅需要这样重写这个方法就可以很顺利的解决ScrollView嵌套ListView出现的ListView显示不全的问题了。那么是因为什么呢?下面我们就从原理上说说!

解决原理

说起原理就可MeasureSpec类分不开了,先来介绍一下这个类。

MeasureSpec类

MeasureSpec类是View的一个内部静态类,MeasureSpec类封装了从父布局到子布局传递的布局需求。每个MeasureSpec对象代表了宽度和高度的要求。一个MeasureSpec类的表示由控件大小和模式两组成。有三种模式:

  • UNSPECIFIED
    父布局没有对子布局施加任何的限制,子布局可以是任何他想要的大小
  • EXACTLY
    父布局已经确定了子布局的大小,子布局会在父布局给出的界限内。子布局的大小是精确的。父布局给多大就是多大。
  • AT_MOST
    父布局会给定一个最大的值,子布局的大小是不能超过这个值的。但是可以比这个值小。

MeasureSpec类为了减少对象的分配用了一个整数来实现这个功能(父布局传递到子布局的的布局需求),这个整数是用模式和大小来表示。

那么这个整数是怎么来实现这个功能的呢?

int表示形式

我们都知道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

makeMeasureSpec()方法

在这个MeasureSpec类中最重要的一个方法恐怕就是makeMeasureSpec这个方法了。

makeMeasureSpec

这个方法就是用给定的大小和模式创建一个int类型的数来满足父布局到子布局传递的布局需求。第一个参数 size就是父布局给子布局传递的大小,第二个参数是模式(就是在上面的三个模式中选择一个)。好了,到这里makeMeasureSpec()这个方法也讲了。

ScrollView嵌套ListView解决方法

方法一:

上面已经讲了,重写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"就可以了。

欢迎大家关注我的微信公众号,和我交流分享
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容