從源碼角度分析ScrollView嵌套ListView顯示不全問題

  1. 簡介
    在我們開發(fā)項目過程中跷跪,我們在座絕大部分的人一定都遇到過ScrollVIew嵌套ListView顯示不全馋嗜,只顯示一個item高度的問題齐板,當(dāng)然吵瞻,你如果說你沒有遇到過也是沒有問題的葛菇。這種現(xiàn)象是很常見的,網(wǎng)上一搜基本解決方法一般有這樣幾種橡羞,
    1.1 動態(tài)設(shè)置listview眯停,去測量每個item高度,通過for循環(huán)疊加計算listview的總高度
    1.2 使用LinearLayout代替ListView
    1.3 自定義MyListView直接集成系統(tǒng)的ListView卿泽,重寫onMeasure()方法
    等等...

  2. 實現(xiàn)
    接下來帶大家具體分析下這幾種方式的具體使用莺债,前兩種使我們這節(jié)課的一個小插曲,待會看下其實現(xiàn)方式即可签夭,今天我們重點帶大家看下第三種實現(xiàn)方式齐邦,它為什么要這樣去寫

    方式一: 動態(tài)設(shè)置listview,去測量每個item高度第租,通過for循環(huán)疊加計算listview的總高度
    我們大家都知道措拇,在布局文件中如果直接將item高度寫死,是可以解決這個問題的慎宾,但是我們也都知道丐吓,listview高度隨著數(shù)據(jù)是可變化,實際高度還需要實際去測量趟据,那么既然這樣券犁,我們就可以手動的去計算ListView的高度了,代碼如下汹碱,直接作為工具類拷貝到項目中即可使用

/**
     * Describe: 動態(tài)設(shè)置listview粘衬,去測量每個item高度,通過for循環(huán)疊加計算listview的總高度
     * <p>
     * Author: Jack-Chen
     * <p>
     * Time 16/9/27 下午4:28
     */
public class ListViewUtil {
  
   public static void adaptiveHight(Context context,ListView listView,float dividerHeight) {
       try {
           ListAdapter listAdapter = listView.getAdapter();
           if (listAdapter == null) {
               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();
           if (dividerHeight != -1) {
               totalHeight += UIHelper.dip2px(context, dividerHeight) * (listAdapter.getCount() - 1);
           }
           params.height = totalHeight;

           listView.setLayoutParams(params);
       }catch (Exception ex){
           ex.printStackTrace();
       }
   }
   
   public static int getItemsHight(ListView listView)  {
       ListAdapter listAdapter = listView.getAdapter();  
        if (listAdapter == null) { 
            return 0; 
        } 
        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(); 
        }  
       return totalHeight;
   }
 
   public static int getItemHight(ListView listView)  {
        ListAdapter listAdapter = listView.getAdapter();  
        if (listAdapter == null) { 
            return 0; 
        } 
        int itemHeight = 0; 
        if(listAdapter.getCount()>0) {
             View listItem = listAdapter.getView(0, null, listView); 
             listItem.measure(0, 0); 
             itemHeight= listItem.getMeasuredHeight(); 
        }       
       return itemHeight;
   }
}

方式二: 使用LinearLayout代替ListView
既然listview不能適應(yīng)ScrollView咳促,那么我們完全可以找一個可以適應(yīng)ScrollView的控件來代替ListView色难,此時LinearLayout是最好的選擇,但如果我們還想繼續(xù)使用已經(jīng)定義好的adapter等缀,那么我們只需要定義一個類去繼承LinearLayout枷莉,最后為其適配BaseAdapter即可
具體代碼如下:
2.2.1: 自定義LinearLayoutForListView 繼承LinearLayout

/**
     * Describe: 使用LinearLayout代替ListView
     * <p>
     * Author: Jack-Chen
     * <p>
     * Time 16/5/27 下午3:45
     */
public class LinearLayoutForListView extends LinearLayout {
    private BaseAdapter adapter;
    private OnClickListener onClickListener = null;
    /**
     * 綁定布局
     */
    public void bindLinearLayout() {
        int count = adapter.getCount();
        this.removeAllViews();
        for (int i = 0; i < count; i++) {
            View v = adapter.getView(i, null, null);
            v.setOnClickListener(this.onClickListener);
            addView(v, i);
        }
       Log.v("countTAG", "" + count);
    }
    public LinearLayoutForListView(Context context) {
        super(context);
}

2.2.2: 將自己之前的ListView布局文件替換為這個包下的布局文件

<com.jackchen.LinearLayoutForListView 
    android:id="@+id/act_solution_3_mylinearlayout"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

2.2.3 : 然后去替換Activity或Fragment中之前ListView的控件為LinearLayoutForListView,最后為其setAdapter適配數(shù)據(jù)即可

方式三:自定義MyListView直接繼承系統(tǒng)的ListView尺迂,重寫onMeasure()方法

/**
    * Describe: 自定義MyListView直接繼承系統(tǒng)的ListView
    * <p>
    * Author: Jack-Chen
    * <p>
    * Time 16/8/27 下午2:40
    */
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);
   }


   /**
    * 解決ScrollView嵌套ListView顯示不全問題
    * @param widthMeasureSpec
    * @param heightMeasureSpec
    */
   @Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       //heightMeasureSpec 參數(shù)1是32位的值 右移2位變成30位的值笤妙,  MeasureSpec.AT_MOST是模式,ListView源碼中應(yīng)該要執(zhí)行MeasureSpec.AT_MOST這個if
//        if (heightMode == MeasureSpec.AT_MOST) {
//            // TODO: after first layout we should maybe start at the first visible position, not 0
//            heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
//        }
       //而不能讓執(zhí)行這個if噪裕,因為這個if里邊剛好是listview的1個item的高度
//        if (heightMode == MeasureSpec.UNSPECIFIED) {
//            heightSize = mListPadding.top + mListPadding.bottom + childHeight +
//                    getVerticalFadingEdgeLength() * 2;
//        }
       heightMeasureSpec =
               MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2 , MeasureSpec.AT_MOST) ;
       super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   }
}

這個方法我相信絕大部分的人都是采用這用方式的蹲盘,因為這種方式相對來說比較簡單,直接拷貝過去用即可膳音,但是為什么要這樣去寫召衔,重寫onMeasure()方法后,里邊的參數(shù)為什么是右移2位祭陷,然后模式給他設(shè)置為MeasureSpec.AT_MOST呢苍凛,接下來我給大家來分析下趣席,為什么這樣去寫,大神可以跳過哈

繼承ListView后醇蝴,大家可以直接點擊super.onMeasure(widthMeasureSpec, heightMeasureSpec);進(jìn)入ListView的源碼宣肚,可以看到

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Sets up mListPadding
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //獲取寬高的模式
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);   //獲取前兩位
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec); //獲取前兩位
       //獲取寬高的值
        int widthSize = MeasureSpec.getSize(widthMeasureSpec); //獲取后面30位
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int childWidth = 0;
        int childHeight = 0;
        int childState = 0;

        mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
        if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
                || heightMode == MeasureSpec.UNSPECIFIED)) {
            final View child = obtainView(0, mIsScrap);

            // Lay out child directly against the parent measure spec so that
            // we can obtain exected minimum width and height.
            measureScrapChild(child, 0, widthMeasureSpec, heightSize);

            childWidth = child.getMeasuredWidth();
            childHeight = child.getMeasuredHeight();
            childState = combineMeasuredStates(childState, child.getMeasuredState());

            if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
                    ((LayoutParams) child.getLayoutParams()).viewType)) {
                mRecycler.addScrapView(child, 0);
            }
        }

        if (widthMode == MeasureSpec.UNSPECIFIED) {
            widthSize = mListPadding.left + mListPadding.right + childWidth +
                    getVerticalScrollbarWidth();
        } else {
            widthSize |= (childState & MEASURED_STATE_MASK);
        }

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

        if (heightMode == MeasureSpec.AT_MOST) {
            // TODO: after first layout we should maybe start at the first visible position, not 0
            heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
        }

        setMeasuredDimension(widthSize, heightSize);

        mWidthMeasureSpec = widthMeasureSpec;
    }

在這里要給大家說一下,widthMeasureSpec和heightMeasureSpec分別都包含了2個信息
Integer.MAX_VALUE是一個32位的值悠栓,右移兩位會將Integer.MAX_VALUE變?yōu)橐粋€30位的值霉涨,最后兩位就是MeasureSpec.AT_MOST

那么由這個可知:

       final int widthMode = MeasureSpec.getMode(widthMeasureSpec);   //獲取前兩位
       final int heightMode = MeasureSpec.getMode(heightMeasureSpec); //獲取前兩位
      //獲取寬高的值
       int widthSize = MeasureSpec.getSize(widthMeasureSpec); //獲取后面30位
       int heightSize = MeasureSpec.getSize(heightMeasureSpec);

heightMode 就是MeasureSpec.AT_MOST
heightSize 就是Integer.MAX_VALUE>>2
因為高度heightMode傳遞的是MeasureSpec.AT_MOST,所以就只會進(jìn)到if (heightMode == MeasureSpec.AT_MOST)中惭适,而不會進(jìn)到heightMode == MeasureSpec.UNSPECIFIED中

為什么使用Integer.MAX_VALUE>>2:
這個Integer.MAX_VALUE是表示32位的一個值笙瑟,然后右移兩位,表示有30為的值癞志,表示大小逮走,后邊的MeasureSpec.AT_MOST是表示model,模式

為什么使用MeasureSpec.AT_MOST:
大家可以看到源碼是重寫onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法今阳,其他的我們可以不去看他师溅,直接看里邊有2個if判斷高度的,寬度的不需要看盾舌,其中第一個if是判斷高度的模式 heightMode == MeasureSpec.UNSPECIFIED墓臭,里邊代碼表示距離上邊+距離下邊+子高度剛好表示一個item的高度,那么現(xiàn)在我們再來回過頭想下妖谴,之前ScrollView嵌套ListView只顯示一條窿锉,我們猜想它應(yīng)該是走的這個if判斷里邊,我們要做的就是不要讓它執(zhí)行這個if膝舅,而是要讓它執(zhí)行下邊的if (heightMode == MeasureSpec.AT_MOST)判斷嗡载,而這里的判斷可以直接點擊進(jìn)去heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);看下
而且它傳遞的也是上邊獲取的高度heightSize,到這里我們就知道為什么第二個參數(shù)是
MeasureSpec.AT_MOST仍稀,如果我們不重寫onMeasure()方法洼滚,其實它里邊的高度heightMeasureSpec默認(rèn)是執(zhí)行heightMode == MeasureSpec.UNSPECIFIED,所以高度才會顯示不全

如果你覺得有幫助技潘,可以關(guān)注我遥巴,我會持續(xù)更新簡書博客,會將自己項目中遇到的問題享幽、遇到的bug铲掐、以及解決方法都會分享出來,也許可能像我這樣的文章或者解決方法網(wǎng)上一搜都有值桩,不過也沒有關(guān)系摆霉,自己覺得還是寫出來會比較踏實,因為這些都是自己用過的、思考過的一些東西携栋,還是覺得蠻有用的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末搭盾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子刻两,更是在濱河造成了極大的恐慌,老刑警劉巖滴某,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件磅摹,死亡現(xiàn)場離奇詭異,居然都是意外死亡霎奢,警方通過查閱死者的電腦和手機户誓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來幕侠,“玉大人帝美,你說我怎么就攤上這事∥钏叮” “怎么了悼潭?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長舞箍。 經(jīng)常有香客問我舰褪,道長,這世上最難降的妖魔是什么疏橄? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任占拍,我火速辦了婚禮,結(jié)果婚禮上捎迫,老公的妹妹穿的比我還像新娘晃酒。我一直安慰自己,他們只是感情好窄绒,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布贝次。 她就那樣靜靜地躺著,像睡著了一般彰导。 火紅的嫁衣襯著肌膚如雪浊闪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天螺戳,我揣著相機與錄音搁宾,去河邊找鬼。 笑死倔幼,一個胖子當(dāng)著我的面吹牛盖腿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼翩腐,長吁一口氣:“原來是場噩夢啊……” “哼鸟款!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起茂卦,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤何什,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后等龙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體处渣,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年蛛砰,在試婚紗的時候發(fā)現(xiàn)自己被綠了罐栈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡泥畅,死狀恐怖荠诬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情位仁,我是刑警寧澤柑贞,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站聂抢,受9級特大地震影響凌外,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜涛浙,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一康辑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧轿亮,春花似錦疮薇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至但骨,卻和暖如春励七,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背奔缠。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工掠抬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人校哎。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓两波,卻偏偏與公主長得像瞳步,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子腰奋,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353