Github项目解析(八)-->Activity启动过程中获取组件宽高的五种方式

转载请标明出处:一片枫叶的专栏

上一个github小项目中我们介绍了防止按钮重复点击的小框架,其实现的核心逻辑是重写OnClickListener的onClick方法,添加防止重复点击的逻辑,即为第二次点击与第一次点击的时间间隔添加阙值,若第二次点击的时间间隔与第一次点击的时间间隔小于阙值,则此次点击无效,再次基础上我们又封装了点击组件验证网络Listener,点击组件验证是否登录Listener等,具体可参考: github项目解析(七)–>防止按钮重复点击

本文中我将介绍一下Android中Activity启动时获取组件宽高的五种方式。我们知道,有时候我们需要在Activity启动的时候获取某一组件的宽或者是高用于动态的更改UI布局文件,但是这时候我们直接通过getWidth和getHeight方法获取是有问题的。为什么这么说呢?这里我们可以下一个测试的例子来验证一下:


问题:

  • 在Activity的启动流程中通过getWidht和getHeight方法获取组件的宽度和高度
/** * 在onCreate方法中调用,用于获取TextView的宽度和高度 */
private void getTextHeightAndWidth() {
        // 我们定义的用于获取宽度和高度的组件
        titleText = (TextView) findViewById(R.id.text_title);

        int height = titleText.getHeight();
        int width = titleText.getWidth();

        Log.i(TAG, "height:" + height + " " + "width:" + width);
    }

这段代码看似是正常没有问题的,但是我们将调用的代码写在onCreate方法的时候,执行这段代码之后,打印的结果:

06-26 20:12:15.356 19453-19453/uuch.com.Android_viewheight I/MainActivity: height:0  width:0

咦?为什么打印的height和width都是0呢?我们在执行一遍呢?结果还是一样的,两个变量都是0,难道这段代码不能再onCreate方法中调用,那么我们试试在onResume方法中调用呢?

只能说然并卵,打印的结果依然是:

06-26 20:52:13.986 19453-19453/uuch.com.Android_viewheight I/MainActivity: height:0  width:0

好吧,问题已经出来了,看样子我们是不能再onCreate方法或者是onResume方法中调用该方法获取组件的宽高的,但是这是为什么呢?平时我们都是通过这个方法来获取组件的宽高的,并且也没问题啊,比如我们将这个方法的调用逻辑写在按钮的点击事件之内呢?我们再来试试。

/** * 这里的button1是我们定义的Button组件,并且我们重写了Button的点击事件,在其中调用了获取组件宽高的方法 */
button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getTextHeightAndWidth();
            }
        });

这样敲完代码之后,我们执行这段代码,界面入下图所示:
《Github项目解析(八)-->Activity启动过程中获取组件宽高的五种方式》” /></p><p>那么我们的打印结果呢?</p><pre><code><span>06</span>-<span>26</span> <span>20</span>:<span>57</span>:<span>08.188</span> <span>22648</span>-<span>22648</span>/uuch<span>.com</span><span>.Android</span>_viewheight I/MainActivity: height:<span>57</span>  width:<span>225</span></code></pre><p>恩?这时候我们发现其打印出了组件的宽和高,那么为什么我们在Activity的onCreate、onResume方法中打印的时候,输出的组件宽高都是为0呢?</p><p><strong><br />原因:</strong></p><p>其实看过我以前写过的 <br /> Android源码解析之(十四)–>Activity启动流程<br />Android源码解析(十七)–>Activity布局加载流程 <br /> Android源码解析(十八)–>Activity布局绘制流程 <br /> 的同学应该对Activity的启动流程和其布局加载绘制流程不陌生,<font>Activity的启动流程和Activity的布局文件加载绘制流程,其实没有相关的关系的,其实两个异步的加载流程</font>,这样我们在Activity的onCreate和onResume方法调用textView.getHeight或者是textView.getWidth方法的时候,其组件并没有执行完绘制流程,因此此时获取到的组件的宽高都是默认的0,也就是无法获取组件的宽和高。</p><p>但是当我们将获取组件宽高的方法卸载按钮的点击事件的时候,由于此时按钮已经显示出来了,所以证明布局文件已经加载绘制完成,这时候点击组件执行组件的获取宽高方法,就能正常的获取到组件的宽和高了。</p><p>这也就是为什么我们在onCreate和onResume方法中调用获取组件宽高都是0,而在按钮的点击事件中获取的时候正常的原因了。</p><p><strong><br />其他解决方案:</strong></p><p>那么如果我们想在Activity的onCreate方法或者是onReusme方法获取组件的宽高怎么办呢?这里提供了以下的五种方式:</p><ul><li>重写Activity的onWindowFocusChanged方法</li></ul><pre><code><span>/** * 重写Acitivty的onWindowFocusChanged方法 */</span> 
<span>@Override</span>
    <span>public</span> <span>void</span> <span>onWindowFocusChanged</span>(<span>boolean</span> hasFocus) {
        <span>super</span>.onWindowFocusChanged(hasFocus);
        <span>/** * 当hasFocus为true的时候,说明Activity的Window对象已经获取焦点,进而Activity界面已经加载绘制完成 */</span>
        <span>if</span> (hasFocus) {
            <span>int</span> widht = titleText.getWidth();
            <span>int</span> height = titleText.getHeight();
            Log.i(TAG, <span> + widht + " " + " height:" + height; } }


说明:

这样重写onWindowFocusChanged方法,当获取焦点的时候我们就可以通过getWidth和getHeight方法得到组件的宽和高了。但是这时候这个方法的逻辑可能会执行多次,也就是说只要我们的Activity的window对象获取了焦点就会执行该语句,所以我们需要做一些逻辑判断,让它在我们需要打印获取组件宽高的时候在执行。

  • 为组件添加OnGlobalLayoutListener事件监听
/** * 为Activity的布局文件添加OnGlobalLayoutListener事件监听,当回调到onGlobalLayout方法的时候我们通过getMeasureHeight和getMeasuredWidth方法可以获取到组件的宽和高 */
private void initOnLayoutListener() {
        final ViewTreeObserver viewTreeObserver = this.getWindow().getDecorView().getViewTreeObserver();
        viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                Log.i(TAG, "开始执行onGlobalLayout().........");
                int height = titleText.getMeasuredHeight();
                int width = titleText.getMeasuredWidth();

                Log.i(TAG, "height:" + height + " width:" + width);
           // 移除GlobalLayoutListener监听 
                   MainActivity.this.getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });
    }

说明:

需要说明的是这里的onGlobalLayout方法会在Activity的组件执行完onLayout方法之后执行,这里的onLayout方法主要用于计算组件的宽高操作,具体可参考:Android源码解析(十八)–>Activity布局绘制流程,这样当我们计算完组件的宽高之后再执行获取组件的宽高操作,自然能够获取到组件的宽度和高度。

  • 为组件添加OnPreDrawListener事件监听
/** * 初始化viewTreeObserver事件监听,重写OnPreDrawListener获取组件高度 */
    private void initOnPreDrawListener() {
        final ViewTreeObserver viewTreeObserver = this.getWindow().getDecorView().getViewTreeObserver();
        viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                Log.i(TAG, "开始执行onPreDraw().........");
                int height = titleText.getMeasuredHeight();
                int width = titleText.getMeasuredWidth();

                Log.i(TAG, "height:" + height + " width:" + width);
                // 移除OnPreDrawListener事件监听
                MainActivity.this.getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this);
                return true;
            }
        });
    }

说明:

需要说明的是这里的onPreDraw方法会在Activity的组件执行onDraw方法之前执行,熟悉我们Activity组件加载绘制流程的同学应该知道,这里的onDraw方法主要用于执行真正的绘制组件操作,而这时候我们已经计算出来了组件的位置,宽高等操作,这样之后再执行获取组件的宽高操作,自然能够获取到组件的宽度和高度。

  • 使用View.post方法获取组件的宽高
/** * 使用View的post方法获取组件的宽度和高度 */
private void initViewHandler() {
        titleText.post(new Runnable() {
            @Override
            public void run() {
                int width = titleText.getWidth();
                int height = titleText.getHeight();

                Log.i(TAG, "initViewHandler height:" + height + " width:" + width);
            }
        });
    }

说明:

这里的view的post方法底层使用的是Android的异步消息机制,消息的执行在MainActivity主进程Loop执行之后,所以这时候也可以获取组件的宽高,更多关于Android中异步消息的内容可参考我的:Android源码解析之(二)–>异步消息机制。

  • 通过Handler对象使用异步消息获取组件的宽高
/** * 在onCreate方法中发送异步消息,在handleMessage中获取组件的宽高 */
private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 101) {
                int width = titleText.getWidth();
                int height = titleText.getHeight();

                Log.i(TAG, "initViewHandler height:" + height + " width:" + width);
            }
        }
    };

说明:
和上面使用view.post方法类似,这里用的是异步消息获取组件的宽高,而这里的异步消息的执行过程是在主进程的主线程的Activity绘制流程之后,所以这时候可以获取组件的宽高。


其他说明:

需要说明的是如果对Acitivty的布局加载绘制流程比较了解的同学应该知道,界面的显示过程经过了:测量位置,测量大小,绘制,三个操作流程。而我们获取组件的宽高就是获取组件的大小,所以我们获取的代码必须要在组件执行完测量大小之后,而无论是我们添加的onWindowFocusChanged方法,onPreDrawListener监听,已经onGlobalLayoutListener监听其实都是在组件完成了测量大小之后执行了,因此这时候我们能够正确的获取到组件的宽和高。


总结:

  • 该类库主要是介绍了五种我们在Activity的启动过程中获取组件宽高的方式;

  • 通过重写onWidnowFocusChanged方法获取组件宽高的方式,可能会回调几次,这点需要我们注意;

  • 项目保存地址:Android-viewheight,欢迎star和follow

另外对github项目,开源项目解析感兴趣的同学可以参考我的:
Github项目解析(一)–>上传Android项目至github
Github项目解析(二)–>将Android项目发布至JCenter代码库
Github项目解析(三)–>Android内存泄露监测之leakcanary
Github项目解析(四)–>动态更改TextView的字体大小
Github项目解析(五)–>Android日志框架
Github项目解析(六)–>自定义实现ButterKnife框架
Github项目解析(七)–>防止按钮重复点击

    原文作者:一片枫叶_刘超
    原文地址: http://blog.csdn.net/qq_23547831/article/details/51764304
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞