• Android: 在onCreate()中获得对象尺寸


    onCreate() 中 View 尚未绘制完成

    很多时候,我们需要在某个界面刚刚加载的时候,执行一些对 View 进行操作的代码,通常我们把这些代码放在 ActivityonCreate() 方法或 FragmentonCreateView() 方法中。但因为在这些 Hook 方法被调用时,View 并没有绘制完成,很多操作不能生效。

    比如,在 onCreate() 中调用某个按钮的 myButton.getHeight(),得到的结果永远是0。不想花时间慢慢看的同学,可以直接去看最后结论中的解决方案。

    Button myButton = null;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_welcome);
        myButton = (Button) findViewById(R.id.button1);
        
        // 尝试在 onCreate() 中获取控件尺寸
        int h = myButton.getHeight();
        Log.i(TAG, "onCreate(): Height=" + h);  // 得到的结果是 Height=0
        
        // ...
    }
    

    类似的情况还有很多,笔者之前自己写了一个下拉刷新的 ListView,希望在 Fragment 完成绘制后,立刻执行一次刷新,并将代码写在了 onCreateView() 中。结果是,列表确实进行了刷新操作,但是没有下拉刷新的动画。

    更晚调用的生命周期函数

    笔者开始以为,既然 onCreate() 中,控件尚未绘制完成,那么将代码写在更晚执行的一些生命周期函数中,问题是不是能得到解决呢?之后笔者分别在 Activity 中的一些常用的 Hook 函数中,尝试获取控件的尺寸,得到如下结果(按被执行的顺序排列):

    onCreate(): Height=0
    onStart(): Height=0
    onPostCreate(): Height=0
    onResume(): Height=0
    onPostResume(): Height=0
    onAttachedToWindow(): Height=0
    onWindowsFocusChanged(): Height=1845
    

    可以看到,直到 onWinodwsFocusChanged() 函数被调用,我们才能得到正确的控件尺寸。其他 Hook 函数,包括在官方文档中,描述为在 Activity 完全启动后才调用的 onPostCreate()onPostResume() 函数,均不能得到正确的结果。

    遗憾的是,虽然在 onWinodwsFocusChanged() 函数中,可以得到正确的控件尺寸。但这只在 Activity 中奏效,而在 Fragment 中,该方法并不能生效。

    更多的解决方案

    1. 使用 ViewTreeObserver 提供的 Hook 方法。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_welcome);
        myButton = (Button) findViewById(R.id.button1);
        
        // 向 ViewTreeObserver 注册方法,以获取控件尺寸
        ViewTreeObserver vto = myButton.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            public void onGlobalLayout() {
                int h = myButton.getHeight();
                Log.i(TAG, "Height=" + h); // 得到正确结果
    
                // 成功调用一次后,移除 Hook 方法,防止被反复调用
                // removeGlobalOnLayoutListener() 方法在 API 16 后不再使用
                // 使用新方法 removeOnGlobalLayoutListener() 代替
                myButton.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            }	
        });
        
        // ...
    }
    

    该方法在 onGlobalLayout() 方法将在控件完成绘制后调用,因而可以得到正确地结果。该方法在 Fragment 中,也可以使用。

    2. 使用 View 的 post() 方法

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_welcome);
        myButton = (Button) findViewById(R.id.button1);
        
        // 使用myButton 的 post() 方法
        myButton.post(new Runnable() {
            @Override
            public void run() {
                int h = myButton.getHeight();
                Log.i(TAG, "Height=" + h); // 得到正确结果
            }
        });
        
        // ...
    }
    

    该方法同样在 Fragment 中适用,是目前笔者发现的最佳解决方案。

    注意:通过调用测量对象的 post() 方法注册的 Runnable 总在对象完全绘制后才调用,所以可以得到正确的结果。但直接在 onCreate() 中,使用 new Handler().post(mRunnable) 并不能得到正确的结果。

    很多教程中,通过延迟一个毫秒数,即 new Handler().postDelayed(mRunnable, 500),来调用执行实际测量工作的 Runnable。这样虽然能得到正确的结果,但显然不是一个好的解决方案:太小的毫秒数不能保证控件完成绘制,太大的毫秒数,会严重破坏用户的操作体验。

    结论

    如果需要在某个 View 完成绘制后,立刻执行一段代码(如需要获取控件尺寸),最优雅的做法是:在 onCreate() 等生命周期函数中,调用该 Viewpost() 方法。

    (参见上面的代码)

    使用 ViewTreeObserver 注册 OnGlobalLayoutListener,或使用 ActivityonWinodwsFocusChanged() 方法,也可以达到相同目的。

  • 相关阅读:
    Qt与VC编程合作起龌龊
    谈谈误差补偿
    谈谈单片机编程入门方法
    小议设计模式
    创建VS工程使用神经网络库——FANN
    设计一个循环缓冲区模板
    旧文备份:FFTW介绍
    旧文备份:硬盘MBR引导记录损坏的一种修复方法
    How to disable the unused macros warning? --rust
    看阿里架构师是如何在高并发的情景下运筹帷幄
  • 原文地址:https://www.cnblogs.com/superlcr/p/3940170.html
Copyright © 2020-2023  润新知