美文网首页Android技术知识Android开发Android开发经验谈
Android自定义View之requestLayout方法和i

Android自定义View之requestLayout方法和i

作者: AntDream | 来源:发表于2018-08-08 09:43 被阅读37次

    Android自定义View系列

    自定义View的时候难免会和这2个方法打交道,在之前的文章中我们已经介绍了invalidate方法,今天我们来对比下requestLayout方法和invalidate方法有什么不同之处

    等不及的先给个结论:

    requestLayout方法会导致View的onMeasure、onLayout、onDraw方法被调用;invalidate方法则只会导致View的onDraw方法被调用

    requestLayout方法源码分析

    //View.class
    @CallSuper
    public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();
    
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }
    
        //设置PFLAG_FORCE_LAYOUT标记位,这样就会导致重新测量和布局
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        //设置PFLAG_INVALIDATED就会进行重新绘制
        mPrivateFlags |= PFLAG_INVALIDATED;
    
        if (mParent != null && !mParent.isLayoutRequested()) {
            //不断调用上层View的requestLayout方法
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }
    

    在View的requestLayout方法中,首先会设置View的标记位,PFLAG_FORCE_LAYOUT表示当前View要进行重新布局,PFLAG_INVALIDATED表示要进行重新绘制。

    requestLayout方法中会一层层向上调用父布局的requestLayout方法,设置PFLAG_FORCE_LAYOUT标记,最终调用的是ViewRootImpl中的requestLayout方法。

    //ViewRootImpl.class
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    

    可以看到ViewRootImpl中的requestLayout方法中会调用scheduleTraversals方法,scheduleTraversals方法最后会调用performTraversals方法开始执行View的三大流程,会分别调用View的measure、layout、draw方法。

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
    
        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
    
        ...
        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
    
            resolveRtlPropertiesIfNeeded();
    
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                //调用onMeasure方法
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
    
            
            //设置PFLAG_LAYOUT_REQUIRED标记位,用于layout方法
            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }
    
        ...
    }
    

    由于requestLayout方法设置了PFLAG_FORCE_LAYOUT标记位,所以measure方法就会调用onMeasure方法对View进行重新测量。在measure方法中最后设置了PFLAG_LAYOUT_REQUIRED标记位,这样在layout方法中就会执行onLayout方法进行布局流程。

    public void layout(int l, int t, int r, int b) {
        ...
    
        //由于measure方法中设置了PFLAG_LAYOUT_REQUIRED标记位,所以会进入调用onLayout方法进行布局流程
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
    
            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }
    
            //取消PFLAG_LAYOUT_REQUIRED标记位
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
    
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }
    
        //取消PFLAG_FORCE_LAYOUT标记位
        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }
    

    由于measure方法中设置了PFLAG_LAYOUT_REQUIRED标记位,所以在layout方法中onLayout方法会被调用执行布局流程。最后清除PFLAG_FORCE_LAYOUT和PFLAG_LAYOUT_REQUIRED标记位。

    总结

    (1)requestLayout方法会标记PFLAG_FORCE_LAYOUT,然后一层层往上调用父布局的requestLayout方法并标记PFLAG_FORCE_LAYOUT,最后调用ViewRootImpl中的requestLayout方法开始View的三大流程,然后被标记的View就会进行测量、布局和绘制流程,调用的方法为onMeasure、onLayout和onDraw。

    (2)invalidate方法我们分析过,它的过程和requestLayout方法方法很像,但是invalidate方法没有标记PFLAG_FORCE_LAYOUT,所以不会执行测量和布局流程,而只是对需要重绘的View进行重绘,也就是只会调用onDraw方法,不会调用onMeasure和onLayout方法。

    测试

    简单自定义一个View,然后设计2个按钮,点击后分别调用View的requestLayout方法和invalidate方法,查看日志。

    测试界面

    点击invalidate按钮后调用View的invalidate方法


    可以看见只有onDraw方法被调用

    点击layout按钮调用View的requestLayout方法


    可以看见onMeasure、onLayout、onDraw方法都被调用
                        欢迎关注我的微信公众号,和我一起每天进步一点点!
    
    AntDream

    相关文章

      网友评论

        本文标题:Android自定义View之requestLayout方法和i

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