概论:
- 三种自定义的比较
- 继承自ViewGroup
- onMeasure()
- onLayout()
- 自定义LayoutParams
- 稍微说下draw吧
- 参考链接
1.三种自定义的比较
Custom Views
- Extending the View
- Overriding onMeasure()
- Overriding onDraw()
- Saving state using the BaseSavedState pattern
- Working with custom attributes
- Understanding and applying requestLayout and invalidate
In contrast, you can ignore the following details when creating custom views:
- Overriding onLayout()
- Implementating and using LayoutParams
Compound Controls
- Extend an existing layout
- Saving state using the BaseSavedState pattern
- Taking control of saving state for its child views
- Working with custom attributes
While you can ignore:
- Overriding onMeasure()
- Overriding onDraw()
- Overriding onLayout()
- Worrying about requestLayout() and invalidate()
- Implementing and using LayoutParams
Custom Layouts
- Inherit from ViewGroup
- Override onMeasure()
- Override onLayout()
- Implement custom LayoutParams with any additional layout attributes
- Override layout parameters construction methods in the custom layout class
2.继承自ViewGroup
流水线布局的样子:

这里的代码和自定义view差不多,如下:
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
| public class FlowLayout extends ViewGroup { private static final String tag="FlowLayout"; private int hspace=10; private int vspace=10; public FlowLayout(Context context) { super(context); initialize(context); } public FlowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray t = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout, 0, 0); hspace = t.getDimensionPixelSize(R.styleable.FlowLayout_hspace, hspace); vspace = t.getDimensionPixelSize(R.styleable.FlowLayout_vspace, vspace); Log.d(tag,"hspace:" + hspace); Log.d(tag,"vspace:" + vspace); t.recycle(); initialize(context); } public FlowLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } private void initialize(Context context) { } ... }
|
就是自定义属性多了个布局属性,如下:
1 2 3 4 5 6 7 8 9
| <resources> <declare-styleable name="FlowLayout"> <attr name="hspace" format="dimension"/> <attr name="vspace" format="dimension" /> </declare-styleable> <declare-styleable name="FlowLayout_Layout"> <attr name="layout_space" format="dimension"/> </declare-styleable> </resources>
|
3.onMeasure()
这里需要注意的地方比较多,也有点难理解。
由于是ViewGroup
,你需要先measure children,但是比不能直接用child.measure()
,因为这个方法完全没考虑ViewGroup的MeasureSpec,你应该用的是ViewGroup.measureChild()
。到最后你setMeasuredDimension()
,还需要用resolveSize(w, widthMeasureSpec)
来计算ViewGroup的实际尺寸,因为你所有的children的尺寸有可能超出ViewGroup的尺寸,所以框架提供了这个方便的方法让我们使用。
看下这个图有助于了解接下来的代码:

示例代码如下:
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int rw = MeasureSpec.getSize(widthMeasureSpec); int rh = MeasureSpec.getSize(heightMeasureSpec); int h = 0; int w = 0; int h1 = 0, w1=0; int numOfChildren = this.getChildCount(); for (int i=0; i < numOfChildren; i++ ) { View child = this.getChildAt(i); this.measureChild(child,widthMeasureSpec, heightMeasureSpec); int vw = child.getMeasuredWidth(); int vh = child.getMeasuredHeight(); if (w1 + vw > rw) { w = Math.max(w,w1); w1 = 0; h1 = h1 + vh; } int w2 = 0, h2 = 0; w2 = w1 + vw; h2 = h1; h = Math.max(h,h1 + vh); LayoutParams lp = (LayoutParams)child.getLayoutParams(); lp.x = w1; lp.y = h1; w1=w2; h1=h2; } w = Math.max(w1,w); setMeasuredDimension( resolveSize(w, widthMeasureSpec), resolveSize(h,heightMeasureSpec)); };
|
这里还需要说明的是当我们onLayout()
的时候我们需要知道children的坐标和尺寸,尺寸不用担心,已经有了,但原点(左上角)坐标呢?这当然就是layout parameters的功用了,LayoutParams
就是ViewGroup让children保存的数据,定义LayoutParams
还是在ViewGroup中定义的。
1 2 3
| LayoutParams lp = (LayoutParams)child.getLayoutParams(); int spacing = lp.spacing;
|
具体的LayoutParams
的定义还有ViewGroup如何调用它稍后再讲。
4.onLayout()
这里没太多可说的,直接依次调用child.layout()
方法就行了,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Override protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) { int numOfChildren = this.getChildCount(); for (int i=0; i < numOfChildren; i++ ) { View child = this.getChildAt(i); LayoutParams lp = (LayoutParams)child.getLayoutParams(); child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y + child.getMeasuredHeight()); } }
|
5.自定义LayoutParams
LayoutParams 是由child views生成和持有的,但是是在ViewGroup中定义的,原因很简单,ViewGroup需要child的这些信息,当然得由他来定义需要哪些信息。看代码吧:
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
| public static class LayoutParams extends ViewGroup.MarginLayoutParams { public int spacing = -1; public int x =0; public int y =0; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout_Layout); spacing = a.getDimensionPixelSize(R.styleable.FlowLayout_Layout_layout_space, 0); Log.d(tag,"child spacing:" + spacing); a.recycle(); } public LayoutParams(int width, int height) { super(width, height); spacing = 0; } public LayoutParams(ViewGroup.LayoutParams p) { super(p); } public LayoutParams(MarginLayoutParams source) { super(source); } }
|
还有个问题,child的ViewGroup可以是LinearLayout 或者FlowLayout,child view是怎么知道该构造哪个布局的LayoutParams
呢?这当然要Android SDK来解决了。当一个view被放到布局中时,框架会调用如下四个方法来构造view的LayoutParams
。
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
| @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new FlowLayout.LayoutParams(getContext(), attrs); } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } @Override protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof FlowLayout.LayoutParams; }
|
6.稍微说下draw吧
你当然可以在viewGroup上画点东西,当你想在所有child都画好后再话东西的话,就用dispatchDraw()
,当然你得先call super.dispatchDraw()
。现在画的东西就是在最上层。如果你想在child之前画点东西的话,当然就要用onDraw()
了,注意,你先得配置setWillNotDraw(false)
,因为默认ViewGroup是不会画东西的,就不会调用onDraw()
。
7.参考链接
http://reacoder.github.io/2014/11/23/android-good-video/
《ExpertAndroid》