揭秘Android中如何可靠获取视图的宽度和高度

193 阅读4分钟

1. 背景

在Android的Activity生命周期中,onStart() 方法标志着Activity对用户可见了,但此时Activity的视图(View)可能还没有完全准备好进行绘制。这是因为onStart() 方法调用时,Activity的窗口(Window)已经准备好被展示给用户,但是具体的绘制流程还没有开始或者还没有完成。

2. 分析

这里涉及到几个关键的概念和步骤来解释为什么页面可见了但还无法获取到宽高:

  • View的测量(Measure):在绘制过程开始之前,Android需要知道每个View的大小(宽和高)。这个过程是通过View的measure()方法完成的,它递归地遍历View树,根据每个View的布局参数(LayoutParams)和父容器的约束来计算每个View的大小。

  • View的布局(Layout):在测量过程之后,Android会调用View的layout()方法来确定每个View在其父容器中的确切位置。这个过程也是递归的,它确保所有的子View都被正确地放置。

  • View的绘制(Draw):最后,Android会调用View的draw()方法来绘制每个View。这个过程涉及到绘制背景、内容以及子View等。

3. 原因

onStart()方法中,虽然Activity的窗口已经对用户可见,但上述的绘制三部曲(测量、布局、绘制)可能还没有开始,或者刚刚开始但还没有完成。这是因为onStart()仅仅是Activity生命周期中的一个状态点,表示Activity即将变得可见,但并不意味着所有的初始化工作(包括视图的测量、布局和绘制)都已完成。

4. 更可靠的方式:在View的onSizeChanged()回调或ViewTreeObserver.OnGlobalLayoutListener中监听视图尺寸的变化

为了能够在Activity的某个时刻获取到视图的宽高,你通常需要在onResume()之后进行尝试,因为onResume()表示Activity已经准备好与用户交互,并且视图的绘制流程很可能已经完成或接近完成。然而,即使在onResume()中,也不能保证视图的宽高一定可用,特别是当涉及到异步加载的视图或复杂的布局时。因此,一种更可靠的做法是在View的onSizeChanged()回调或ViewTreeObserver.OnGlobalLayoutListener中监听视图尺寸的变化,这些回调通常会在视图的大小最终确定时被调用。

5. 示例代码

以下是一些示例代码,展示了如何使用ViewTreeObserver.OnGlobalLayoutListener来监听视图尺寸的变化:

使用 ViewTreeObserver.OnGlobalLayoutListener

public class MyActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);

        final View rootView = findViewById(android.R.id.content).getRootView();
        
        // 添加 OnGlobalLayoutListener 监听器
        rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                // 移除监听器以避免多次触发
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                } else {
                    rootView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                }

                // 获取视图的宽度和高度
                int width = rootView.getWidth();
                int height = rootView.getHeight();

                // 打印宽度和高度
                Log.d("MyActivity", "Width: " + width + ", Height: " + height);

                // 在这里可以执行其他操作,例如调整布局或初始化组件
            }
        });
    }
}

使用 View.onSizeChanged()

如果你有一个自定义的View,可以在onSizeChanged()方法中处理视图尺寸的变化:

public class MyCustomView extends View {

    public MyCustomView(Context context) {
        super(context);
    }

    public MyCustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        // 视图尺寸发生变化时执行的操作
        Log.d("MyCustomView", "Width: " + w + ", Height: " + h);

        // 在这里可以执行其他操作,例如调整布局或初始化组件
    }
}

使用 View.post()

另一种方法是使用View.post()方法,在视图绘制完成后执行代码:

public class MyActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);

        final View rootView = findViewById(android.R.id.content).getRootView();
        
        // 使用 post() 方法
        rootView.post(new Runnable() {
            @Override
            public void run() {
                // 获取视图的宽度和高度
                int width = rootView.getWidth();
                int height = rootView.getHeight();

                // 打印宽度和高度
                Log.d("MyActivity", "Width: " + width + ", Height: " + height);

                // 在这里可以执行其他操作,例如调整布局或初始化组件
            }
        });
    }
}

6. 总结

通过以上几种方法,你可以在视图的尺寸确定后获取其宽度和高度,从而避免在onStart()onResume()中遇到视图尺寸不可用的问题。这些方法包括:

  • ViewTreeObserver.OnGlobalLayoutListener:适用于Activity和Fragment。
  • View.onSizeChanged():适用于自定义View。
  • View.post():适用于需要在视图绘制完成后执行的操作。

选择哪种方法取决于你的具体需求和应用场景。