Bootstrap

布局ViewGroup原理解析(三):RelativeLayout

Android中最基础的三大布局为FrameLayout、LinearLayout与RelativeLayout。

记得刚初学Android 的时候最喜欢用LinearLayout因为它简单易用,但是越做到后面越喜欢使用RelativeLayout,因为它的灵活性和适用范围都要比前两者要好,但是这里也要提醒初学者,不要用RelativeLayout做太多嵌套,会产生性能问题。今天讲的就是在项目开发过程中遇到的一个关于RelativeLayout的问题。

问题出现与初步解决

在一次版本迭代中,UI宝宝给出了一个类似于在布局上需要子View超出父布局的设计稿。因为之前没有做过类似的需求,在网上查了一下大概是这样:

Android View的绘制布局过程中,子布局默认是无法超出父布局显示的,如果有类似需要超出父布局的需求,可以通过设置根布局的clipChildren属性。

什么嘛?原来这么简单就可以了,于是我赶紧写了一个demo试了一下:

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#987"
    android:clipChildren="false">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="400dp"
        android:background="@color/colorAccent">
        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginTop="450dp"
            android:background="#fff"/>
    </RelativeLayout>
</RelativeLayout>

最后展示的样式如下,白色的ImageView超出了粉红的RelativeLayout显示了出来,基本达到了预期的效果。

在这里插入图片描述

初步解决失败

正当我欢天喜地的去做需求的时候,又发现了另外一个问题:我的需求是一个子View超出RelativeLayout显示,但是不是全部超出而是部分超出。于是我按照刚才的方式布局后发现,部分超出的布局和相信中不太一样,如下图:

在这里插入图片描述

整体布局与刚才的类似,这不过marginTop设置为350 dp,使得最后的ImageView有50 dp是超出父布局的,然而结果是超出的50 dp被裁掉了。

为了确定产生这个问题的原因,需要思考另一个问题:是ImageView有部分没有被绘制出来,还是ImageView的高度被压缩成50dp了?

这个也简单,把鼠标点在studio预览中ImageView的位置,会发现给出的ImageView的边界高度只有50dp了。这代表这在RelativeLayout在布局的过程中把ImageView的高度裁剪掉了。产生的最终原因我们已经知道,但是为什么RelativeLayout会产生这个问题呢?这个就需要我们去源码中寻找答案了。

不得不说源码的越多过程实在是很头疼,特别是像RelativeLayout这种相对复杂的系统控件。我在网上也看到了一些人的源码解析都很棒,但在这里我希望自己能以最简单的方式把这个解析说清楚。

源码解析

首先我们需要知道,任何一个View或者是ViewGroup在呈现到界面上都需要经过三个阶段:

  • 测量onMesure
  • 布局onLayout
  • 绘制onDraw

对于一个非布局View来说,测量和绘制是其中的比较重要的两个步骤;而对于一个布局ViewGroup来说测量和布局是其中比较重要的两个步骤。

布局阶段onLayout

ViewGroup测量过程需要确定自身的宽高与其子View的宽高,布局则是确定其子View与他的相对位置(主要ViewGroup其本身的位置不是在它的布局过程中确定的,而是在其父ViewGroup布局过程中确定的)。我们可以先看RelativeLayout布局过程是如何完成的:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //  The layout has actually already been performed and the positions
        //  cached.  Apply the cached values to the children.
        final int count = getChildCount();

        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                RelativeLayout.LayoutParams st =
                        (RelativeLayout.LayoutParams) child.getLayoutParams();
                child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
            }
        }
    }

整个方法非常简单,对比其他ViewGroup(如FrameLayout)就会发现,其中并不包含关于子View该如何摆放的逻辑计算,直接把子View的LayoutParams中的mTop等布局属性设置进去就好了,那关键在于这些布局属性是在哪里被赋值的呢?

测量阶段onMeasure

我们再

;