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
我们再