編輯:關於Android編程
在自定義ListView中,需要將下拉刷新的View在初始化的時候設置padding隱藏起來,這時就要在初始化的時候獲得要加載的布局View的高度。
private View headView; headView = inflater.inflate(R.layout.header, null);
如果接下來調用:
headView.getHeight(); headView.getMeasuredHeight();
我們知道都會返回0,原因是getMeasuredHeight要在measure後才有值,getHeight要在layout後才有值。要在這裡取得加載的布局的寬高,需要程序員調用measure測量出該布局的寬高。以前在寫下拉ListView的時候有這樣一段代碼,一直不太明白,今天研究了一下:
private void measureView(View child) { ViewGroup.LayoutParams lp = child.getLayoutParams(); if(lp == null){ lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } //headerView的寬度信息 int childMeasureWidth = ViewGroup.getChildMeasureSpec(0, 0, lp.width); int childMeasureHeight; if(lp.height > 0){ childMeasureHeight = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); //最後一個參數表示:適合、匹配 } else { childMeasureHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);//未指定 } //System.out.println("childViewWidth"+childMeasureWidth); //System.out.println("childViewHeight"+childMeasureHeight); //將寬和高設置給child child.measure(childMeasureWidth, childMeasureHeight); }
在inflate完布局後,調用measureView(headView),然後再調用getMeasureHeight函數就可以獲得實際高度了。
接下來從原理上分析一下這段代碼:
通過inflater.inflate(R.layout.header, null);將XML轉化為View的時候,是沒有設置View的LayoutParams的,就會進入到lp ==null條件中new一個LayoutParams。接下來設置View的childMeasureWidth和childMeasureHeight這點比較費解。
childMeasureWidth:
調用ViewGroup的getChildMeasureSpec傳遞的第一個參數(父類的spec)竟然是0,傳進0表示父類的spec是未指定的MeasureSpec.UNSPECIFIED模式,size也為0。那麼該函數返回的值就是一個UNSPECIFIED類型size為0的值。
childMeasureHeight:
和上面類似,如果lp.height > 0就表示設置的是一個精確值,接下來會封裝一個EXACTLY和lp.height的值給childMeasureHeight,然後這裡lp.height是wrap_content,他是個負值(-2),所以會封裝一個未指定UNSPECIFIED和size為0的值給childMeasureHeight。
即childMeasureWidth和childMeasureHeight兩個參數均為0。
關於傳遞給child的widthMeasureSpec和heightMeasureSpec可以通過打印出的結果看出來確實是這樣的。
然後將這兩個值作為參數傳遞給子View的measure函數。
上次在張鴻洋大神的博客看到在劃動刪除代碼裡這樣寫,獲取PopupWindow的寬高之前調用了mPopupWindow.getContentView().measure(0, 0);獲取他的寬高。
View view = mInflater.inflate(R.layout.delete_btn, null); mDelBtn = (Button) view.findViewById(R.id.id_item_btn); mPopupWindow = new PopupWindow(view, LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); /** * 先調用下measure,否則拿不到寬和高 */ mPopupWindow.getContentView().measure(0, 0); mPopupWindowHeight = mPopupWindow.getContentView().getMeasuredHeight(); mPopupWindowWidth = mPopupWindow.getContentView().getMeasuredWidth();
這樣看來,其實和一開始給的代碼是一樣的。因為分析完開始的代碼,我們發現最後執行child.measure(childMeasureWidth, childMeasureHeight);的時候兩個參數就是0。
我最初很難理解父布局傳遞給子布局一個UNSPECIFIED和0的值。因為默認情況下對於View來說會直接設置:
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
裡面的函數前面都學習過,很明顯會將傳過來的0設置給該View。但是調用這段代碼後卻是得到了真事的尺寸不是0。
帶著問題,我將測量的布局僅僅寫一個View,命名為view_layout.xml:
還有一個View被LinearLayout包裹著,viewgroup_layout.xml:
還有一個textview_layout.xml:
activity_main.xml
然後在onCerate的時候執行下面代碼:
inflater = LayoutInflater.from(this); mainLayout = (LinearLayout) findViewById(R.id.mainLayout); view = inflater.inflate(R.layout.view_layout, null); textview = inflater.inflate(R.layout.textview_layout, null); viewgroup = inflater.inflate(R.layout.viewgroup_layout, null); measureView(view); measureView(textview); measureView(viewgroup); Log.i(TAG, "view的width和height分別是:" + view.getMeasuredWidth()+ " " + view.getMeasuredHeight()); Log.i(TAG, "textview的width和height分別是:" + textview.getMeasuredWidth()+ " " + textview.getMeasuredHeight()); Log.i(TAG, "viewgroup的width和height分別是:" + viewgroup.getMeasuredWidth()+ " " + viewgroup.getMeasuredHeight());
得到的結果是:
vcHLVmlld7XEs9+056OsxMfDtNTavavL+8PHzO2807W9uLiyvL7Wo6zU2bvxyKHS1M/CsuLBv7/tuN+jrNXiyrG1xL/tuN+y4sG/0qrU2m9uV2luZG93Rm9jdXNDaGFuZ2VkwO/D5qGjCjxwPjwvcD4KPHByZSBjbGFzcz0="brush:java;">mainLayout.addView(view); mainLayout.addView(textview); mainLayout.addView(viewgroup); @Override public void onWindowFocusChanged(boolean hasFocus) { Log.i(TAG, "獲得焦點後"); // Log.i(TAG, "view的width和height分別是:" + view.getMeasuredWidth()+ " " + view.getHeight()); Log.i(TAG, "textview的width和height分別是:" + textview.getMeasuredWidth()+ " " + textview.getMeasuredHeight()); Log.i(TAG, "viewgroup的width和height分別是:" + viewgroup.getMeasuredWidth()+ " " + viewgroup.getMeasuredHeight()); super.onWindowFocusChanged(hasFocus); }
打印出來的Log如下:
View的寬高竟然有值了!原因是什麼?在調用addView的時候會使試圖重新繪制!至於寬和高為什麼是這樣的,那是因為調用generateDefaultLayoutParams返回的寬為match_parent高為wrap_content。最終傳測量該View是的onMeasure的參數是AT_MOST和ScreenSize。因為是個View,直接調用setMeasuredDimension設置。
後面兩個出現0的原因是View將屏幕占滿了。如果不調用mainLayout.addView(view);可以看到:
改成"horizontal"後,打印信息如下:
至於高度,textview為15,由於是最外層,最終為包裹內容。Viewgroup的最終寬高為50dp,由於我的手機像素密度為0.75,因此換算為像素為38,結果一致。
這就告訴我,自己調用onMeasure只是暫時拿到了View的寬高測量值,這個值與傳入的父布局的widthMeasureSpec, heightMeasureSpec有關。實際上添加到布局的真實寬高可能和這個值不一樣。
ListView中也有類似的方法,和開始的方法基本一樣。
private void measureItem(View child) { ViewGroup.LayoutParams p = child.getLayoutParams(); if (p == null) { p = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, mListPadding.left + mListPadding.right, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); }
1、概述 上一篇已經基本給大家介紹了如何自定義ViewGroup,如果你還不了解,請查看:Android 手把手教您自定ViewGroup ,本篇將使用上篇
讓我們一起學習一下模擬器的使用。本文內容如下: 模擬器和真機的比較 創建Android模擬器(emulator) 運行Android模擬器 設置簡體中文語言界面
Android手勢密碼LockPatternView、LockPasswordUtils、LockPatternUtils在使用別人寫的這個手勢密碼的時候,我們通常是有自
android是因為我的興趣而自學的,當初學完java se感覺迫切需要實現下自己的技能,然後看到身邊的手機,就決定學下android編個app玩下。現在斷斷續續倒騰了兩