专栏首页伟大程序猿的诞生View.Post()保证UI带你装逼带你飞

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

前言

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

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() 。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android浸入式

    现在比较流行的就是浸入式,也就是状态栏颜色和当前页面颜色保持一致,为了有更好的视觉效果。 首先: 在setContentView()之前加上如下代码 ...

    先知先觉
  • 【React Native 安卓开发】----(View实战之仿携程)【第三篇】

    如图:我们可以想到先做三个View 这三个View使用FlexBox平分,flex都为1,这里的flex其实就相当于安卓里面的weight权重的概念。

    先知先觉
  • Android自动化测试【初级篇】-- Monkey测试

    Monkey–猴子,顾名思义,就像一只猴子, 在电脑面前,乱敲键盘在测试。 Monkey是一个运行在模拟器或实际设备中的测试工具,他向系统发送伪随机的用户事...

    先知先觉
  • Marketing Cloud前台UI应用的名称

    版权声明:本文为博主汪子熙原创文章,未经博主允许不得转载。 https://jerry.blog....

    Jerry Wang
  • 使用S/4HANA里的Smart Business消费通过CDS view暴露的OData服务

    In my previous blog Build Chart and Table representation via Analytics Path Fram...

    Jerry Wang
  • 总结:如何找到一份机器学习的工作

    我选择了公司的校招中比较严格的(top5%)一个jd要求,我们看下如何拿下这个offer。

    sladesal
  • 一个网友问的该不该加入公司Share Matching Plan

    这个网友的回复比较中肯: I would do it ONLY if the following is already in place:

    Jerry Wang
  • 场景深度学习

    用户1908973
  • 【PAT甲级】 List Sorting

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    喜欢ctrl的cxk
  • 自定义 Windows PowerShell 和 cmd 的字体

    2017-11-22 16:26

    walterlv

扫码关注云+社区

领取腾讯云代金券