Android自定义View:ViewGroup(三)

栏目: IOS · Android · 发布时间: 5年前

内容简介:我们知道ViewGroup是一个组合View,它与普通的基本View(只要不是ViewGroup,都是基本View)最大的区别在于,它可以容纳其他View,这些View既可以是基本View,也可以ViewGroup,但是在我们的ViewGroup眼中,不管是View还是ViewGroup,它们都抽象成了一个普通的View,如图所示:

我们知道ViewGroup是一个组合View,它与普通的基本View(只要不是ViewGroup,都是基本View)最大的区别在于,它可以容纳其他View,这些View既可以是基本View,也可以ViewGroup,但是在我们的ViewGroup眼中,不管是View还是ViewGroup,它们都抽象成了一个普通的View, ViewGroup的最最根本的职责就是,在自己内部,给它们每一个人找一个合适的位置 ,也就是调用它们的如下方法:

public void layout(int left, int top, int right, int bottom)
复制代码

如图所示:

Android自定义View:ViewGroup(三)

这个方法,既确定了子View的位置,也确定了子View的大小 ,请注意,这个大小是由我们的 ViewGroup最后决定的分给该子View的屏幕区域大小

一般情况下,ViewGroup在设定这个大小时,会考虑子View的自身要求的,也就是它们measured的大小(getMeasuredWidth , getMeasuredHeight),通常最后给每个子View设定的大小就是它们所要求的大小,但这不是绝对的。

假如有一个二愣子性格的ViewGroup,它宣称:“我所有的子View的大小都必须是30*30的尺寸!”,这种SB的ViewGroup在调用每个子View的layout方法时,通过让bottom-top=right-left=30,就把所有的子View最后占据的屏幕区域设定为30*30了,不管各个子View所要求的大小是多少,此时都没有任何用处了。

当然,除了有特殊需求,我相信没人愿意用这种ViewGroup的,这里我们可以知道,我们自定义ViewGroup,大体上有两条路可选:

  • 一条就是让这个ViewGroup满足我们开发中的特定需求,这个时候,你可以随心所欲地去定义ViewGroup,反正我也只是自己用,不打算给别人用的。
  • 另一条就是自定义一个ViewGroup,提供给更多的人使用,这个时候,你就要遵守一些基本的规矩,让你的ViewGroup符合使用者的使用习惯和期望,这样大家才能愿意用你的ViewGroup。

    **那么使用者使用一个ViewGroup最基本的期望是什么?**我想,应该是使用者放入这个ViewGroup中的子View,layout出来的尺寸和每个子View measured的尺寸相符。只有这样,才能确保使用者的每个子View顺利完成自己的交互任务。

对于上面的图,有两点非常容易让人产生误解,需要解释一下:

  • 关于left、right、top、bottom。它们都是坐标值,既然是坐标值,就要明确坐标系,这个坐标系是什么?我们知道,这些值都是ViewGroup设定的,那么,这个坐标系自然也是由ViewGroup决定的了。 这个坐标系就是以ViewGroup左上角为原点,向右x,向下y构建起来的。

    ViewGroup的左上角又在哪里呢?我们知道,在ViewGroup的parent(也是ViewGroup)眼中,我们的ViewGroup就是一个普通的View,parent也会调用我们的ViewGroup的如下方法:

    //注意,这个layout方法是ViewGroup的parent在layout我们的ViewGroup,
    //不要和我们的ViewGroup layout自己的子View搞混了。
    public void layout(int left, int top, int right, int bottom)
    复制代码

    此时,我们ViewGroup的左上角,就是在parent的坐标系内的点(left,top)。好奇的你可能又问,假如我们的ViewGroup没有parent,它的左上角在屏幕上的位置又该如何确定?系统控制的Window都有一个DecorView,我们所能创建的View也好,ViewGroup也好,都是它的儿子、孙子、重孙、重重孙......,所以不用担心我们的ViewGroup没有parent,至于DecorView左上角在屏幕上的位置,是由系统帮我们决定的,我们不用操那么多心。

    由此我们看到,Google创建的这一套坐标系统非常的高效,只要确定DecorView左上角在屏幕上的位置,那么,所有的View在屏幕上的相对位置都可以精准地确定。

  • 第二点就是上图中代表ViewGroup的那个方框。

    • 那么这个方框是什么意思?
    • 是代表ViewGroup的大小吗?
    • 如果是的话,这个大小是不是ViewGroup在onMeasure方法中设定的各个子View大小的和?

    正确的答案是,这个方框是ViewGroup的parent在layout我们的ViewGroup时,给ViewGroup设定的大小 ,parent调用我们的ViewGroup的如下layout方法:

    /注意,这个layout方法是ViewGroup的parent在layout我们的ViewGroup,
    //不要和我们的ViewGroup layout自己的子View搞混了。
    public void layout(int left, int top, int right, int bottom)
    复制代码

    上图中,代表ViewGroup的方框的宽是上述方法中的 right-left ,方框的高是 bottom-top 。我们一般将这个宽高称为 availableWidth availableHeight (请记住这两个值,下面还要用到), 它们表示的是我们的ViewGroup总共可以获得的屏幕区域大小 (请仔细体会available的含义)。

    那么问题来了,假如我们的ViewGroup的parent是二球货,给我们的ViewGroup设定的宽高小于我们的ViewGroup measured的宽高,让我们的ViewGroup怎么优雅地layout自己的子View 呢?

    答案是:我们的ViewGroup在layout自己的子View时,想怎么layout就怎么layout,可以diao,也可以不diao parent给自己设定的尺寸。

    为什么是这样呢?既然可以不diao这个尺寸,为什么我们的ViewGroup还要辛苦地在onMeasure方法中计算每一个子View的宽高,还二乎乎地将它们的尺寸加起来,告诉它的parent呢?

ViewGroup如何优雅的Layout

ViewGroup在自己的layout方法中,获得了parent给自己设定的尺寸大小,即 availableWidth availableHeight 这个值相当于parent告诉ViewGroup:“请以你的左上角为圆点,向右为x,向下为y的坐标系,给你的每一个子View确定位置和大小。我可以向你保证,这个坐标系中的点P1(0,0)、点P2(availableWidth,0)、点P3(0,availableHeight)、点P4(availableWidth,availableHeight)组成的方框区域内的子View都可以获得在手机屏幕(这里指硬件意义上的屏幕)上展示自己的机会。这个方框之外的子View,能不能在手机屏幕上展示自己,我就管不了了。”

从这里我们看到,parent给我们的ViewGroup设定的尺寸,并不一定就完全对应着手机屏幕上的一块相同大小的区域,在有些情况下,parent给我们的ViewGroup设定的这个尺寸可能比整个手机屏幕还大。但是,parent仍然向我们保证,在该区域内layout的子View,都能获得在手机屏幕上展示自己的机会,parent是如何做到这一点的呢?答案是:通过parent的scroll功能。这里我们不详细叙述scroll,如果你不是很理解,请查看相关资料。

好奇的我们可能要问:“假如我是一个ViewGroup,我把一个子View的一部分layout在了parent给定的区域内,另一部分超出了该区域,这个子View是不是最多只能获得部分展示自己的机会?”不用怀疑,答案是:Yes!

你可能还要问:“那些完全被layout在parent限定的区域之外的子View怎么办呢?它们难道就该在无边黑暗中永不见天日吗?”这确实有点残酷,所以,作为一个ViewGroup,你可以有三个选择:

很简单,不要将子View 放到这个区域之外,万事大吉!
让你的ViewGroup实现scroll功能
将你的ViewGroup的parent换成ScrollView
Android自定义View:ViewGroup(三)

看到没?作为一个优秀的ViewGroup,当你layout自己的子View时,只要保证子View在availableWidth之内,即使超过了parent要求的高度也没有关系,开发者还是愿意使用你的,因为他们可以为你指定ScrollView作为parent。

这就是我们看到许多的ViewGroup在layout子View时,宁超高度,不超宽度的原因。

至此,你应该明白,上文中我们提出的,对于parent指定的availableWidth和availableHeight,作为ViewGroup还是要尽量不超过parent限定的区域,如果一定要超过的话,那就超availableHeight,而不要超availableWidth。

了解一下layout_gravity

我们看到,Android系统提供的FrameLayout、LinearLayout等都支持子View设定layout_gravity,它到底是干什么用的?我们自己自定义ViewGroup时能不能也用上它?

关于它的作用,一句话就能说明白,当ViewGroup给子View分配的空间超过子View要求的大小时,就需要gravity帮助ViewGroup为子View精确定位。可见,layout_gravity就是ViewGroup在layout阶段,协助ViewGroup给它的子View确定位置的,没错,就是协助确定子View的 left,top,bottom,right四个值。

下面,我们以FrameLayout为例来进行说明。假设FrameLayout中有一个子View,这个子View的所要求的展示尺寸(measuredWidth,measuredHeight)小于FrameLayout的尺寸,但是FrameLayout是个实心眼,它不管子View要求多大,都会把它所有的屏幕区域给子View,这样就可以保证,用户在这个区域中的交互动作,都是与子View的交互。那么问题来了,FrameLayout在layout子View时,总不能让它的left和top为0,right和bottom等于自己的宽和高吧。如果这么干,子View就要在这个尺寸下,绘制自己,就不可避免地要对它包含的drawables进行拉伸,展示效果必然受到影响,那怎么办?

FrameLayout会提取子View的 LayoutParams中的gravity,看看子View想在哪个位置,假设子View的layout_gravity的值是"top|left",那么FrameLayout就会把子View layout到自己的左上角,大小嘛就是子View所要求的大小。 但是请注意,虽然此时子View绘制时是按照自己要求的大小绘制的,但是,能与它发生交互的区域却是整个FrameLayout所占的屏幕区域。

所以,要不要使用layout_gravity,就看你自定义的ViewGroup是不是给子View分配大于它们要求的空间。

下面我就举一个简单的例子来说明。

假设ViewGroup现在要layout一个子View,如下是该子View要求的尺寸大小:

final int childWidth = child.getMeasuredWidth(); 
final int childHeight = child.getMeasuredHeight();
复制代码

现在,ViewGroup要给这个子View设定位置和大小了。设定的位置和大小用如下四个参数表示:

bigLeft,bigTop,bigRight,bigBottom。
复制代码

这四个值在ViewGroup的以左上角为原点,向右x,向下y的坐标系中构成了一个矩形。如下:

Rect bigRect = new Rect( bigLeft, bigTop, bigRight, bigBottom);
复制代码

进一步假设这个bigRect的宽高大于子View要求的宽高(是为了更明显地说明layout_gravity的作用,实际情况可能不是这样的),如下图所示:

Android自定义View:ViewGroup(三)

现在ViewGroup准备把bigRect区域全部分给子View,但是ViewGroup显然不能直接这样layout 子View:

child.layout(bigLeft,bigTop,bigRight,bigBottom);
复制代码

这样的话,child就要在bigRect区域内绘制自己,不可避免地要拉伸自己,导致展示的效果变差(想像一下10 10的图片扩成100 100是什么效果)。所以,我们需要在bigRect内进一步为子View定位,怎么定位?

  • 第一步就是读出子View的LayoutParams对象中的layout_gravity值 。如下:
final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 
int child_layout_gravity = lp.gravity;
复制代码

从上面代码可以看出,layout_gravity最终是以整数的形式存放于子View的LayoutParams中的。

  • 第二步就是构建一个空的Rect,准备接收为子View定位后的四个坐标值 ,如下:
Rect smallRect = new Rect();
复制代码
  • 第三步就是见证奇迹的时刻,如下:
Gravity.apply(child_layout_gravity, childWidth, childHeight, bigRect, smallRect);
复制代码

经过上面的调用,Gravity会在smallRect中存入依据子View的layout_gravity以及子View要求的尺寸,在bigRect中为子View精确定位后的坐标值,注意这个坐标值所在的坐标系还是ViewGroup的坐标系。所以,我们现在可以愉快地layout子View了。

child.layout(smallRect.left, smallRect.top, smallRect.right, smallRect.bottom);
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Tagging

Tagging

Gene Smith / New Riders / 2007-12-27 / GBP 28.99

Tagging is fast becoming one of the primary ways people organize and manage digital information. Tagging complements traditional organizational tools like folders and search on users desktops as well ......一起来看看 《Tagging》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

URL 编码/解码
URL 编码/解码

URL 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试