不可不知的开发技巧之View.Post()

作者: 原来是控控 | 来源:发表于2016-08-14 21:10 被阅读7435次

    稍微有点经验的安卓开发人员应该都知道View类的post和postDelayed方法。我们知道调用这个方法可以保证在UI线程中进行需要的操作,方便地进行异步通信。以下是官方文档对该方法的注释及源码。(postDelayed类似,不再赘述)

    Causes the Runnable to be added to the message queue.The runnable will be run on the user interface thread.

    public boolean post(Runnable action) {    
      final AttachInfo attachInfo = mAttachInfo;
      if (attachInfo != null) {       
      return attachInfo.mHandler.post(action);
      }    
    // Assume that post will succeed later       
      ViewRootImpl.getRunQueue().post(action); 
      return true;
    }
    

    意思是将任务交由attachInfo中的Handler处理,保证在UI线程执行。从本质上说,它还是依赖于以Handler、Looper、MessageQueue、Message为基础的异步消息处理机制。相对于新建Handler进行处理更加便捷。因为attachInfo中的Handler其实是由该View的ViewRootImpl提供的,所以post方法相当于把这个事件添加到了UI 事件队列中。下面举一个常用的例子,比如在onCreate方法中获取某个view的宽高,而直接View#getWidth获取到的值是0。要知道View显示到界面上需要经历onMeasure、onLayout和onDraw三个过程,而View的宽高是在onLayout阶段才能最终确定的,而在Activity#onCreate中并不能保证View已经执行到了onLayout方法,也就是说Activity的声明周期与View的绘制流程并不是一一绑定。那为什么调用post方法就能起作用呢?首先MessageQueue是按顺序处理消息的,而在setContentView()后队列中会包含一条询问是否完成布局的消息,而我们的任务通过View#post方法被添加到队列尾部,保证了在layout结束以后才执行。

    下面我举一个例子,加深大家的理解。假如你有一个需求,需要在某个页面中加入一个点击事件:从AppBar(ActionBar)下滑出一个控件,并且在该控件出现的同时依然可以点击AppBar上的MenuItem。因此不能使用popupWindow,只能将该控件放置于布局内,同时为了不阻碍其他控件的交互,需要将该view的可见性设置为GONE。于是你写下下面的代码

    private void show(){
        if(myView.getVisibility()==View.GONE){
         myView.setVisibility(View.VISIBLE);
         }
        //对view执行动画
      }
    

    但运行后却发现view是一瞬间显现的,并没有执行动画,我想你也猜到了,问题在于动画开始时该View还未成功被设置为可见,导致动画失效。
    而把动画执行加入到View#post中后可以达到理想的效果

    private void show(){
      if(myView.getVisibility()==View.GONE){
         myView.setVisibility(View.VISIBLE);
      }
         myView.post(new Runnable() {
                @Override
                public void run() {
                    //对view执行动画
                }
            });
      }
    

    原因在于将view的可见性从GONE设置为VISIBLE时会申请requestLayout,而layout是异步执行的,所以setVisibility方法返回了但是该操作还未完成。前面说了post方法可以保证新任务是在layout调用过后执行。下面是setVisibility方法中比较关键的代码:

    if (newVisibility == VISIBLE) {
            if ((changed & VISIBILITY_MASK) != 0) {
                /*
                 * If this view is becoming visible, invalidate it in case it changed while
                 * it was not visible. Marking it drawn ensures that the invalidation will
                 * go through.
                 */
                mPrivateFlags |= PFLAG_DRAWN;
                invalidate(true);
    
                needGlobalAttributesUpdate(true);
    
                // a view becoming visible is worth notifying the parent
                // about in case nothing has focus.  even if this specific view
                // isn't focusable, it may contain something that is, so let
                // the root view try to give this focus if nothing else does.
                if ((mParent != null) && (mBottom > mTop) && (mRight > mLeft)) {
                    mParent.focusableViewAvailable(this);
                }
            }
        }
    
        /* Check if the GONE bit has changed */
        if ((changed & GONE) != 0) {
            needGlobalAttributesUpdate(false);
            // 这里申请了重新布局
            requestLayout();
    
            if (((mViewFlags & VISIBILITY_MASK) == GONE)) {
                if (hasFocus()) clearFocus();
                clearAccessibilityFocus();
                destroyDrawingCache();
                if (mParent instanceof View) {
                    // GONE views noop invalidation, so invalidate the parent
                    ((View) mParent).invalidate(true);
                }
                // Mark the view drawn to ensure that it gets invalidated properly the next
                // time it is visible and gets invalidated
                mPrivateFlags |= PFLAG_DRAWN;
            }
            if (mAttachInfo != null) {
                mAttachInfo.mViewVisibilityChanged = true;
            }
        }
    

    总结

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

    相关文章

      网友评论

      • Kuma_233:可以理解成是 在当前操作视图UI线程添加队列吗
        原来是控控:你可以先这么理解
      • 红日_b46d:挺好,核心点(异步问题):
        原因在于将view的可见性从GONE设置为VISIBLE时会申请requestLayout,而layout是异步执行的,所以setVisibility方法返回了但是该操作还未完成。前面说了post方法可以保证新任务是在layout调用过后执行。
        07f6379d020e:哪里看出request是异步的??
        原来是控控:@红日_b46d 是的 关键在于异步
      • 空而小sao:你好,刚好路过看到你的这篇文章。我刚碰到一个问题,listview 在adapter notifyDataSetChanged后,直接对某一个item进行类似你的动画操作,我发现用listview.post()操作就可以实现逻辑。原来的问题出在,listview notifyDataSetChanged的时候 getview 还没有开始就先执行我的“动画”操作了。所以这个post也是将任务add到 主线程listview绘制完成之后队列里吗??是不是一样的道理
      • 7f6cf2bef9d9:路过帮顶
      • ania:好多错别字,强迫症有点看不下去,求校正
        原来是控控:抱歉,某个段落中错别字是有点多,已改正
      • f1ef8442c8c8:三农,哥来了

      本文标题:不可不知的开发技巧之View.Post()

      本文链接:https://www.haomeiwen.com/subject/mdaqlttx.html