使用FrameLayout进行自定义ViewGroup-测量和布局策略

一、引言

很多时候,当android系统控件不能满足我们的业务需求时,我们会考虑实现自定义view。而自定义View可以分为两种情况,一种是想实现View,另一种是想实现ViewGroup。

本文的介绍是自定义ViewGroup中所采用的一种常见方式是继承FrameLayout来实现需求。

二、FrameLayout的优势

熟练掌握anroid布局的程序员都知道,android系统提供了四种主要的布局形式,LinearLayout、RelativeLayout、GridLayout、FrameLayout。

其中LinearLayout与GridLayout对于复写不是很友好,因为它本身已经限定了布局方式是特殊话的,很难再进行改造。而RelativeLayout与FrameLayout的布局方式 相对的属性较多,用户的可控性大,同时,继承之后的可塑性更强。

两种比较的话,RelativeLayout的实现更复杂一些,如果本身对其了解不够深入的话,可能会遇到很多的问题。FrameLayout的布局理念和实现都相对简单,同时又比ViewGroup多了具体的测量和布局实现、。

三、FrameLayout的测量策略

1.FrameLayout的测量理念

全部子View都单独测量,选择最大的子View测量结果作为FrameLayout的测量大小

2.FrameLayout的测量步骤

(1)关于MeasureSpec【来自父view的限制】

理解MeasureSpec是测量的首要要求,因为onMeasure的两个参数就是widthSpec和HeightSpec。

下面介绍MeasureSpec的含义和作用:

a.measureSpec是一个int值,它的32位数据中,包含了两种信息,mode和size。它的高两位是mode,剩下30位是size。

其中mode可选的有三种 0 1 2分别是UNSPECIFIED、EXACTLY、AT_MOST。

UNSPECIFIED: 未指定,也就是没有规则

EXACTLY:限制了大小,大小在size字段

AT_MOST:限制了最大的大小,大小在size字段

需要注意的是,提供的mode只是父view给当前view测量的一个参考和建立,你可以遵从也可以不遵从。

比如提供了mode为EXACTLY,正常情况下我们会尊重父view的意见,设置自己为对应的size(事实上默认view的测量结果也是这样的,可见view的getDefaultSize方法).

但是你也可以拒绝这种意见,设置自己的大小为想要的,但是你可能也会付出代价(在layout的时候父view并不给你足够的大小)。

所以,一切取决于你想做什么。

(2)关于LayoutParams【来自子View的请求】

如果你是一个view,你关注meaurespec很大程度上就能解决问题,但是如果你是一个ViewGroup。你的子view的LayoutParams也会影响到你的测量(因为你是一个容器,你需要尽可能的保证子view能够达到满意的状态)。

而LayoutParams就是子view在测量或布局过程中,所能提供给父view的请求信息,以为父view为子view的measureChild过程提供参考(同样,父view也可以忽略它)。

LayoutParams最重要也是不可缺的两个属性是layout_width和layout_height。

经验丰富的开发者知道它可以有三种选择:
1. 一个确定的大于0的dp值

2.MATCH_PARENT

3.WRAP_CONTENT
简要说明:

一个确定的大于0的dp值:即子view期待父布局给自己这样一个大小

MATCH_PARENT:子view不知道自己的大小,但是希望和父布局一样大

WRAP_CONTENT:子view也不知道自己的大小,它想测量自己完之后再做决定

此时,我们想知道默认情况下ViewGroup是如何测量的,

事实上,ViewGroup的onMeasure方法是空的,但是它提供了一个方法,这是它建议的测量策略-ViewGroup的getChildMeasureSpec方法。

这个方法并没有决定ViewGroup的大小,而是确定了子View的measureSpec,然后在测量完子view后可以根据整体来决定ViewGroup本身的大小。

现在假设ViewGroup是需要测量的控件,parentMeasureSpec是父view提供的限定,即上面的(1),layoutparams是子View提供的建议。

这两个每个都有三种情况,自由组合的话有九种可能性。读者可自行阅读这部分的代码,比较容易理解。

(3)最终大小的设定

ViewGroup需要将测量结果确定下来(因为父布局会拿来决定自己的大小),所以需要有一个办法能够设置自己的大小,

这个方法是setMeasuredDimension。这个方法需要在onMeasure里面调用,因这个onMeasure方法执行完,可能会回到父view的测量方法中,它可能会很快用到。

同样,父view只需要执行child.getMeasureWidth等...方法来获取子view大小

(4)FrameLayout的解决方案

1.首先对所有的view都进行测量一遍,使用默认建议的测量方式,并考虑margin。(measurechildwithMargin) [如果你对layoutparams为matchparent的child是如何测量的有疑问,可以参考getChildMeasureSpec这个默认的处理方式]

2.找到layoutparams为match_parent的child,放到一个list里面。等非match_parent的测量完之后,注意此时framelayout的大小已经可以确定,因为所有有大小想法的view都已经测量完毕,FrameLayout可以setMeasuredDimension了。

3.对于list里面的child,需要进行再次测量,因为之前match_parent但是具体大小未定,现在定下来后,改变child的measurespec为excatly,然后让子view正确的测量。

(5) 为什么需要(4)中的3

需要对child再次测量,这是每个viewGroup的责任,你应当至少需要对child进行一次测量(这样子view可以设置自己的measureWidth).这是一个健康的流程。

如果你不设置,那么在接下来layout的时候便获取不到子view的大小,那么布局没有参考,此时会比较为难,除非你确定不需要子view的数据就可以重新布局它。

但是如果你的子view本身是viewGroup,那么它如果没有child的measureWidth,也很难走下去。

小结:测量的主要目的是让各个view都了解并设置自身的大小,以便在接下来的layout过程中能够提供恰当的参考,所以,不用做多,也不要不做

四、使用FrameLayout自定义ViewGroup在测量方面可以做哪些操作

1.如果你不想某些view的测量结果影响FrameLayout的大小

事实上,你可以将FrameLayout的onMeasure方法,在继承的方法里重新写一遍,然后根据自己的逻辑调整,这是最简单也是最容易改的方法。但有时候想尽量减少写代码的数量(代码多、bug就多).

2.不想拷贝代码

那么你只能通过继承measureChildWIthMargin想想办法了,这样可能会产生意想不到的bug。

五、FrameLayout的布局策略

..待补充

 

posted @ 2018-06-05 12:42  lightverse  阅读(538)  评论(0编辑  收藏  举报