RelativeLayout的gravity和ignoreGravity

Android开发中使用最多的ViewGroup应该就是RelativeLayout了,虽然众所周知它在Measure的时候会测量两次子View,耗时会略微多一点,但是毕竟比其他布局更加灵活,熟练使用的话可以大大减少布局的嵌套,避免IDE提示你TooDeepLayout的时候才反应过来已经嵌套了太多的ViewGroup.

使用RelativeLayout的时候常用的应该就是给子View设置一些属性,例如子View相对于父Layout的layout_centerInParent,layout_centerHorizontallayout_alignParentTop,layout_alignParentLeft等等这类属性,以及子View相对于其他子View的layout_above,layout_toLeftOf和类似的属性,常用RelativeLayout的开发者应该对这些属性都很熟悉了,能都做到熟练使用的话一般的布局应该都不成问题了.所以就不在这里废话了.今天总结的主要是RelativeLayout一个不太常见的属性–gravity(或者是我和周围的同事不太常用的).

gravity属性

有一天你的PM让你实现一个界面,大概是这样:

gravity示例

要求是中间这几个button占用的空间不论button怎么变化始终都在父布局的中间.

看起来并不难,父布局用一个RelativeLayout,写一个width和height都为wrap_content的RelativeLayout设置属性layout_centerInParent为true.布局中second button放在first button的右边,third button放在first button下面。这样不论三个button大小怎么变,他们所占的空间始终是子布局的RelativeLayout的大小,所以始终在父布局的正中间.如果觉得性能不够好的话也可以把父布局设为FrameLayout,然后子RelativeLayout设置的属性为layout_gravity="center",也可以达到同样的效果.
代码如下:

main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true">

<Button
android:text="first button"
android:id="@+id/btn_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

<Button
android:text="second button"
android:id="@+id/btn_2"
android:layout_toRightOf="@id/btn_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

<Button
android:text="third button"
android:id="@+id/btn_3"
android:layout_below="@id/btn_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

</RelativeLayout>
</RelativeLayout>

虽然是两层嵌套,但是达到了我们的效果,这也算是RelativeLayout的强大之处.不过这个界面并不复杂还好,如果说一个界面中存在很多这种情况,积少成多,都用这种方法解决可能就有点性能上的问题了.

这时候就是gravity出场的时候了,我们把xml代码修改一下,去掉子RelativeLayout,给父布局加上gravity="center"的属性,代码如下:

main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">


<Button
android:text="first button"
android:id="@+id/btn_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>


<Button
android:text="second button"
android:id="@+id/btn_2"
android:layout_toRightOf="@id/btn_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>


<Button
android:text="third button"
android:id="@+id/btn_3"
android:layout_below="@id/btn_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>


</RelativeLayout>

一层ViewGroup,就达到了同样的效果~~

这样使用的优势是什么呢?比如说如果在保持几个子View之间相对位置不变的情况下,要把这个整体放在父布局底部居中,如图:

second

平时遇到这样的情况会怎么做呢?仍然是两层嵌套?或者是先定好third button的位置,再让其他两个View依赖于third button?似乎都不是很好的解决方案.而使用gravity的话只需要设定一个gravity="bottom|center_horizontal",效果就达到了~

然而需求永远大于实现,这时候你的PM又说了,除了这个,还有一个单独的控件,要放在界面上不依赖其他控件,但依赖于父布局的位置.但是这时候我们已经给父布局设定了一个gravity了,看起来所有的子View都会被父布局的属性所影响,怎么解决呢?说实话我看了半天源码也不是很明白Google工程师是怎么想的,但是呢他们就是提供了这么一个属性ignoreGravity,猜也能猜出来,它能让gravity失效,还是用代码说话吧~

比如布局是这样的:

second

代码如下:

main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom"
android:ignoreGravity="@+id/btn_4"
>

<Button
android:text="first button"
android:id="@+id/btn_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

<Button
android:text="second button"
android:id="@+id/btn_2"
android:layout_toRightOf="@id/btn_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

<Button
android:text="third button"
android:id="@+id/btn_3"
android:layout_below="@id/btn_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

<Button
android:text="forth button"
android:id="@id/btn_4"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

</RelativeLayout>

这时候我们可以看到forth button已经不再受gravity属性的影响了,这时候就可以随意给forth button设置相对父布局的一些属性了.

但是!还没有完,为啥第三张图中的三个button我都放在左下角,也就是gravity="bottom",而不是继续用gravity="bottom|center_horizontal"呢,因为这时候突然不生效了,也就是发生了一些意外的布局冲突,没有达到我们想要的效果.源码之前了无秘密,说了这么多还是得靠源码解决一些问题呀,知其然也要知其所以然,还是研究下RelativeLayout的源码知道其基本的原理比较好.看看ignoreGravity相关的:

RelativeLayout.java

1
2
3
if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) {
ignore = findViewById(mIgnoreGravity);
}

源码中,关于ignoreGravity比较关键的就这两句,写的很明白:只有在gravity不为center_horizontal也不为center_vertical的时候,这个ignoreGravity才能切实发挥作用,至于具体的原因,就得继续深扒RelativeLayout的源码了,本人对RelativeLayout的研究还是不够,所以不敢自己乱加猜测了.

当然了,如果子View只是互相依赖的话这样用就没问题,但是如果给子View使用了相对于父布局的一些属性的话可能会有一些冲突,这个就看大家使用的时候怎么取舍了,毕竟是一个不那么常用的属性.不过这个gravity也足够帮我们解决很多问题了吧.