ScrollView中嵌入Listview,当item高度不一样的时候,item展示不全问题
首先在scrollview中嵌入listview,我們需要動態(tài)設(shè)置listview的高度,否則只會展示一行。
public void fixListViewHeight(ListView listView) {// 如果沒有設(shè)置數(shù)據(jù)適配器,則ListView沒有子項,返回。 ListAdapter listAdapter = listView.getAdapter(); int totalHeight = 0; if (listAdapter == null) {return; }for (int i = 0, len = listAdapter.getCount(); i < len; i++) {View listViewItem = listAdapter.getView(i , null, listView); // 計算子項View 的寬高 listViewItem.measure(0, 0); // 計算所有子項的高度和 totalHeight += listViewItem.getMeasuredHeight(); }ViewGroup.LayoutParams params = listView.getLayoutParams(); // listView.getDividerHeight()獲取子項間分隔符的高度 // params.height設(shè)置ListView完全顯示需要的高度 params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); listView.setLayoutParams(params); } 但是當item高度不一樣的時候,或者可能一個item的高度就占滿一個屏幕,或者一個屏幕還不夠展示一個較寬的item的時候,那么就會出現(xiàn)item展示不全問題。我看網(wǎng)上有人說講述的方法進行修正:
public void fixListViewHeight(ListView listView) {// 如果沒有設(shè)置數(shù)據(jù)適配器,則ListView沒有子項,返回。 ListAdapter listAdapter = listView.getAdapter(); int totalHeight = 0; if (listAdapter == null) {return; }for (int i = 0, len = listAdapter.getCount(); i < len; i++) {View listViewItem = listAdapter.getView(i , null, listView); //根據(jù)屏幕寬度計算高度 int width = View.MeasureSpec.makeMeasureSpec(_mActivity.getWindowManager().getDefaultDisplay().getWidth(), View.MeasureSpec.EXACTLY); //依據(jù)寬度計算高度 int height = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.UNSPECIFIED); // 計算子項View 的寬高 listViewItem.measure(0, 0); // 計算所有子項的高度和 totalHeight += listViewItem.getMeasuredHeight(); }ViewGroup.LayoutParams params = listView.getLayoutParams(); // listView.getDividerHeight()獲取子項間分隔符的高度 // params.height設(shè)置ListView完全顯示需要的高度 params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); listView.setLayoutParams(params); } 但是這個方法還是不對的。
出現(xiàn)上述問題,主要原因是我們的item里面只能使用LinearLayout,因為使用其他父布局(如relativelayout)沒有measure方法,會爆出異常。但是LinearLayout方法默認的measure方法使用的是MeasureSpec.EXACTLY 計算方法。
計算子view中高度,是由父view通知計算的: ??-------- 下面是剖析原理,不想看原理可以直接看解決方法,但是不一定可以解決你的問題,因為和布局有關(guān)系(原理中介紹)
那么父容器怎么把這些要求告訴子View呢?MeasureSpec其實就是承擔這種作用:MeasureSpec是父控件提供給子View的一個參數(shù),作為設(shè)定自身大小參考,只是個參考,要多大,還是View自己說了算。先看下MeasureSpec的構(gòu)成,MeasureSpec由size和mode組成,mode包括三種,UNSPECIFIED、EXACTLY、AT_MOST,size就是配合mode給出的參考尺寸,具體意義如下:
-
UNSPECIFIED(未指定),父控件對子控件不加任何束縛,子元素可以得到任意想要的大小,這種MeasureSpec一般是由父控件自身的特性決定的。比如ScrollView,它的子View可以隨意設(shè)置大小,無論多高,都能滾動顯示,這個時候,size一般就沒什么意義。
-
EXACTLY(完全),父控件為子View指定確切大小,希望子View完全按照自己給定尺寸來處理,跟上面的場景1跟2比較相似,這時的MeasureSpec一般是父控件根據(jù)自身的MeasureSpec跟子View的布局參數(shù)來確定的。一般這種情況下size>0,有個確定值。
-
AT_MOST(至多),父控件為子元素指定最大參考尺寸,希望子View的尺寸不要超過這個尺寸,跟上面場景3比較相似。這種模式也是父控件根據(jù)自身的MeasureSpec跟子View的布局參數(shù)來確定的,一般是子View的布局參數(shù)采用wrap_content的時候。
先來看一下ViewGroup源碼中measureChild怎么為子View構(gòu)造MeasureSpec的:
protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {final LayoutParams lp = child.getLayoutParams();final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom, lp.height);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}由于任何View都是支持Padding參數(shù)的,在為子View設(shè)置參考尺寸的時候,需要先把自己的Padding給去除,這同時也是為了Layout做鋪墊。接著看如何getChildMeasureSpec獲取傳遞給子View的MeasureSpec的:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {int specMode = MeasureSpec.getMode(spec);int specSize = MeasureSpec.getSize(spec);int size = Math.max(0, specSize - padding);int resultSize = 0;int resultMode = 0;switch (specMode) {// Parent has imposed an exact size on uscase MeasureSpec.EXACTLY:if (childDimension >= 0) {resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size. So be it.resultSize = size;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size. It can't be// bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// Parent has imposed a maximum size on uscase MeasureSpec.AT_MOST:if (childDimension >= 0) {// Child wants a specific size... so be itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size, but our size is not fixed.// Constrain child to not be bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size. It can't be// bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// Parent asked to see how big we want to becase MeasureSpec.UNSPECIFIED:if (childDimension >= 0) {// Child wants a specific size... let him have itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size... find out how big it should// beresultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size.... find out how// big it should beresultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;}break;}return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
當子View接收到父控件傳遞的MeasureSpec的時候,就可以知道父控件希望自己如何顯示,這個點對于開發(fā)者而言就是onMeasure函數(shù),先來看下View.java中onMeasure函數(shù)的實現(xiàn):
public static int getDefaultSize(int size, int measureSpec) {int result = size;int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {case MeasureSpec.UNSPECIFIED:result = size;break;case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;break;}return result; } 可以看到,如果自定義View沒有重寫onMeasure函數(shù),MeasureSpec.AT_MOST跟MeasureSpec.AT_MOST的表現(xiàn)是一樣的,也就是對于場景2跟3的表現(xiàn)其實是一樣的,也就是wrap_content就跟match_parent一個效果,現(xiàn)在我們知道MeasureSpec的主要作用:父控件傳遞給子View的參考,那么子View拿到后該如何用呢?
接收到父控件傳遞的MeasureSpec后,View應(yīng)該如何用來處理自己的尺寸呢?onMeasure是View測量尺寸最合理的時機,如果View不是ViewGroup相對就比較簡單,只需要參照MeasureSpec,并跟自身需求來設(shè)定尺寸即可,默認onMeasure的就是完全按照父控件傳遞MeasureSpec設(shè)定自己的尺寸的。這里重點講一下ViewGroup,為了獲得合理的寬高尺寸,ViewGroup在計算自己尺寸的時候,必須預(yù)先知道所有子View的尺寸,舉個例子,用一個常用的流式布局FlowLayout來講解一下如何合理的設(shè)定自己的尺寸。
先分析一下FLowLayout流式布局(從左到右)的特點:FLowLayout將所有子View從左往右依次放置,如果當前行,放不開的就換行。從流失布局的特點來看,在確定FLowLayout尺寸的時候,我們需要知道下列信息:
-
父容器傳遞給FlowLayout的MeasureSpec推薦的大小(超出了,顯示不出來,又沒意義)
-
FlowLayout中所有子View的寬度與寬度:計算寬度跟高度的時候需要用的到。
-
綜合MeasureSpec跟自身需求,得出合理的尺寸
public static int resolveSize(int size, int measureSpec) {return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;}public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {final int specMode = MeasureSpec.getMode(measureSpec);final int specSize = MeasureSpec.getSize(measureSpec);final int result;switch (specMode) {case MeasureSpec.AT_MOST:if (specSize < size) {result = specSize | MEASURED_STATE_TOO_SMALL;} else {result = size;}break;case MeasureSpec.EXACTLY:result = specSize;break;case MeasureSpec.UNSPECIFIED:default:result = size;}return result | (childMeasuredState & MEASURED_STATE_MASK); }
可以看到:
-
如果父控件傳遞給的MeasureSpec的mode是MeasureSpec.UNSPECIFIED,就說明,父控件對自己沒有任何限制,那么尺寸就選擇自己需要的尺寸size
-
如果父控件傳遞給的MeasureSpec的mode是MeasureSpec.EXACTLY,就說明父控件有明確的要求,希望自己能用measureSpec中的尺寸,這時就推薦使用MeasureSpec.getSize(measureSpec)
-
如果父控件傳遞給的MeasureSpec的mode是MeasureSpec.AT_MOST,就說明父控件希望自己不要超出MeasureSpec.getSize(measureSpec),如果超出了,就選擇MeasureSpec.getSize(measureSpec),否則用自己想要的尺寸就行了
對于FlowLayout,可以假設(shè)每個子View都可以充滿FlowLayout,因此,可以直接用measureChildren測量所有的子View的尺寸:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int widthSize = MeasureSpec.getSize(widthMeasureSpec);int paddingLeft = getPaddingLeft();int paddingRight = getPaddingRight();int paddingBottom = getPaddingBottom();int paddingTop = getPaddingTop();int count = getChildCount();int maxWidth = 0;int totalHeight = 0;int lineWidth = 0;int lineHeight = 0;int extraWidth = widthSize - paddingLeft - paddingRight;<!--直接用measureChildren測量所有的子View的高度-->measureChildren(widthMeasureSpec, heightMeasureSpec);<!--現(xiàn)在可以獲得所有子View的尺寸-->for (int i = 0; i < count; i++) {View view = getChildAt(i);if (view != null && view.getVisibility() != GONE) {if (lineWidth + view.getMeasuredWidth() > extraWidth) {totalHeight += lineHeight ;lineWidth = view.getMeasuredWidth();lineHeight = view.getMeasuredHeight();maxWidth = widthSize;} else {lineWidth += view.getMeasuredWidth();}<!--獲取每行的最高View尺寸-->lineHeight = Math.max(lineHeight, view.getMeasuredHeight());}}totalHeight = Math.max(totalHeight + lineHeight, lineHeight);maxWidth = Math.max(lineWidth, maxWidth);<!--totalHeight 跟 maxWidth都是FlowLayout渴望得到的尺寸--><!--至于合不合適,通過resolveSize再來判斷一遍,當然,如果你非要按照自己的尺寸來,也可以設(shè)定,但是不太合理-->totalHeight = resolveSize(totalHeight + paddingBottom + paddingTop, heightMeasureSpec);lineWidth = resolveSize(maxWidth + paddingLeft + paddingRight, widthMeasureSpec);setMeasuredDimension(lineWidth, totalHeight); }可以看到,設(shè)定自定義ViewGroup的尺寸其實只需要三部:
-
測量所有子View,獲取所有子View的尺寸
-
根據(jù)自身特點計算所需要的尺寸
-
綜合考量需要的尺寸跟父控件傳遞的MeasureSpec,得出一個合理的尺寸
傳遞給子View的MeasureSpec是父容器根據(jù)自己的MeasureSpec及子View的布局參數(shù)所確定的,那么根MeasureSpec是誰創(chuàng)建的呢?我們用最常用的兩種Window來解釋一下,Activity與Dialog,DecorView是Activity的根布局,傳遞給DecorView的MeasureSpec是系統(tǒng)根據(jù)Activity或者Dialog的Theme來確定的,也就是說,最初的MeasureSpec是直接根據(jù)Window的屬性構(gòu)建的,一般對于Activity來說,根MeasureSpec是EXACTLY+屏幕尺寸,對于Dialog來說,如果不做特殊設(shè)定會采用AT_MOST+屏幕尺寸。這里牽扯到WindowManagerService跟ActivityManagerService,感興趣的可以跟蹤一下WindowManager.LayoutParams 。
-------------------------------- 原理剖析完畢
解決方案:
上述問題解決方案,重寫listview,去重寫measure方法即可:
public class CommListView extends ListView {public CommListView(Context context) {super(context); // TODO Auto-generated constructor stub }public CommListView(Context context, AttributeSet attrs) {super(context, attrs); }public CommListView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle); }@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); }@Override public boolean dispatchTouchEvent(MotionEvent ev) {if(ev.getAction() == MotionEvent.ACTION_MOVE){return true; }return super.dispatchTouchEvent(ev); } }
總結(jié)
以上是生活随笔為你收集整理的ScrollView中嵌入Listview,当item高度不一样的时候,item展示不全问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 自我智造上的进阶
- 下一篇: TP-Link TL-H39RT 无线路