自定义View的流程:Layout

之前讲了下自定义View的measure过程,下面说说layout过程,layout过程,对于View来说,layout的过程就是确定自己的位置的过程。view的layout方法的源码如下:

    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//重点代码

        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;
            }

            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);
                }
            }
        }

        final boolean wasLayoutValid = isLayoutValid();

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

        if (!wasLayoutValid && isFocused()) {
            mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
            if (canTakeFocus()) {
                // We have a robust focus, so parents should no longer be wanting focus.
                clearParentsWantFocus();
            } else if (getViewRootImpl() == null || !getViewRootImpl().isInLayout()) {
                // This is a weird case. Most-likely the user, rather than ViewRootImpl, called
                // layout. In this case, there's no guarantee that parent layouts will be evaluated
                // and thus the safest action is to clear focus here.
                clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
                clearParentsWantFocus();
            } else if (!hasParentWantsFocus()) {
                // original requestFocus was likely on this view directly, so just clear focus
                clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
            }
            // otherwise, we let parents handle re-assigning focus during their layout passes.
        } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
            mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
            View focused = findFocus();
            if (focused != null) {
                // Try to restore focus as close as possible to our starting focus.
                if (!restoreDefaultFocus() && !hasParentWantsFocus()) {
                    // Give up and clear focus once we've reached the top-most parent which wants
                    // focus.
                    focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
                }
            }
        }

        if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
            mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
            notifyEnterOrExitForAutoFillIfNeeded(true);
        }
    }

可以看到这个方法里面调用了setOpticalFrame(l, t, r, b) 或者 setFrame(l, t, r, b)方法,通过查看setOpticalFrame方法的源码发下,它里面最终还是调用了setFrame(l, t, r, b),所以setFrame(l, t, r, b)方法确定view自身的位置的,接着往下看,会看到onLayout方法,可能看到这里,有些人会有疑问,怎么view中也有onLayout方法,我们知道onLayout方法其实是为自己的子view确定位置的,view是不包含子view的,那么这个方法具体做了什么呢?`

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

哈哈,看到View类中的onLayout是空实现的,估计大家就放心了。正好和我们的设想相同,view不包含子view,这个方法当然不用去做具体实现,可能大家会想,既然这个方法是空实现,那何必不将这个方法去掉呢?其实我们的ViewGroup也是继承View的,虽然view自身用不到,但是其子类ViewGroup是要用到的,所以,这个方法还是不能去掉,但是,可能有人会说,既然ViewGroup用到,View用不到,那何必不将这个方法直接放到ViewGroup类中呢,当然,是可以这么处理,但是为了流程的统一性,以及面向对象的思想,这个onLayout方法放到View类中会更好。对于ViewGroup来说,它重写了父类View的layout方法,并将这个重写发layout方法用final修饰,表示自己的子类不能在重写这个layout方法,ViewGroup的layout方法是确定自身的位置,onLayout方法是确定子view的位置,
下面看看ViewGroup的layout方法的具体实现:

    @Override
    public final void layout(int l, int t, int r, int b) {
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            if (mTransition != null) {
                mTransition.layoutChange(this);
            }
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }

从这个方法中,可以看到,它里面调用了super.layout方法,其实就是View的layout方法,后续的流程就和前面分析的一致了,但是有一些区别,那就是,ViewGroup重写了View的onLayout方法,这是因为,ViewGroup是可能有自己的子view的,它要确定子view的摆放位置,所以自己要重新实现onLayout方法,ViewGroup的onLayout方法的具体实现如下:

    @Override
    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);

嗯?怎么ViewGroup的onlayout方法是个抽象方法,其实看到这里,也不必大惊小怪的,因为,每个具体的父容器,他们的布局特性都是不同的,所以,他们的子view的摆放方式也是不同的,也就没法去具体的实现,只能让具体的父容器,自己去根据自己的布局特性去实现了。比如:LinearLayout的布局就有两种方式,一种是Vertcal,一种是Horizontal,这两个方向不同,子view的摆放方式就不一样。ViewGroup中通过layout调用onLayout,onLayout方法又去调用其子view的layout方法,通过递归调用,完成所有子view的摆放。下面结合一个具体的例子,在来阐述一下,layout的过程,假如一个页面,根节点是个LinearLayout,orientation是Vertical的,LinearLayout中又包含了一个TextView,这时,如果开始摆放LinearLayout,则会先调用LinearLayout的layout方法,由于Linarlayout是继承自ViewGroup,而ViewGroup又继承自View的,所以,调用linearlayout的layout方法,其实就是调用View类中的layout方法,这样就会执行到setFrame方法,这样就完成了LinearLayout自身的位置的确定,接着继续向下执行View类中layout方法的代码,这时onLayout方法就会执行,由于,linearLayout重写了onLayout方法,执行的就是LinearLayout类中的onLayout方法,下面我们看看LinearLayout方法中的onLayout方法的具体实现:

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical(l, t, r, b);
        } else {
            layoutHorizontal(l, t, r, b);
        }
    }

由于我们例子中,开始的orientation是Vertical的,所以,就会执行layoutVertical这个方法,下面继续看layoutVertical(l, t, r, b)的具体实现:

void layoutVertical(int left, int top, int right, int bottom) {
        final int paddingLeft = mPaddingLeft;

        int childTop;
        int childLeft;

        // Where right end of child should go
        final int width = right - left;
        int childRight = width - mPaddingRight;

        // Space available for child
        int childSpace = width - paddingLeft - mPaddingRight;

        final int count = getVirtualChildCount();

        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

        switch (majorGravity) {
           case Gravity.BOTTOM:
               // mTotalLength contains the padding already
               childTop = mPaddingTop + bottom - top - mTotalLength;
               break;

               // mTotalLength contains the padding already
           case Gravity.CENTER_VERTICAL:
               childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
               break;

           case Gravity.TOP:
           default:
               childTop = mPaddingTop;
               break;
        }

        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();

                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();

                int gravity = lp.gravity;
                if (gravity < 0) {
                    gravity = minorGravity;
                }
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                                + lp.leftMargin - lp.rightMargin;
                        break;

                    case Gravity.RIGHT:
                        childLeft = childRight - childWidth - lp.rightMargin;
                        break;

                    case Gravity.LEFT:
                    default:
                        childLeft = paddingLeft + lp.leftMargin;
                        break;
                }

                if (hasDividerBeforeChildAt(i)) {
                    childTop += mDividerHeight;
                }

                childTop += lp.topMargin;
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }

这个方法的核心逻辑就是,遍历LinearLayout中的所有直接子view(这里的直接子view是指儿子节点),并获取到直接子view的测量的宽高,并调用
setChildFrame(child, childLeft, childTop + getLocationOffset(child),childWidth, childHeight);
下面来看setChildFrame方法的具体实现:

    private void setChildFrame(View child, int left, int top, int width, int height) {
        child.layout(left, top, left + width, top + height);
    }

这个方法内部就是调用了layout方法,我们知道layout方法就是用来来确定各个子view的位置的,这样就完成了LinearLayout中自己的各个子view的位置的摆放。具体我们这个例子来说,就完成了TextView的位置的摆放。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值