相關文章:
[Android開發之自定義控件(一)---onMeasure詳解](http://blog.csdn.net/dmk877/article/details/49558367)> [Android開發之自定義控件(二)---onLayout詳解](http://blog.csdn.net/dmk877/article/details/49632959)
轉載請注明出處:[http://blog.csdn.net/dmk877/article/details/49734869](http://blog.csdn.net/dmk877/article/details/49734869/)
我相信很多人對getMeasuredWidth和getWidth方法(getMeasuredHeight和getHeight類似這里僅以getMeasuredWidth和getWidth為例)都有過疑惑,并且網上去查閱資料看后也似懂非懂感覺,甚至有網上的講解是錯的,看到這肯定有很多人會說有哪些是錯的?你憑什么說別人是錯的?憑什么讓我們相信你說的是對的?對于這個問題,由于我剛開始查閱資料時看到網上有人說:“實際上在當屏幕可以包裹內容的時候,他們的值是相等的,只有當view超出屏幕后,才能看出他們的區別:getMeasuredWidth()是實際View的大小,與屏幕無關,而getHeight的大小此時則是屏幕的大小。當超出屏幕后getMeasuredWidth()等于getWidth()加上屏幕之外沒有顯示的大小”,相信不止我一個人看到這樣的答案,當時我也覺著有道理由于水平有限,我就將上述說法記在了腦子里,但是隨著學習的深入我發現這種說法是不正確的,下面我將詳細的從源碼的角度來分析這兩者的區別以及為什么上面的說法是錯誤的,相信看完后肯定會有收獲,強烈建議閱讀完上述兩篇文章后再來讀此篇文章。
如果有謬誤歡迎批評指正,如有疑問歡迎留言
1、證明上述觀點錯誤
首先為什么說上面的那種說法是錯誤的?我們來看例子看完例子你就會同意我的說法,我的思路是這樣的
思路:在onWindowFocusChanged方法中控件都測量好了,可以獲取控件的寬和高,我們可以不斷的改變控件的寬和高直至超過屏幕的寬度此時打印getMeasuredWidth和getWidth的值。
代碼很簡單就是通過getMeasuredWidth方法和getWidth方法獲取控件的寬度并打印,代碼如下
~~~
package com.example.customviewpractice;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;
import android.widget.Button;
public class MainActivity extends Activity {
private Button btnTest;
private boolean isFocus=false;
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main1);
btnTest=(Button) findViewById(R.id.btn_test);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(!isFocus&&hasFocus){
Log.i("MainActivity","btnTest.getWidth="+btnTest.getWidth());
Log.i("MainActivity","btnTest.getMeasureWidth="+btnTest.getMeasuredWidth());
isFocus=true;
}
}
}
~~~
布局文件如下
~~~
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/btn_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="測試一" />
</LinearLayout>
~~~
運行效果圖如下

打印結果如下

我們看到當屬性是wrap_content時寬度是64(這里的單位是px,這是480*320的模擬器此時1dp=1px),此時getWidth和getMeasuredWidth的值是相同的。我們將上述布局文件中的btn_test的值設置為200dp運行效果圖如下

打印結果如下

此時getMeasuredWidth和getWidth的值也是相同的,按照上面的說法,假如View的大小沒有超出屏幕的大小那么這兩個值是相同的,這樣看來是沒有錯,但是我們再將布局文件中的btn_test的寬度改為1000dp讓btn_test超出屏幕的大小此時的運行效果如下

打印結果如下

從運行效果我們可以看到此時的button已經超出屏幕,因為button的文字已經看不到了,但是看打印結果呢?仍然都是1000。到這里我說文章開頭那個說法是錯誤的大家認同了吧?
2.getMeasuredWidth和getWidth方法源碼分析
那么getMeasuredWidth和getWidth到底有什么差別呢?現在我們就從源碼出發來詳細分析下這兩者的含義(注:源碼采用的是2.3的源碼)
getMeasuredWidth方法的源碼
~~~
/**
* The width of this view as measured in the most recent call to measure().
* This should be used during measurement and layout calculations only. Use
* {@link #getWidth()} to see how wide a view is after layout.
*
* @return The measured width of this view.
*/
public final int getMeasuredWidth() {
return mMeasuredWidth;
}
~~~
這里我們看到它的返回值是mMeasuredWidth,這個mMeasuredWidth是哪兒來的呢?看過[Android開發之自定義控件(一)---onMeasure詳解](http://blog.csdn.net/dmk877/article/details/49558367)這篇博客后你可能對它并不陌生,它是setMeasuredDimension方法傳遞過來的參數,我們來看看setMeasuredDimension方法的源碼
~~~
/**
* <p>This mehod must be called by {@link #onMeasure(int, int)} to store the
* measured width and measured height. Failing to do so will trigger an
* exception at measurement time.</p>
*
* @param measuredWidth the measured width of this view
* @param measuredHeight the measured height of this view
*/
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= MEASURED_DIMENSION_SET;
}
~~~
看到沒?在調用setMeasuredDimension方法時,所設置的measuredWidth就是這里的mMeasuredWidth,也就是我們的getMeasuredWidth方法的返回值,到這里我們也就明白在measure方法結束后getMeasuredWidth方法就會有值。分析完getMeasuredWidth方法后,我們來看看getWidth同樣來看其源碼
getWidth方法源碼
~~~
/**
* Return the width of the your view.
*
* @return The width of your view, in pixels.
*/
@ViewDebug.ExportedProperty
public final int getWidth() {
return mRight - mLeft;
}
~~~
從源碼中發現它的返回值是mRight-mLeft(關于這兩個值在[Android開發之自定義控件(二)---onLayout詳解](http://blog.csdn.net/dmk877/article/details/49632959)博客中有詳細的解釋),那么這里的mRight和mLeft到底是什么呢?其實它是layout過程傳過來的四個參數中的兩個,那還等什么去看看
layout源碼
~~~
public final void layout(int l, int t, int r, int b) {
boolean changed = setFrame(l, t, r, b);
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~LAYOUT_REQUIRED;
}
mPrivateFlags &= ~FORCE_LAYOUT;
}
~~~
在其中調用了setFrame方法,它的源碼如下
~~~
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
。。。省略部分代碼。。。
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
。。。省略部分代碼。。。
}
return changed;
}
~~~
到這里我們找到了getWidth方法的返回值的兩個參數①mRight②mLeft。這樣大家明白了getWidth返回值了嗎?如果你要是問我layout方法中的那四個參數是哪兒來的,那么我要告訴你請去讀([Android開發之自定義控件(二)---onLayout詳解](http://blog.csdn.net/dmk877/article/details/49632959)),到這里我們也清楚getWidth方法是在layout方法完成后才有的值,所以說在自定義控件的時候在onLayout方法中一般采用getMeasuredWidth來獲得控件的寬度,因為getMeasuredWidth在measure后就有了值,而getWidth在layout才有了值。而在除了onLayout方法中采用getMeasuredWidth方法外在其之外的其他地方一般采用getWidth方法來獲取控件的寬度。從源碼角度分析了getMeasuredWidth和getWidth方法后,我將分享下我在學習過程中的遇到的問題,并在這里分析解答。
(1)怎樣才能讓getMeasuredWidth和getWidth方法的返回值不一樣?
要想回答這個問題,其實不難,因為getMeasuredWidth的值是在setMeasuredDimension方法中設置的,而getWidth的值是onLayout方法中我們傳過去的四個參數,只要setMeasuredDimension方法設置的寬度值和onLayout方法中傳遞過去的mRight-mLeft的值不相等打印結果就會不同,是不是這樣呢?我們通過代碼驗證最具有說服力,我們自定義一個ViewGroup,代碼如下
~~~
package com.example.customviewpractice;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
public class MyViewGroup1 extends ViewGroup {
public MyViewGroup1(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
View view = getChildAt(0);
/**
* 設置寬度值為100,MeasureSpec.EXACTLY是測量模式
*/
measureChild(view, MeasureSpec.EXACTLY + 50, MeasureSpec.EXACTLY + 100);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
View childView = getChildAt(0);
/**
* 設置子View的位置,左上右下
*/
childView.layout(0, 0, 200, 200);
}
}
~~~
? ? 在上面我們看到通過ViewGroup中的measureChild方法為其設置的寬度為50,測量模式是EXACTLY如果不熟悉可以閱讀([Android開發之自定義控件(一)---onMeasure詳解](http://blog.csdn.net/dmk877/article/details/49558367)),而通過layout為其設置的寬度為200-0=200。同樣在Activity的onWindowFocusChanged方法中打印getWidth和getMeasuredWidth的值。
MainActivity的代碼如下
~~~
package com.example.customviewpractice;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;
import android.widget.Button;
public class MainActivity extends Activity {
private Button btn1;
private boolean isFocus=false;
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn1=(Button) findViewById(R.id.btn1);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(!isFocus&&hasFocus){
Log.i("MainActivity","btnTest.getWidth="+btn1.getWidth());
Log.i("MainActivity","btnTest.getMeasureWidth="+btn1.getMeasuredWidth());
isFocus=true;
}
}
}
~~~
布局文件很簡單就是在自定的ViewGroup下面放個Button,如下
~~~
<com.example.customviewpractice.MyViewGroup1 xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="測試一" />
</com.example.customviewpractice.MyViewGroup1>
~~~
運行后效果圖如下

打印結果如下

這時你會發現getWidth=200,是layout方法中傳過去的200-0,getMeasureWidth=50,是setMeasuredDimension方法設置的值,但是顯示在屏幕上的確是一個正方形也就是顯示的是layout設置的寬200,高200。但是這種情況是非常少見的,這里這是為了演示效果才這樣寫,一般情況下getMeasuredWidth和getWidth方法的值是一致的,這里只要記住一般情況下除了在onLayout方法中調用getMeasuredWidth方法外其它的地方用getWidth方法就行了。
(2)在自定義控件中重寫onMeasure然后直接調用super.onMeasure(widthMeasureSpec, widthMeasureSpec)將測量過程按照默認的過程測量,假如按照這種方式的話在onWindowFocusChanaged方法中調用getMeasuredWidth方法你會發現值為0?為什么?
? ? 原因很簡單在調用super.onMeasure(widthMeasureSpec, widthMeasureSpec)方法,在onMeasure方法中會調用setMeasuredDimension方法,如果沒有設置mMinWidth的話一般給寬設置的默認值為0所以getMeasuredDimension方法的返回就為0;這樣我們就了解到如果要想通過getMeasuredWidth獲得值必須要測量即通過一個路徑可以調用到setMeasuredDimension方法,否則一般情況下getMeasuredWidth的值是為0的;
3.總結
①getMeasuredWidth方法獲得的值是setMeasuredDimension方法設置的值,它的值在measure方法運行后就會確定
②getWidth方法獲得是layout方法中傳遞的四個參數中的mRight-mLeft,它的值是在layout方法運行后確定的
③一般情況下在onLayout方法中使用getMeasuredWidth方法,而在除onLayout方法之外的地方用getWidth方法。
如果您發現文章中有錯的地方歡迎批評,指正,我們一同進步。
如果你覺著此篇博客對您有用,就留言或頂一個唄,您的支持是我前進的動力,嘎嘎。。。。
轉載請注明出處:[http://blog.csdn.net/dmk877/article/details/49734869](http://blog.csdn.net/dmk877/article/details/49734869/)