安卓初学者的博客

踽踽而行的安卓应用开发学习者

安卓开发学习之FrameLayout的layout过程

背景

前段时间我阅读了三大常用布局FrameLayout、RelativeLayout、LinearLayout的测量过程,众所周知,view的绘制过程有三大步:测量、布局、绘制,所以做事有始亦有终,我开始了阅读三大布局的layout过程,主要是阅读onLayout()方法

从框架布局开始,它的测量过程在文章安卓开发学习之FrameLayout测量流程源码阅读中。


onLayout

代码如下

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

直接调用了layoutChildren()方法


layoutChildren

方法不长,也就60多行,我就不分步骤了

代码如下

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();

        final int parentLeft = getPaddingLeftWithForeground(); // 子view各端点的极限
        final int parentRight = right - left - getPaddingRightWithForeground();

        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();

        for (int i = 0; i < count; i++) {
            // 遍历子view
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                int childLeft;
                int childTop;

                int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }

                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); // 横向重心
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; // 纵向重心

                // 根据重心设置子view左端和上端
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        if (!forceLeftGravity) { // forceLeftGravity默认是false
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    case Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }

                switch (verticalGravity) {
                    case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break;
                    default:
                        childTop = parentTop + lp.topMargin;
                }

                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
}

思路很简单,就是根据重心设置子view的左端和右端,而后根据子view的measuredWidth和measuredHeight确定右端和上端,就调用子view的layout()方法,子view的layout()方法、measure()、draw()方法的阅读记录,我会放在三大布局的onMeasure()、onLayout()、onDraw()方法结束后,写出来。


Gravity#getAbsoluteGravity

可以看到在layoutChildren()方法中,为了得到子view的横向重心,当前布局调用了Gravity.getAbsoluteGravity()方法,这其实就是位运算,我就不深入了

public static int getAbsoluteGravity(int gravity, int layoutDirection) {
        int result = gravity;
        // If layout is script specific and gravity is horizontal relative (START or END)
        if ((result & RELATIVE_LAYOUT_DIRECTION) > 0) { // 重心有效性
            if ((result & Gravity.START) == Gravity.START) { // 和父view的起点对齐
                // Remove the START bit
                result &= ~START;
                if (layoutDirection == View.LAYOUT_DIRECTION_RTL) { // 父view方向从右往左
                    // Set the RIGHT bit
                    result |= RIGHT;
                } else { // 默认起点是左边
                    // Set the LEFT bit
                    result |= LEFT;
                }
            } else if ((result & Gravity.END) == Gravity.END) { // 和父view的终点对齐
                // Remove the END bit
                result &= ~END;
                if (layoutDirection == View.LAYOUT_DIRECTION_RTL) { // 父view方向从左往右
                    // Set the LEFT bit
                    result |= LEFT;
                } else {
                    // Set the RIGHT bit
                    result |= RIGHT;
                }
            }
            // Don't need the script specific bit any more, so remove it as we are converting to
            // absolute values (LEFT or RIGHT)
            result &= ~RELATIVE_LAYOUT_DIRECTION;
        }
        return result;
}


结语

可以看到,框架布局的onLayout()它的onMeasure()还要简单,其实不只是框架布局,所有view的绘制过程最复杂的一步就是测量过程,跨过那道坎,后面的路会相对轻松一些

另外由于框架布局和它的父类ViewGroup都没有覆写onDraw()方法,所以它的绘制流程,是在View中实现的,但View不是调用的onDraw()方法,因为这是个空实现,要交给子类实现,真正调用的,是View.draw(canvas)方法,详情参见参加安卓开发学习之View的draw(canvas)方法一文
阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

安卓开发学习之FrameLayout的layout过程

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭