项目中,经常会遇到要获取View宽高的需求。那么我们什么时机获取呢?比如onCreate方法中是获取不到View的宽度和高度的。
一.三种常见的实现方式
1.onWindowFocusChanged生命周期方法获取
<1> 代码
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.Textview1);
getViewWidthAndHeight("onCreate");
}
@Override
protected void onStart() {
super.onStart();
getViewWidthAndHeight("onStart");
}
@Override
protected void onResume() {
super.onResume();
getViewWidthAndHeight("onResume");
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
getViewWidthAndHeight("onWindowFocusChanged");
}
}
private void getViewWidthAndHeight(String method) {
int width = textView.getWidth();
int height = textView.getHeight();
Log.d("MainActivity", method + "方法获取View宽度----:" + width);
Log.d("MainActivity", method + "方法获取View高度----:" + height);
}
}
<2> 结果
D/MainActivity: onCreate方法获取View宽度----:0
D/MainActivity: onCreate方法获取View高度----:0
D/MainActivity: onStart方法获取View宽度----:0
D/MainActivity: onStart方法获取View高度----:0
D/MainActivity: onResume方法获取View宽度----:0
D/MainActivity: onResume方法获取View高度----:0
D/MainActivity: onWindowFocusChanged方法获取View宽度----:800
D/MainActivity: onWindowFocusChanged方法获取View高度----:800
<3> 说明
由上述结果可知,在onCreate方法,onStart方法,onResume方法中获取的View的宽高都是0。而在onWindowFocusChanged方法中可以正常的获取View的宽高。
(1) onCreate方法,onStart方法,onResume方法中无法获取View宽高的原因:
这几个生命周期的方法,仅仅说明View刚刚解析绘制完成。所以无法获取View的宽高。详情
Activity启动流程源码讲解(上)
https://blog.csdn.net/weixin_37730482/article/details/69569670
Activity启动流程源码详解(下)
https://blog.csdn.net/weixin_37730482/article/details/70829995
(2) onWindowFocusChanged方法可以获取View的宽高的原因:
Called when the current {@link Window} of the activity gains or loses focus.
This is the best indicator of whether this activity is the entity with which the user actively interacts.
The default implementation clears the key tracking state, so should always be called.
当 当前活动的窗口 获得或失去焦点时调用。
这是该活动是否是用户积极交互的实体的最佳指示器。
默认实现清除键跟踪状态,因此应该始终调用。
2.View.post方法获取
<1> 代码
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.Textview1);
getViewWidthAndHeight();
}
private void getViewWidthAndHeight() {
textView.post(() -> {
int width = textView.getWidth();
int height = textView.getHeight();
Log.d("MainActivity", "获取View宽度----:" + width);
Log.d("MainActivity", "获取View高度----:" + height);
});
}
}
<2> 结果
D/MainActivity: 获取View宽度----:800
D/MainActivity: 获取View高度----:800
<3> 说明
View.post方法。内部其实是使用的Handler通信。
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
其中的Handler使用的是View类的内部静态类AttachInfo类的Handler。
final static class AttachInfo {
}
View.post方法可以获取View的宽高原因:
因为具体的View解析,绘制内部也使用了Handler。只不过系统的使用的Handler都是同步机制的。所以优先处理。那么优先处理了系统的View。也就是说View已经绘制完成。就可以获取View的宽高了。具体Handler的同步机制
Handler详解(下)
https://blog.csdn.net/weixin_37730482/article/details/72864096
3.ViewTreeObserver获取View的宽高
<1> 代码
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.Textview1);
getViewWidthAndHeight();
}
private void getViewWidthAndHeight() {
ViewTreeObserver observer = textView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(() -> {
int width = textView.getWidth();
int height = textView.getHeight();
Log.d("MainActivity", "获取View宽度----:" + width);
Log.d("MainActivity", "获取View高度----:" + height);
});
}
}
<2> 结果
D/MainActivity: 获取View宽度----:800
D/MainActivity: 获取View高度----:800
<3> 说明
ViewTreeObserver:当View树的状态发生改变或者View树内部的View的可见性发生改变,onGlobalLayout方法就会回调,因此这是获取View的宽高一个很好的时机,需要注意的是,伴随着View树状态的改变,这个方法也会被调用多次。所以使用的时候需要谨慎。
二.getWidth()&getMeasuredWidth()和getHeight()&getMeasuredHeight()区别
上述举例都是使用的getWidth()&getHeight()没有使用getMeasuredWidth()&getMeasuredHeight()。那么他们有什么区别呢?我们一起看一下。
由于宽度和高度类似。我们这里就只是按照宽度的讲解。
1.解释
getWidth():获取的是这个view最终显示的大小,这个大小有可能等于原始的大小也有可能不等于原始大小。
getMeasuredWidth():获取的是View原始的大小,也就是这个view在XML文件中配置或者是代码中设置的大小。
2.源码分析
getWidth()源码
View类
/**
* The distance in pixels from the left edge of this view's parent
* to the right edge of this view.
* {@hide}
*/
@ViewDebug.ExportedProperty(category = "layout")
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
protected int mRight;
/**
* The distance in pixels from the left edge of this view's parent
* to the left edge of this view.
* {@hide}
*/
@ViewDebug.ExportedProperty(category = "layout")
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
protected int mLeft;
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
...
}
/**
* Return the width of your view.
*
* @return The width of your view, in pixels.
*/
@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
return mRight - mLeft;
}
从源码分析,在layout方法中,会操作mRight和mLeft。然后getWidth方法使用mRight-mLeft获取宽度。
getMeasuredWidth()源码
View类中
/**
* Width as measured during measure pass.
* {@hide}
*/
@ViewDebug.ExportedProperty(category = "measurement")
@UnsupportedAppUsage
int mMeasuredWidth;
/**
* Bits of {@link #getMeasuredWidthAndState()} and
* {@link #getMeasuredWidthAndState()} that provide the actual measured size.
*/
public static final int MEASURED_SIZE_MASK = 0x00ffffff;
/**
* Like {@link #getMeasuredWidthAndState()}, but only returns the
* raw width component (that is the result is masked by
* {@link #MEASURED_SIZE_MASK}).
*
* @return The raw measured width of this view.
*/
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
从源码上来看,getMeasuredWidth() 获取的是mMeasuredWidth的这个值。这个值是一个8位的十六进制的数字,高两位表示的是这个measure阶段的Mode的值,这里mMeasuredWidth & MEASURED_SIZE_MASK表示的是测量阶段结束之后,view真实的值。而且这个值会在调用了setMeasuredDimensionRaw()函数之后会被设置。所以getMeasuredWidth()的值是measure阶段结束之后得到的view的原始的值。
3.举例说明
自定义ViewGroup
public class MyLayout extends LinearLayout {
public MyLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
int count = getChildCount();
for (int i = 0; i < count; i++) {
View view = getChildAt(i);
view.layout(view.getLeft(), view.getTop(), view.getRight() + 100, view.getBottom());
}
}
}
ViewGroup的子View右边+100。
布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".MainActivity">
<com.wjn.componentdemo.MyLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/TextView1"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@color/cardview_shadow_start_color"
android:gravity="center"
android:text="TextView1">
</TextView>
</com.wjn.componentdemo.MyLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Activity代码
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.TextView1);
getViewWidthAndHeight();
}
private void getViewWidthAndHeight() {
ViewTreeObserver observer = textView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(() -> {
int width = textView.getWidth();
int measuredWidth = textView.getMeasuredWidth();
Log.d("MainActivity", "获取View宽度width----:" + width);
Log.d("MainActivity", "获取View高度measuredWidth----:" + measuredWidth);
});
}
}
结果
D/MainActivity: 获取View宽度width----:900
D/MainActivity: 获取View高度measuredWidth----:800
总结
getWidth获取的是最终的宽度,getMeasuredWidth获取的布局中或者Java代码中设置的宽度。如果不操作特殊的操作,比如在onLayout方法中操作right。两个方法获取的宽度相等。