View.Post()保证UI带你装逼带你飞

来源:互联网 发布:mac优酷弹幕怎么设置 编辑:程序博客网 时间:2024/06/16 08:11

前言

日常开发中我们可能会遇到如下问题:

1、在onCreate\onStrart()\onResume()中获取View的宽高为0;
2、在onCreate\onStrart()\onResume()中直接调用Scroview.scrollTo(x,y)没有效果;

那么接下来一探究竟:

原因分析:

因为当onCreate()方法被调用的时候会通过LayoutInflater将xml文件填充到ContentView。
填充过程中只包括创建视图,不包括设置视图大小。而设置视图的大小和具体的位置则是通过布局层层遍历获得的。
如下图:

测量过程由measure(int , int)方法完成,该方法从上到下遍历视图树。在递归的过程中,每个视图都会向下层传递尺寸和规格,当measure方法遍历结束时,每个视图都保存了各自的尺寸信息。第二个过程由layout(int, int, int, int)方法完成,该方法也是由上而下遍历视图树。遍历过程中,每个父视图通过测量过程的结果定位所有姿势图的位置信息。

也就是说我们在onCreate\onStrart()\onResume()的时候我们并不知道什么时候布局测量完成,所以接下来我们去寻找一些方法。

解决方案:

方案一:View.Post()/View.PostDelay() [重点]

我们先来看下源码:

/** * <p>Causes the Runnable to be added to the message queue. * The runnable will be run on the user interface thread.</p> * * @param action The Runnable that will be executed. * * @return Returns true if the Runnable was successfully placed in to the *         message queue.  Returns false on failure, usually because the *         looper processing the message queue is exiting. * * @see #postDelayed * @see #removeCallbacks */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;}

重点是这句话:The runnable will be run on the user interface thread
Runnable是一个接口,不是一个线程,一般线程会实现Runnable。所以如果我们使用匿名内部类是运行在UI主线程的,如果我们使用实现这个Runnable接口的线程类,则是运行在对应线程的。

也就是说我们用View.post(Runnable action)方法里,View获得当前线程(即UI线程)的Handler,然后将action对象post到Handler里。
在Handler里,它将传递过来的action对象包装成一个Message(Message的callback为action),然后将其投入UI线程的消息循环中。
当Handler再次处理该Message时,已经在UI线程里,直接调用runnable的run方法。因此,我们可以毫无顾虑的来更新UI。

也就是说我们通过View.Post()/View.PostDelay()方法就可以实现获取view的宽高,并且Scroview.scrollTo(x,y)可以正常使用了。

view.post(new Runnable() {        @Override        public void run() {        //view的相关操作    }});

所以个人推荐使用View.post()既方便又可以保证指定的任务在视图操作中顺序执行。

方案二:onWindowFocusChanged

使用如下:

@Override   public void onWindowFocusChanged(boolean hasFocus) {    //view的相关操作}

我们看下官方注释:

/** * Called when the current {@link Window} of the activity gains or loses * focus.  This is the best indicator of whether this activity is visible * to the user.  The default implementation clears the key tracking * state, so should always be called. * * <p>Note that this provides information about global focus state, which * is managed independently of activity lifecycles.  As such, while focus * changes will generally have some relation to lifecycle changes (an * activity that is stopped will not generally get window focus), you * should not rely on any particular order between the callbacks here and * those in the other lifecycle methods such as {@link #onResume}. * * <p>As a general rule, however, a resumed activity will have window * focus...  unless it has displayed other dialogs or popups that take * input focus, in which case the activity itself will not have focus * when the other windows have it.  Likewise, the system may display * system-level windows (such as the status bar notification panel or * a system alert) which will temporarily take window input focus without * pausing the foreground activity. * * @param hasFocus Whether the window of this activity has focus. * * @see #hasWindowFocus() * @see #onResume * @see View#onWindowFocusChanged(boolean) */public void onWindowFocusChanged(boolean hasFocus) {}

该方法会在view绘制完成之后调用,所以我们在这个时候去获取view宽高,或者Scroview.scrollTo(x,y)都可以正常运行了。
但是该方法如原注释所说,当Activity的窗口得到焦点和失去焦点时均会被调用一次,如果频繁地进行onResume和onPause,那么onWindowFocusChanged也会被频繁地调用。
所以也要结合具体业务场景。

方案三:ViewTreeObserver

使用ViewTreeObserver的众多回调可以完成这个功能,比如使用OnGlobalLayoutListener这个接口,当view树的状态发生改变或者view树内部的view的可见性发生改变时,onGlobalLayout方法将被回调,因此这是获取view的宽高一个很好的时机。需要注意的是,伴随着view树的状态改变等,onGlobalLayout会被调用多次。

view.getViewTreeObserver().addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() {    @Override    public void onGlobalFocusChanged(View oldFocus, View newFocus) {      //view的相关操作    }});

我们看一下View官方注释:

/** * A view tree observer is used to register listeners that can be notified of global * changes in the view tree. Such global events include, but are not limited to, * layout of the whole tree, beginning of the drawing pass, touch mode change.... * * A ViewTreeObserver should never be instantiated by applications as it is provided * by the views hierarchy. Refer to {@link android.view.View#getViewTreeObserver()} * for more information. */public final class ViewTreeObserver {  //代码省略。。。}

ViewTreeObserver这个类,这个类是用来注册当view tree全局状态改变时的回调监听器,这些全局事件包括很多,比如整个view tree视图的布局,视图绘制的开始,点击事件的改变等等。还有千万不要在应用程序中实例化ViewTreeObserver对象,因为该对象仅是由视图提供的。

综上,个人比较推荐方案一:View.Post()/View.PostDelay() 。

扫码关注公众号“伟大程序猿的诞生“,更多干货等着你~
扫码关注公众号“伟大程序猿的诞生“,更多干货等着你~
扫码关注公众号“伟大程序猿的诞生“,更多干货等着你~

原创粉丝点击