zoukankan      html  css  js  c++  java
  • ScrollView嵌套使用ListView冲突的解决与分析

      因为ScrollView与ListView都是具有滚动条的控件,所以嵌套在一起使用的时候可能会出现事件的冲突,比如我就遇见了ListView中只显示一条数据的问题。解决的办法,就是自定义了一个ListView,重写它的onMeasure()方法:

      

     1 public class MyListView extends ListView {
     2 
     3     public MyListView(Context context) {
     4         super(context);
     5         // TODO Auto-generated constructor stub
     6     }
     7 
     8     public MyListView(Context context, AttributeSet attrs) {
     9         super(context, attrs);
    10         // TODO Auto-generated constructor stub
    11     }
    12 
    13     public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
    14         super(context, attrs, defStyleAttr);
    15         // TODO Auto-generated constructor stub
    16     }
    17 
    18     @Override
    19     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    20         int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
    21         super.onMeasure(widthMeasureSpec, expandSpec);
    22 
    23     }
    24 
    25 }

    MeasureSpec.makeMeasureSpec()方法是由我们给出的尺寸大小和模式,来生成一个包含这两个信息的int类型的变量。根据我们提供打大小值和模式创建一个测量值(格式)。

    每次onMeasure()的时候都重新绘制高度,这样就不会只显示一条数据。

    为什么会只显示一行的高度呢?这其实跟ListView的源码有关,ListView的父容器测量模式为UNSPECIFIED的时候,ListView的高度默认为一个item的高度

    ScrollView中的部分源码:

    1 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    2         if (heightMode == MeasureSpec.UNSPECIFIED) {
    3             return;
    4         }

    ListView中源码如下:

    1 if (heightMode == MeasureSpec.UNSPECIFIED) {
    2     heightSize = mListPadding.top + mListPadding.bottom + childHeight +
    3                    getVerticalFadingEdgeLength() * 2;
    4 }

    原理分析:

      原理的理解是看了这篇博主的博文才理解的,写的很清楚,不明白的可以去看下:

      ScrollView嵌套ListView,ListView完全展开及makeMeasureSpec测量机制原理分析

      从上面看出,我们把高度写成了一个固定的值expandSpec,这个值得计算方法如下:

    1 expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);

      可以看出我们将整数类型的最大值向右移了两位作为参数传入,模式我们传入了常量MeasureSpec.AT_MOST。而为什么我们重新计算了高度值,并且还是一个这么奇葩的数值作为参数传入呢?这就跟Android中的实体测量机制相关了,android中规定,测量的值,高度或者是宽度,是作为一个int类型的数值,但不是普通的int,而是一个经过处理的int值,在view视图中,我们制定一个高度或者宽度,需要指定两个参数,一个是具体大小,另一个是测量模式,测量模式就是我们在布局中经常用到的MATCH_PARENT、WRAP_CONTENT。他们是一个int类型的常量,分别对应值:

    1 LayoutParams.MATCH_PARENT  对应 MeasureSpec.EXACTLY
    2 LayoutParams.WRAP_CONTENT  对应 MeasureSpec.AT_MOST

    在源码中的展示为:

     1 public class View implements ... {
     2 ...
     3 public static class MeasureSpec {
     4 private static final int MODE_SHIFT = 30; //移位位数为30
     5 //int类型占32位,向左移位30位,用来与size和mode进行"&"运算,获取对应值。
     6 private static final int MODE_MASK = 0x3 << MODE_SHIFT;
     7  //向左移位30位,其值为00 + (30位0)  , 即 0x0000(16进制表示)  
     8     public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
     9     //向左移位30位,其值为01 + (30位0)  , 即0x1000(16进制表示)  
    10     public static final int EXACTLY     = 1 << MODE_SHIFT;  
    11     //向左移位30位,其值为02 + (30位0)  , 即0x2000(16进制表示)  
    12     public static final int AT_MOST     = 2 << MODE_SHIFT;  
    13 
    14     //创建一个整形值,其高两位代表mode类型,其余30位代表长或宽的实际值。可以是WRAP_CONTENT、MATCH_PARENT或具体大小exactly size  
    15     public static int makeMeasureSpec(int size, int mode) {  
    16         return size + mode;  
    17     }  
    18     //获取模式  ,与运算  
    19     public static int getMode(int measureSpec) {  
    20         return (measureSpec & MODE_MASK);  
    21     }  
    22     //获取长或宽的实际值 ,与运算  
    23     public static int getSize(int measureSpec) {  
    24         return (measureSpec & ~MODE_MASK);  
    25     }  
    26 
    27 }  
    28 ...

    可以看到,EXACTLY和AT_MOST的值是:

    1 private static final int MODE_SHIFT = 30;      
    2 
    3         public static final int EXACTLY     = 1 << MODE_SHIFT;    //填满父控件高度
    4         public static final int AT_MOST     = 2 << MODE_SHIFT;    //自适应当前控件高度

    可以看到,分别是将1和2向左移了30位,为什么向左移30位呢?

    android中把测量出的int做了处理,int类型的长度是32位,把前两位作为标记位,标记了测量模式,如EXACTLY、AT_MOST,把最后30位作为测量的具体高度或者是宽度

    也就是说把一个int分为了两部分,是一个int同时具有了测量模式和具体数值两部分信息!

    EXACTLY的值是1向左进位30,就是01 00000000000…(01后跟30个0) 
    AT_MOST的值是2向左进位30,就是10 00000000000…(10后跟30个0) 

    所以我们在调用MeasureSpec.makeMeasureSpec(size,mode)方法时,传入的size参数要把Integer.MAX_VALUE右移2位,因为前两位会被认为是标志,而不是值。这样我们传入的参数才会被认为是最大的int类型的值,同时传入AT_MOST作为模式,那么前两位就会被赋值为10,那么我们来实际计算一下,我们调用MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST)的实际结果是多少:

    int类型长度是32位,其中包含1个标准位,所有最大值Integer.MAX_VALUE为 2的31次方 :2147483648 
    16进制下为0x7fffffff,二进制为:01 11111111111…(01后跟30个1) 
    由于前两位在android测量基础下是无效的标志位,所以我们右移2位的结果为000111111111111… 
    调用makeMeasureSpec方法后会把前两位替换为AT_MOST的前两位(其实是直接相加) 
    结果为:100111111111… 十六进制:9fffffff,十进制:-1610612737
    
    所以你调试的时候后发现,返回的值为-1610612737 ,就是这个原因,因为前2位是无效的标志位,所以其实这个数所代表的最大值是 2的29次方:536870912。所以对于android测量机制来理解我们传入的-1610612737,是这样理解的:
    1 -1610612737  代表: 测量模式为AT_MOST,最大高度为536870912(2的29次方)

     关于MeasureSpec类的使用可以移文这篇博客:

    android学习——MeasureSpec介绍及使用

  • 相关阅读:
    14-ESP8266 SDK开发基础入门篇--上位机串口控制 Wi-Fi输出PWM的占空比,调节LED亮度,8266程序编写
    Zookeeper之Curator(1)客户端基本的创建,删除,更新,查找操作api
    【1】基于quartz框架和Zookeeper实现集群化定时任务系统
    Spring源码学习之:ClassLoader学习(5)-自测
    java多线程:线程体往外抛出异常的处理机制实践
    MySQL详解--锁,事务
    ubuntu下访问支付宝官网,安装安全控件
    spring源码学习之:springAOP实现底层原理
    spring源码学习之:项目公共配置项解决方案
    java多线程:synchronized和lock比较浅析
  • 原文地址:https://www.cnblogs.com/RabbitLx/p/5858031.html
Copyright © 2011-2022 走看看