Android开发之自定义控件(一)---onMeasure详解Android开发之自定义控件(二)---onLayout详解
转载请注明出处: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;
}
}
}
布局文件如下
运行效果图如下
打印结果如下
我们看到当属性是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详解这篇博客后你可能对它并不陌生,它是setMeasuredDimension方法传递过来的参数,我们来看看setMeasuredDimension方法的源码
/**
* 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.
*
* @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详解博客中有详细的解释),那么这里的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详解),到这里我们也清楚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详解),而通过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,如下
运行后效果图如下
这时你会发现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